├── .editorconfig ├── .eslintignore ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── actions │ └── setup │ │ └── action.yml ├── release-drafter.yml └── workflows │ ├── build-apk.yml │ ├── ci.yml │ ├── jekyll-gh-pages-deploy.yml │ └── npm-publish.yml ├── .gitignore ├── .markdownlint.json ├── .npmignore ├── .nvmrc ├── .watchmanconfig ├── .yarnrc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.0.9.2.MD ├── README.1.1.x.MD ├── README.MD ├── _config.yml ├── android ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── src │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── jimmydaddy │ │ └── imagemarker │ │ ├── ImageMarkerManager.kt │ │ ├── ImageMarkerPackage.kt │ │ ├── ImageProcess.kt │ │ ├── MarkerImageLoader.kt │ │ └── base │ │ ├── Constants.kt │ │ ├── CornerRadius.kt │ │ ├── ErrorCode.kt │ │ ├── ImageOptions.kt │ │ ├── MarkImageOptions.kt │ │ ├── MarkTextOptions.kt │ │ ├── MarkerError.kt │ │ ├── MarkerInsets.kt │ │ ├── Options.kt │ │ ├── Padding.kt │ │ ├── Position.kt │ │ ├── PositionEnum.kt │ │ ├── RNImageSRC.kt │ │ ├── Radius.kt │ │ ├── SaveFormat.kt │ │ ├── ShadowLayerStyle.kt │ │ ├── TextBackgroundStyle.kt │ │ ├── TextOptions.kt │ │ ├── TextStyle.kt │ │ ├── Utils.kt │ │ └── WatermarkImageOptions.kt │ └── test │ └── java │ └── com │ └── jimmydaddy │ └── imagemarker │ └── base │ └── CornerRadiusTest.kt ├── app.plugin.js ├── assets ├── AndroidMarker.gif ├── IOSMarker.gif ├── alphabgonly.png ├── alphicononly.png ├── icon.png ├── imagewatermark.png ├── multiple_icon_markers.png ├── multipletexts.png ├── rotatebg.png ├── rotateicon.png ├── rotateimageicon.png ├── rotatetexts.png ├── rotatetexts_1.png ├── sample1.png ├── sample2.png ├── shadow.jpeg ├── shadow_bg_fit.jpeg ├── shadow_bg_sx.jpeg ├── shadow_bg_sy.jpeg ├── textbgcornerradius.png └── textswihoutbg.png ├── babel.config.js ├── docs ├── custom.css ├── latest │ ├── .nojekyll │ └── assets │ │ ├── custom.css │ │ ├── highlight.css │ │ ├── main.js │ │ ├── search.js │ │ └── style.css └── v1.0.x │ ├── .nojekyll │ └── assets │ ├── highlight.css │ ├── main.js │ ├── search.js │ └── style.css ├── example ├── .bundle │ └── config ├── .eslintrc.js ├── .node-version ├── .watchmanconfig ├── Gemfile ├── android │ ├── app │ │ ├── build.gradle │ │ ├── debug.keystore │ │ ├── proguard-rules.pro │ │ ├── release.keystore │ │ └── src │ │ │ ├── androidTest │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── imagemarkerexample │ │ │ │ └── MainActivityTest.kt │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── assets │ │ │ └── fonts │ │ │ │ ├── MaShanZheng-Regular.ttf │ │ │ │ └── RubikBurned-Regular.ttf │ │ │ ├── java │ │ │ └── com │ │ │ │ └── imagemarkerexample │ │ │ │ ├── MainActivity.kt │ │ │ │ └── MainApplication.kt │ │ │ └── res │ │ │ ├── drawable │ │ │ └── rn_edit_text_material.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── link-assets-manifest.json │ └── settings.gradle ├── app.json ├── assets │ └── fonts │ │ ├── MaShanZheng-Regular.ttf │ │ └── RubikBurned-Regular.ttf ├── babel.config.js ├── index.js ├── ios │ ├── .gitignore │ ├── .xcode.env │ ├── File.swift │ ├── ImageMarkerExample-Bridging-Header.h │ ├── ImageMarkerExample.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ ├── ImageMarkerExample.xcscheme │ │ │ └── ImageMarkerExampleUITests.xcscheme │ ├── ImageMarkerExample.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── ImageMarkerExample │ │ ├── AppDelegate.h │ │ ├── AppDelegate.mm │ │ ├── Images.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Info.plist │ │ ├── LaunchScreen.storyboard │ │ └── main.m │ ├── ImageMarkerExampleUITests │ │ ├── ImageMarkerExampleUITests.swift │ │ └── ImageMarkerExampleUITestsLaunchTests.swift │ ├── Podfile │ ├── link-assets-manifest.json │ └── scripts.sh ├── metro.config.js ├── package.json ├── react-native.config.js └── src │ ├── App.tsx │ ├── bas64bg.js │ ├── bg.png │ ├── icon.jpeg │ └── yahaha.jpeg ├── expo-example ├── .gitignore ├── App.tsx ├── app.json ├── assets │ ├── adaptive-icon.png │ ├── bas64bg.js │ ├── bg.png │ ├── favicon.png │ ├── icon.jpeg │ ├── icon.png │ ├── splash.png │ └── yahaha.jpeg ├── babel.config.js ├── eas.json ├── package.json └── tsconfig.json ├── ios ├── Podfile ├── RCTImageMarker.xcodeproj │ └── project.pbxproj └── RCTImageMarker │ ├── Constants.swift │ ├── CornerRadius.swift │ ├── ImageMarker.swift │ ├── ImageOptions.swift │ ├── MarkImageOptions.swift │ ├── MarkPosition.swift │ ├── MarkTextOptions.swift │ ├── Options.swift │ ├── Padding.swift │ ├── RCTConvert+ImageMarker.swift │ ├── RCTImageMarkerBridge.m │ ├── RNImageSRC.swift │ ├── Radius.swift │ ├── TextBackground.swift │ ├── TextOptions.swift │ ├── TextStyle.swift │ ├── UIColorHex.swift │ ├── UIImageEdit.swift │ ├── Utils.swift │ ├── WatermarkImageOptions.swift │ └── react-native-image-marker-Bridging-Header.h ├── lefthook.yml ├── package.json ├── react-native-image-marker.podspec ├── scripts └── bootstrap.js ├── src ├── __tests__ │ └── index.test.ts ├── expo-plugin │ └── withImageMarker.ts └── index.ts ├── tsconfig.build.json ├── tsconfig.doc.json └── tsconfig.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | indent_style = space 10 | indent_size = 2 11 | 12 | end_of_line = lf 13 | charset = utf-8 14 | trim_trailing_whitespace = true 15 | insert_final_newline = true 16 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | docs 4 | 5 | base64.js 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | # specific for windows script files 3 | *.bat text eol=crlf -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Devlopment environment(please complete the following information):** 27 | - OS: [e.g. OSX/windows] 28 | - nodejs: [e.g. 8.9.0] 29 | - react-native: [e.g. 0.47] 30 | - react-native-image-marker : [e.g. 0.37] 31 | 32 | **Smartphone (please complete the following information):** 33 | - Device: [e.g. iPhone6/Simulator] 34 | - OS: [e.g. iOS8.1] 35 | 36 | **Additional context** 37 | Add any other context about the problem here. 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/actions/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup 2 | description: Setup Node.js and install dependencies 3 | 4 | runs: 5 | using: composite 6 | steps: 7 | - name: Setup Node.js 8 | uses: actions/setup-node@v3 9 | with: 10 | node-version-file: .nvmrc 11 | 12 | - name: Cache dependencies 13 | id: yarn-cache 14 | uses: actions/cache@v3 15 | with: 16 | path: | 17 | **/node_modules 18 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 19 | restore-keys: | 20 | ${{ runner.os }}-yarn- 21 | 22 | - name: Install dependencies 23 | if: steps.yarn-cache.outputs.cache-hit != 'true' 24 | run: | 25 | yarn install --cwd example --frozen-lockfile 26 | yarn install --frozen-lockfile 27 | shell: bash 28 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: 'v$RESOLVED_VERSION 🌈' 2 | tag-template: 'v$RESOLVED_VERSION' 3 | categories: 4 | - title: '🚀 Features' 5 | labels: 6 | - 'feature' 7 | - 'enhancement' 8 | - 'feat' 9 | - title: '🐛 Bug Fixes' 10 | labels: 11 | - 'fix' 12 | - 'bugfix' 13 | - 'bug' 14 | - 'fixed' 15 | - title: '🧰 Maintenance' 16 | labels: 17 | - 'chore' 18 | - 'ci' 19 | - 'refactor' 20 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)' 21 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. 22 | version-resolver: 23 | major: 24 | labels: 25 | - 'major' 26 | minor: 27 | labels: 28 | - 'minor' 29 | patch: 30 | labels: 31 | - 'patch' 32 | default: patch 33 | template: | 34 | ## Changes 35 | 36 | $CHANGES -------------------------------------------------------------------------------- /.github/workflows/build-apk.yml: -------------------------------------------------------------------------------- 1 | name: Build Android APK 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v4 13 | 14 | - name: Setup node 16 15 | uses: actions/setup-node@v3 16 | with: 17 | node-version: '16' 18 | 19 | - name: Set up JDK 1.8 20 | uses: actions/setup-java@v3 21 | with: 22 | distribution: 'temurin' 23 | java-version: '17' 24 | cache: 'gradle' 25 | 26 | - name: Setup Android SDK 27 | uses: android-actions/setup-android@v2 28 | 29 | - name: Install dependencies 30 | run: | 31 | npm install 32 | cd example 33 | npm install 34 | 35 | - name: Build APK 36 | run: | 37 | npm run prepack 38 | cd example/android 39 | ./gradlew assembleRelease 40 | mv app/build/outputs/apk/release/app-release.apk app-release-${{ github.sha }}.apk 41 | 42 | - name: Upload APK 43 | uses: actions/upload-artifact@v3 44 | with: 45 | name: app-release-${{ github.sha }}.apk 46 | path: example/android/app-release-${{ github.sha }}.apk -------------------------------------------------------------------------------- /.github/workflows/jekyll-gh-pages-deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Jekyll with GitHub Pages 2 | 3 | on: 4 | pull_request: 5 | types: [closed] 6 | paths: 7 | - "src/**" 8 | - "assets/**" 9 | - "README.MD" 10 | - "README.0.9.2.MD" 11 | - "README.1.1.x.MD" 12 | - "tsconfig.doc.json" 13 | 14 | # Allows you to run this workflow manually from the Actions tab 15 | workflow_dispatch: 16 | 17 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 18 | permissions: 19 | contents: write 20 | pages: write 21 | id-token: write 22 | pull-requests: write 23 | 24 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 25 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 26 | concurrency: 27 | group: "pages" 28 | cancel-in-progress: false 29 | 30 | jobs: 31 | # Build job 32 | build: 33 | runs-on: ubuntu-latest 34 | if: github.event.pull_request.merged == true 35 | steps: 36 | - name: Checkout 37 | uses: actions/checkout@v3 38 | 39 | - uses: actions/setup-node@v2 40 | with: 41 | node-version: 16 42 | - name: Install dependencies 43 | run: npm install 44 | 45 | - name: Build docs 46 | run: npm run docs 47 | 48 | - name: Setup Pages 49 | uses: actions/configure-pages@v3 50 | - name: Build with Jekyll 51 | uses: actions/jekyll-build-pages@v1 52 | with: 53 | source: ./docs/latest 54 | destination: ./_site 55 | - name: Upload artifact 56 | uses: actions/upload-pages-artifact@v1 57 | - name: Build with Jekyll v1.0.1 58 | uses: actions/jekyll-build-pages@v1 59 | with: 60 | source: ./docs/v1.0.x 61 | destination: ./_site/v1.0.x 62 | - name: Upload artifact 63 | uses: actions/upload-pages-artifact@v1 64 | 65 | # Deployment job 66 | deploy: 67 | environment: 68 | name: github-pages 69 | url: ${{ steps.deployment.outputs.page_url }} 70 | runs-on: ubuntu-latest 71 | needs: build 72 | steps: 73 | - name: Deploy to GitHub Pages 74 | id: deployment 75 | uses: actions/deploy-pages@v2 76 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | name: publish npm Package 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | publish-npm: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - uses: actions/setup-node@v2 13 | with: 14 | node-version: 14 15 | registry-url: https://registry.npmjs.org/ 16 | - run: npm install 17 | - run: npm run prepack 18 | - run: npm publish 19 | env: 20 | NODE_AUTH_TOKEN: ${{secrets.NPM_AUTH_TOKEN}} 21 | 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # XDE 6 | .expo/ 7 | 8 | # VSCode 9 | .vscode/ 10 | jsconfig.json 11 | 12 | # Xcode 13 | # 14 | build/ 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata 24 | *.xccheckout 25 | *.moved-aside 26 | DerivedData 27 | *.hmap 28 | *.ipa 29 | *.xcuserstate 30 | project.xcworkspace 31 | .xcode.env.local 32 | 33 | # Android/IJ 34 | # 35 | .classpath 36 | .cxx 37 | .gradle 38 | .idea 39 | .project 40 | .settings 41 | local.properties 42 | android.iml 43 | 44 | # Cocoapods 45 | # 46 | example/ios/Pods 47 | 48 | # Ruby 49 | example/vendor/ 50 | 51 | # node.js 52 | # 53 | node_modules/ 54 | npm-debug.log 55 | yarn-debug.log 56 | yarn-error.log 57 | package-lock.json 58 | yarn.lock 59 | 60 | # BUCK 61 | buck-out/ 62 | \.buckd/ 63 | android/app/libs 64 | android/keystores/debug.keystore 65 | 66 | # Expo 67 | .expo/ 68 | 69 | # Turborepo 70 | .turbo/ 71 | 72 | # generated by bob 73 | lib/ 74 | 75 | docs/**/*.html 76 | 77 | # testing 78 | /coverage 79 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "MD003": { "style": "atx" }, 4 | "MD007": { "indent": 2 }, 5 | "no-hard-tabs": false, 6 | "no-inline-html": false, 7 | "first-line-h1": false, 8 | "no-duplicate-heading": { 9 | "siblings_only": true 10 | }, 11 | "no-alt-text": false, 12 | "line-length": false 13 | } 14 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | asset/* 2 | example/* 3 | build/* 4 | *.iml 5 | 6 | # OSX 7 | # 8 | .DS_Store 9 | 10 | # Xcode 11 | # 12 | build/ 13 | *.pbxuser 14 | !default.pbxuser 15 | *.mode1v3 16 | !default.mode1v3 17 | *.mode2v3 18 | !default.mode2v3 19 | *.perspectivev3 20 | !default.perspectivev3 21 | xcuserdata 22 | *.xccheckout 23 | *.moved-aside 24 | DerivedData 25 | *.hmap 26 | *.ipa 27 | *.xcuserstate 28 | *.iml 29 | 30 | # Android/IJ 31 | # 32 | .idea 33 | .gradle 34 | local.properties 35 | .vscode 36 | android/build 37 | 38 | # node.js 39 | # 40 | npm-debug.log 41 | node_modules 42 | package-lock.json 43 | .huskyrc 44 | yarn.lock 45 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16.18.1 2 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /.yarnrc: -------------------------------------------------------------------------------- 1 | # Override Yarn command so we can automatically setup the repo on running `yarn` 2 | 3 | yarn-path "scripts/bootstrap.js" 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, caste, color, religion, or sexual 11 | identity and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the overall 27 | community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or advances of 32 | any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email address, 36 | without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | [INSERT CONTACT METHOD]. 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series of 87 | actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or permanent 94 | ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within the 114 | community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.1, available at 120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 127 | [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are always welcome, no matter how large or small! 4 | 5 | We want this community to be friendly and respectful to each other. Please follow it in all your interactions with the project. Before contributing, please read the [code of conduct](./CODE_OF_CONDUCT.md). 6 | 7 | ## Development workflow 8 | 9 | To get started with the project, run `yarn` in the root directory to install the required dependencies for each package: 10 | 11 | ```sh 12 | yarn 13 | ``` 14 | 15 | > While it's possible to use [`npm`](https://github.com/npm/cli), the tooling is built around [`yarn`](https://classic.yarnpkg.com/), so you'll have an easier time if you use `yarn` for development. 16 | 17 | While developing, you can run the [example app](/example/) to test your changes. Any changes you make in your library's JavaScript code will be reflected in the example app without a rebuild. If you change any native code, then you'll need to rebuild the example app. 18 | 19 | To start the packager: 20 | 21 | ```sh 22 | yarn example start 23 | ``` 24 | 25 | To run the example app on Android: 26 | 27 | ```sh 28 | yarn example android 29 | ``` 30 | 31 | To run the example app on iOS: 32 | 33 | ```sh 34 | yarn example ios 35 | ``` 36 | 37 | Make sure your code passes TypeScript and ESLint. Run the following to verify: 38 | 39 | ```sh 40 | yarn typecheck 41 | yarn lint 42 | ``` 43 | 44 | To fix formatting errors, run the following: 45 | 46 | ```sh 47 | yarn lint --fix 48 | ``` 49 | 50 | Remember to add tests for your change if possible. Run the unit tests by: 51 | 52 | ```sh 53 | yarn test 54 | ``` 55 | 56 | To edit the Objective-C or Swift files, open `example/ios/ImageMarkerExample.xcworkspace` in XCode and find the source files at `Pods > Development Pods > react-native-image-marker`. 57 | 58 | To edit the Java or Kotlin files, open `example/android` in Android studio and find the source files at `react-native-image-marker` under `Android`. 59 | 60 | 61 | ### Commit message convention 62 | 63 | We follow the [conventional commits specification](https://www.conventionalcommits.org/en) for our commit messages: 64 | 65 | - `fix`: bug fixes, e.g. fix crash due to deprecated method. 66 | - `feat`: new features, e.g. add new method to the module. 67 | - `refactor`: code refactor, e.g. migrate from class components to hooks. 68 | - `docs`: changes into documentation, e.g. add usage example for the module.. 69 | - `test`: adding or updating tests, e.g. add integration tests using detox. 70 | - `chore`: tooling changes, e.g. change CI config. 71 | 72 | Our pre-commit hooks verify that your commit message matches this format when committing. 73 | 74 | ### Linting and tests 75 | 76 | [ESLint](https://eslint.org/), [Prettier](https://prettier.io/), [TypeScript](https://www.typescriptlang.org/) 77 | 78 | We use [TypeScript](https://www.typescriptlang.org/) for type checking, [ESLint](https://eslint.org/) with [Prettier](https://prettier.io/) for linting and formatting the code, and [Jest](https://jestjs.io/) for testing. 79 | 80 | Our pre-commit hooks verify that the linter and tests pass when committing. 81 | 82 | ### Publishing to npm 83 | 84 | We use [release-it](https://github.com/release-it/release-it) to make it easier to publish new versions. It handles common tasks like bumping version based on semver, creating tags and releases etc. 85 | 86 | To publish new versions, run the following: 87 | 88 | ```sh 89 | yarn release 90 | ``` 91 | 92 | ### Scripts 93 | 94 | The `package.json` file contains various scripts for common tasks: 95 | 96 | - `yarn bootstrap`: setup project by installing all dependencies and pods. 97 | - `yarn typecheck`: type-check files with TypeScript. 98 | - `yarn lint`: lint files with ESLint. 99 | - `yarn test`: run unit tests with Jest. 100 | - `yarn example start`: start the Metro server for the example app. 101 | - `yarn example android`: run the example app on Android. 102 | - `yarn example ios`: run the example app on iOS. 103 | 104 | ### Sending a pull request 105 | 106 | > **Working on your first pull request?** You can learn how from this _free_ series: [How to Contribute to an Open Source Project on GitHub](https://app.egghead.io/playlists/how-to-contribute-to-an-open-source-project-on-github). 107 | 108 | When you're sending a pull request: 109 | 110 | - Prefer small pull requests focused on one change. 111 | - Verify that linters and tests are passing. 112 | - Review the documentation to make sure it looks good. 113 | - Follow the pull request template when opening a pull request. 114 | - For pull requests that change the API or implementation, discuss with maintainers first by opening an issue. 115 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 jimmydaddy 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman 2 | title: react native image marker 3 | description: Add text or icon watermark to your images 4 | baseurl: /react-native-image-marker -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["ImageMarker_kotlinVersion"] 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | if (project == rootProject) { 9 | repositories { 10 | google() 11 | mavenCentral() 12 | } 13 | } else { 14 | dependencies { 15 | classpath 'com.android.tools.build:gradle:7.2.1' 16 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 17 | } 18 | } 19 | } 20 | 21 | def isNewArchitectureEnabled() { 22 | return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true" 23 | } 24 | 25 | apply plugin: "com.android.library" 26 | apply plugin: "kotlin-android" 27 | 28 | def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') } 29 | 30 | if (isNewArchitectureEnabled()) { 31 | apply plugin: "com.facebook.react" 32 | } 33 | 34 | def getExtOrDefault(name) { 35 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["ImageMarker_" + name] 36 | } 37 | 38 | def getExtOrIntegerDefault(name) { 39 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["ImageMarker_" + name]).toInteger() 40 | } 41 | 42 | android { 43 | compileSdkVersion getExtOrIntegerDefault("compileSdkVersion") 44 | 45 | defaultConfig { 46 | minSdkVersion getExtOrIntegerDefault("minSdkVersion") 47 | targetSdkVersion getExtOrIntegerDefault("targetSdkVersion") 48 | buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() 49 | } 50 | buildTypes { 51 | release { 52 | minifyEnabled false 53 | } 54 | } 55 | 56 | lintOptions { 57 | disable "GradleCompatible" 58 | } 59 | 60 | compileOptions { 61 | sourceCompatibility JavaVersion.VERSION_1_8 62 | targetCompatibility JavaVersion.VERSION_1_8 63 | } 64 | 65 | } 66 | 67 | repositories { 68 | mavenCentral() 69 | google() 70 | jcenter() 71 | } 72 | 73 | def kotlin_version = getExtOrDefault("kotlinVersion") 74 | 75 | dependencies { 76 | // For < 0.71, this will be from the local maven repo 77 | // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin 78 | //noinspection GradleDynamicVersion 79 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.0" 80 | implementation "com.facebook.react:react-native:+" 81 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 82 | testImplementation 'junit:junit:4.13.2' 83 | androidTestImplementation 'androidx.test.ext:junit:1.1.5' 84 | testImplementation "org.mockito:mockito-core:3.+" 85 | implementation "io.coil-kt:coil:2.5.0" 86 | implementation "io.coil-kt:coil-svg:2.5.0" 87 | implementation "io.coil-kt:coil-gif:2.5.0" 88 | } 89 | 90 | if (isNewArchitectureEnabled()) { 91 | react { 92 | jsRootDir = file("../src/") 93 | libraryName = "ImageMarker" 94 | codegenJavaPackageName = "com.jimmydaddy.imagemarker" 95 | } 96 | } 97 | 98 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | ImageMarker_kotlinVersion=1.8.0 2 | ImageMarker_minSdkVersion=24 3 | ImageMarker_targetSdkVersion=31 4 | ImageMarker_compileSdkVersion=31 5 | ImageMarker_ndkversion=21.4.7075529 6 | newArchEnabled=true 7 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/java/com/jimmydaddy/imagemarker/ImageMarkerPackage.kt: -------------------------------------------------------------------------------- 1 | package com.jimmydaddy.imagemarker 2 | 3 | import com.facebook.react.ReactPackage 4 | import com.facebook.react.bridge.NativeModule 5 | import com.facebook.react.bridge.ReactApplicationContext 6 | import com.facebook.react.uimanager.ViewManager 7 | 8 | class ImageMarkerPackage : ReactPackage { 9 | override fun createNativeModules(reactContext: ReactApplicationContext): List { 10 | val modules: MutableList = ArrayList() 11 | modules.add(ImageMarkerManager(reactContext)) 12 | return modules 13 | } 14 | 15 | override fun createViewManagers(reactContext: ReactApplicationContext): List> { 16 | return emptyList() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /android/src/main/java/com/jimmydaddy/imagemarker/ImageProcess.kt: -------------------------------------------------------------------------------- 1 | package com.jimmydaddy.imagemarker 2 | 3 | import android.graphics.Bitmap 4 | import android.graphics.Matrix 5 | import android.util.Log 6 | import com.jimmydaddy.imagemarker.base.Constants.IMAGE_MARKER_TAG 7 | 8 | class ImageProcess { 9 | companion object { 10 | fun rotate(bitmap: Bitmap, rotation: Number): Bitmap { 11 | val rotationMatrix = Matrix().apply { postRotate(rotation.toFloat()) } 12 | return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, rotationMatrix, true) 13 | } 14 | 15 | fun scaleBitmap(bitmap: Bitmap, scale: Float): Bitmap? { 16 | val w = bitmap.width 17 | val h = bitmap.height 18 | val mtx = Matrix() 19 | if (scale != 1f && scale >= 0) { 20 | mtx.postScale(scale, scale) 21 | } 22 | var scaledBitmap: Bitmap? = null 23 | try { 24 | scaledBitmap = Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true) 25 | } catch (e: OutOfMemoryError) { 26 | print(e.message) 27 | while (scaledBitmap == null) { 28 | System.gc() 29 | System.runFinalization() 30 | scaledBitmap = Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true) 31 | } 32 | } 33 | Log.d(IMAGE_MARKER_TAG, "original width: " + w + " original height: " + h + " scaled width: " + scaledBitmap?.width + " scaled height: " + scaledBitmap?.height) 34 | return scaledBitmap 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /android/src/main/java/com/jimmydaddy/imagemarker/MarkerImageLoader.kt: -------------------------------------------------------------------------------- 1 | package com.jimmydaddy.imagemarker 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.res.Resources 5 | import android.graphics.Bitmap 6 | import android.graphics.BitmapFactory 7 | import android.os.Build 8 | import android.os.Build.VERSION.SDK_INT 9 | import android.util.Log 10 | import androidx.annotation.RequiresApi 11 | import androidx.core.graphics.drawable.toBitmap 12 | import coil.ImageLoader 13 | import coil.decode.GifDecoder 14 | import coil.decode.ImageDecoderDecoder 15 | import coil.decode.SvgDecoder 16 | import coil.request.ImageRequest 17 | import coil.size.Size 18 | import com.facebook.react.bridge.ReactApplicationContext 19 | import com.jimmydaddy.imagemarker.base.Constants.IMAGE_MARKER_TAG 20 | import com.jimmydaddy.imagemarker.base.ErrorCode 21 | import com.jimmydaddy.imagemarker.base.ImageOptions 22 | import com.jimmydaddy.imagemarker.base.MarkerError 23 | import kotlinx.coroutines.Dispatchers 24 | import kotlinx.coroutines.async 25 | import kotlinx.coroutines.awaitAll 26 | import kotlinx.coroutines.withContext 27 | import java.util.concurrent.CompletableFuture 28 | 29 | class MarkerImageLoader(private val context: ReactApplicationContext, private val maxSize: Int) { 30 | 31 | private var imageLoader: ImageLoader = ImageLoader.Builder(context) 32 | .components { 33 | if (SDK_INT >= 28) { 34 | add(ImageDecoderDecoder.Factory()) 35 | } else { 36 | add(GifDecoder.Factory()) 37 | } 38 | add(SvgDecoder.Factory()) 39 | } 40 | .allowHardware(false) 41 | .build() 42 | private val resources: Resources 43 | get() = context.resources 44 | 45 | @RequiresApi(Build.VERSION_CODES.N) 46 | suspend fun loadImages(images: List): List = withContext(Dispatchers.IO) { 47 | 48 | val deferredList = images.map { img -> 49 | async { 50 | try { 51 | val isCoilImg = isCoilImg(img.uri) 52 | Log.d(IMAGE_MARKER_TAG, "isCoilImg: $isCoilImg") 53 | if (isCoilImg) { 54 | val future = CompletableFuture() 55 | var request = ImageRequest.Builder(context) 56 | .data(img.uri) 57 | if (img.src != null && img.src.width > 0 && img.src.height > 0) { 58 | request = request.size(img.src.width, img.src.height) 59 | Log.d(IMAGE_MARKER_TAG, "src.width: " + img.src.width + " src.height: " + img.src.height) 60 | } else { 61 | request = request.size(Size.ORIGINAL) 62 | } 63 | imageLoader.enqueue(request.target ( 64 | onStart = { _ -> 65 | // Handle the placeholder drawable. 66 | Log.d(IMAGE_MARKER_TAG, "start to load image: " + img.uri) 67 | }, 68 | onSuccess = { result -> 69 | val bitmap = result.toBitmap() 70 | val bg = ImageProcess.scaleBitmap(bitmap, img.scale) 71 | if (bg == null) { 72 | future.completeExceptionally(MarkerError(ErrorCode.LOAD_IMAGE_FAILED, 73 | "Can't retrieve the file from the src: " + img.uri)) 74 | } 75 | future.complete(bg) 76 | }, 77 | onError = { _ -> 78 | future.completeExceptionally(MarkerError(ErrorCode.LOAD_IMAGE_FAILED, 79 | "Can't retrieve the file from the src: " + img.uri)) 80 | } 81 | ).build()) 82 | return@async future.get() 83 | } else { 84 | val resId = getDrawableResourceByName(img.uri) 85 | Log.d(IMAGE_MARKER_TAG, "resId: $resId") 86 | if (resId == 0) { 87 | Log.d(IMAGE_MARKER_TAG, "cannot find res") 88 | throw MarkerError(ErrorCode.GET_RESOURCE_FAILED, "Can't get resource by the path: ${img.uri}") 89 | } else { 90 | val r = resources 91 | Log.d(IMAGE_MARKER_TAG, "src.width: " + img.src.width + " src.height: " + img.src.height) 92 | val originalBitMap = BitmapFactory.decodeResource(r, resId) 93 | var bitmap = originalBitMap 94 | if (img.src != null && img.src.width > 0 && img.src.height > 0) { 95 | bitmap = Bitmap.createScaledBitmap(originalBitMap, img.src.width, img.src.height, true); 96 | } 97 | Log.d(IMAGE_MARKER_TAG, bitmap!!.height.toString() + "") 98 | val bg = ImageProcess.scaleBitmap(bitmap, img.scale) 99 | Log.d(IMAGE_MARKER_TAG, bg!!.height.toString() + "") 100 | if (!bitmap.isRecycled && img.scale != 1f) { 101 | bitmap.recycle() 102 | System.gc() 103 | } 104 | return@async bg 105 | } 106 | } 107 | } catch (e: Exception) { 108 | Log.e("ImageLoader", "Failed to load image: ${img.uri}", e) 109 | null 110 | } 111 | } 112 | } 113 | deferredList.awaitAll() 114 | } 115 | 116 | private fun isCoilImg(uri: String?): Boolean { 117 | // val base64Pattern = 118 | // "^data:(image|img)/(bmp|jpg|png|tif|gif|pcx|tga|exif|fpx|svg|psd|cdr|pcd|dxf|ufo|eps|ai|raw|WMF|webp);base64,(([[A-Za-z0-9+/])*\\s\\S*)*" 119 | return uri!!.startsWith("http://") || uri.startsWith("https://") || uri.startsWith("file://") || uri.startsWith( 120 | "data:" 121 | ) && uri.contains("base64") && (uri.contains("img") || uri.contains("image")) 122 | } 123 | 124 | @SuppressLint("DiscouragedApi") 125 | private fun getDrawableResourceByName(name: String?): Int { 126 | return resources.getIdentifier( 127 | name, 128 | "drawable", 129 | context.packageName 130 | ) 131 | } 132 | 133 | } 134 | -------------------------------------------------------------------------------- /android/src/main/java/com/jimmydaddy/imagemarker/base/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.jimmydaddy.imagemarker.base 2 | 3 | object Constants { 4 | const val DEFAULT_FONT_SIZE = 14f 5 | const val DEFAULT_MAX_SIZE = 2048 6 | const val DEFAULT_QUALITY = 100 7 | 8 | const val DEFAULT_SCALE = 1.0f 9 | 10 | const val DEFAULT_ROTATE = 0f 11 | 12 | const val DEFAULT_ALPHA = 255 13 | 14 | const val IMAGE_MARKER_TAG = "[ImageMarker]" 15 | 16 | const val BASE64 = "base64" 17 | 18 | const val DEFAULT_MARGIN = 20f 19 | } 20 | -------------------------------------------------------------------------------- /android/src/main/java/com/jimmydaddy/imagemarker/base/CornerRadius.kt: -------------------------------------------------------------------------------- 1 | package com.jimmydaddy.imagemarker.base 2 | 3 | import com.facebook.react.bridge.ReadableMap 4 | 5 | import android.graphics.RectF 6 | 7 | data class CornerRadius(val opts: ReadableMap?) { 8 | private var topLeft: Radius? = null 9 | private var topRight: Radius? = null 10 | private var bottomLeft: Radius? = null 11 | private var bottomRight: Radius? = null 12 | private var all: Radius? = null 13 | 14 | init { 15 | 16 | val iterator = opts?.entryIterator 17 | 18 | if (iterator != null) { 19 | while (iterator.hasNext()) { 20 | val entry = iterator.next() 21 | val cornerRadius = entry.value 22 | 23 | when (entry.key) { 24 | "topLeft" -> { 25 | if (cornerRadius == null) break 26 | topLeft = Radius(cornerRadius as ReadableMap?) 27 | } 28 | 29 | "topRight" -> { 30 | if (cornerRadius == null) break 31 | topRight = Radius(cornerRadius as ReadableMap?) 32 | } 33 | 34 | "bottomLeft" -> { 35 | if (cornerRadius == null) break 36 | bottomLeft = Radius(cornerRadius as ReadableMap?) 37 | } 38 | 39 | "bottomRight" -> { 40 | if (cornerRadius == null) break 41 | bottomRight = Radius(cornerRadius as ReadableMap?) 42 | } 43 | 44 | else -> { 45 | if (cornerRadius == null) break 46 | all = Radius(cornerRadius as ReadableMap?) 47 | } 48 | } 49 | } 50 | } 51 | } 52 | 53 | fun radii(rect: RectF): FloatArray { 54 | var mxRadius = 0f 55 | var myRadius = 0f 56 | 57 | if (all != null) { 58 | mxRadius = Utils.parseSpreadValue(all!!.x, rect.width()) 59 | myRadius = Utils.parseSpreadValue(all!!.y, rect.height()) 60 | } 61 | 62 | val radii = floatArrayOf( 63 | mxRadius, // topLeftX 64 | myRadius, // topLeftY 65 | mxRadius, // topRightX 66 | myRadius, // topRightY 67 | mxRadius, // bottomRightX 68 | myRadius, // bottomRightY 69 | mxRadius, // bottomLeftX 70 | myRadius // bottomLeftY 71 | ) 72 | 73 | if (topLeft != null) { 74 | radii[0] = Utils.parseSpreadValue(topLeft!!.x, rect.width()) 75 | radii[1] = Utils.parseSpreadValue(topLeft!!.y, rect.height()) 76 | } 77 | 78 | if (topRight != null) { 79 | radii[2] = Utils.parseSpreadValue(topRight!!.x, rect.width()) 80 | radii[3] = Utils.parseSpreadValue(topRight!!.y, rect.height()) 81 | } 82 | 83 | if (bottomRight != null) { 84 | radii[4] = Utils.parseSpreadValue(bottomRight!!.x, rect.width()) 85 | radii[5] = Utils.parseSpreadValue(bottomRight!!.y, rect.height()) 86 | } 87 | 88 | if (bottomLeft != null) { 89 | radii[6] = Utils.parseSpreadValue(bottomLeft!!.x, rect.width()) 90 | radii[7] = Utils.parseSpreadValue(bottomLeft!!.y, rect.height()) 91 | } 92 | 93 | return radii 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /android/src/main/java/com/jimmydaddy/imagemarker/base/ErrorCode.kt: -------------------------------------------------------------------------------- 1 | package com.jimmydaddy.imagemarker.base 2 | 3 | enum class ErrorCode(val value: String) { 4 | INVALID_PARAMS("INVALID_PARAMS"), 5 | LOAD_IMAGE_FAILED("LOAD_IMAGE_FAILED"), 6 | GET_RESOURCE_FAILED("GET_RESOURCE_FAILED"), 7 | PARAMS_REQUIRED("PARAMS_REQUIRED"); 8 | } 9 | -------------------------------------------------------------------------------- /android/src/main/java/com/jimmydaddy/imagemarker/base/ImageOptions.kt: -------------------------------------------------------------------------------- 1 | package com.jimmydaddy.imagemarker.base 2 | 3 | import android.graphics.Color 4 | import android.graphics.Paint 5 | import android.graphics.PorterDuff 6 | import android.graphics.PorterDuffColorFilter 7 | import com.facebook.react.bridge.ReadableMap 8 | import com.jimmydaddy.imagemarker.base.Constants.DEFAULT_ALPHA 9 | import com.jimmydaddy.imagemarker.base.Constants.DEFAULT_ROTATE 10 | import com.jimmydaddy.imagemarker.base.Constants.DEFAULT_SCALE 11 | 12 | class ImageOptions(val options: ReadableMap) { 13 | var src: RNImageSRC 14 | 15 | var uri: String? 16 | 17 | var scale: Float 18 | 19 | var rotate: Float 20 | private var alpha: Int 21 | 22 | init { 23 | if (!options.hasKey("src")) { 24 | throw MarkerError(ErrorCode.PARAMS_REQUIRED, "image is required") 25 | } 26 | var originalSRC = options.getMap("src") 27 | src = RNImageSRC(originalSRC) 28 | uri = originalSRC!!.getString(PROP_ICON_URI) 29 | scale = if (options.hasKey("scale")) options.getDouble("scale").toFloat() else DEFAULT_SCALE 30 | rotate = if (options.hasKey("rotate")) options.getInt("rotate").toFloat() else DEFAULT_ROTATE 31 | alpha = if (options.hasKey("alpha")) (options.getDouble("alpha") * 255).toInt() else DEFAULT_ALPHA 32 | } 33 | 34 | fun applyStyle(): Paint { 35 | val paint = Paint() 36 | paint.alpha = alpha 37 | //获取更清晰的图像采样 38 | paint.isDither = true 39 | paint.colorFilter = PorterDuffColorFilter(Color.TRANSPARENT, PorterDuff.Mode.OVERLAY) 40 | return paint 41 | } 42 | 43 | companion object { 44 | const val PROP_ICON_URI = "uri" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /android/src/main/java/com/jimmydaddy/imagemarker/base/MarkImageOptions.kt: -------------------------------------------------------------------------------- 1 | package com.jimmydaddy.imagemarker.base 2 | 3 | import com.facebook.react.bridge.Promise 4 | import com.facebook.react.bridge.ReadableMap 5 | 6 | class MarkImageOptions(options: ReadableMap) : Options(options) { 7 | var watermarkImages: Array 8 | init { 9 | val markerImageOpts = options.getMap("watermarkImage") 10 | val markerImagesOpts = options.getArray("watermarkImages") 11 | if ((markerImagesOpts == null || markerImagesOpts.size() <= 0) && markerImageOpts == null) { 12 | throw MarkerError( 13 | ErrorCode.PARAMS_REQUIRED, 14 | "marker image is required" 15 | ) 16 | } 17 | val myMarkerList = arrayListOf() 18 | if (markerImagesOpts != null && markerImagesOpts.size() > 0) { 19 | for (i in 0 until markerImagesOpts.size()) { 20 | val marker = WatermarkImageOptions(markerImagesOpts.getMap(i)) 21 | myMarkerList.add(marker) 22 | } 23 | } 24 | if (markerImageOpts != null) { 25 | val marker = ImageOptions(markerImageOpts) 26 | val positionOptions = 27 | if (null != options.getMap("watermarkPositions")) options.getMap("watermarkPositions") else null 28 | val x = if (positionOptions!!.hasKey("X")) Utils.handleDynamicToString(positionOptions.getDynamic("X")) else null 29 | val y = if (positionOptions.hasKey("Y")) Utils.handleDynamicToString(positionOptions.getDynamic("Y")) else null 30 | val positionEnum = 31 | if (null != positionOptions.getString("position")) PositionEnum.getPosition( 32 | positionOptions.getString("position") 33 | ) else null 34 | val markerOpts = WatermarkImageOptions(marker, x, y, positionEnum) 35 | myMarkerList.add(markerOpts) 36 | } 37 | watermarkImages = myMarkerList.toTypedArray() 38 | } 39 | 40 | companion object { 41 | @JvmStatic 42 | fun checkParams(opts: ReadableMap, promise: Promise): MarkImageOptions? { 43 | try { 44 | return MarkImageOptions(opts) 45 | } catch (e: MarkerError) { 46 | promise.reject(e.getErrorCode(), e.getErrMsg()) 47 | } 48 | return null 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /android/src/main/java/com/jimmydaddy/imagemarker/base/MarkTextOptions.kt: -------------------------------------------------------------------------------- 1 | package com.jimmydaddy.imagemarker.base 2 | 3 | import com.facebook.react.bridge.Promise 4 | import com.facebook.react.bridge.ReadableMap 5 | 6 | class MarkTextOptions(options: ReadableMap) : Options(options) { 7 | lateinit var watermarkTexts: Array 8 | 9 | init { 10 | val waterMarkTextsMap = options.getArray("watermarkTexts") 11 | if (waterMarkTextsMap!!.size() > 0) { 12 | watermarkTexts = arrayOfNulls(waterMarkTextsMap.size()) 13 | for (i in 0 until waterMarkTextsMap.size()) { 14 | val textMap = waterMarkTextsMap.getMap(i) 15 | watermarkTexts[i] = TextOptions(textMap) 16 | } 17 | } 18 | } 19 | 20 | companion object { 21 | @JvmStatic 22 | fun checkParams(opts: ReadableMap, promise: Promise): MarkTextOptions? { 23 | try { 24 | return MarkTextOptions(opts) 25 | } catch (e: MarkerError) { 26 | promise.reject(e.getErrorCode(), e.getErrMsg()) 27 | } 28 | return null 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /android/src/main/java/com/jimmydaddy/imagemarker/base/MarkerError.kt: -------------------------------------------------------------------------------- 1 | package com.jimmydaddy.imagemarker.base 2 | 3 | class MarkerError internal constructor(private var errorCode: ErrorCode, private var errMsg: String) : Error() { 4 | 5 | fun getErrorCode(): String { 6 | return errorCode.value 7 | } 8 | 9 | @JvmName("functionOfKotlin") 10 | fun getErrMsg(): String { 11 | return errMsg 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/src/main/java/com/jimmydaddy/imagemarker/base/MarkerInsets.kt: -------------------------------------------------------------------------------- 1 | package com.jimmydaddy.imagemarker.base 2 | 3 | data class MarkerInsets(val top: Int, val left: Int, val bottom: Int, val right: Int) 4 | -------------------------------------------------------------------------------- /android/src/main/java/com/jimmydaddy/imagemarker/base/Options.kt: -------------------------------------------------------------------------------- 1 | package com.jimmydaddy.imagemarker.base 2 | 3 | import com.facebook.react.bridge.Promise 4 | import com.facebook.react.bridge.ReadableMap 5 | 6 | open class Options(val options: ReadableMap) { 7 | var backgroundImage: ImageOptions 8 | 9 | private var backgroundImageOpts = options.getMap("backgroundImage") 10 | 11 | var quality: Int 12 | 13 | var filename: String? 14 | 15 | var saveFormat: SaveFormat 16 | 17 | var maxSize: Int 18 | 19 | init { 20 | this.backgroundImageOpts ?: throw MarkerError( 21 | ErrorCode.PARAMS_REQUIRED, 22 | "backgroundImage is required" 23 | ) 24 | backgroundImage = ImageOptions(this.backgroundImageOpts!!) 25 | quality = if (options.hasKey("quality")) options.getInt("quality") else 100 26 | maxSize = if (options.hasKey("maxSize")) options.getInt("maxSize") else 2048 27 | filename = options.getString("filename") 28 | saveFormat = SaveFormat.getFormat(options.getString("saveFormat")) 29 | } 30 | 31 | companion object { 32 | const val PROP_ICON_URI = "uri" 33 | fun checkParams(opts: ReadableMap, promise: Promise): Options? { 34 | try { 35 | return Options(opts) 36 | } catch (e: MarkerError) { 37 | promise.reject(e.getErrorCode(), e.getErrMsg()) 38 | } 39 | return null 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /android/src/main/java/com/jimmydaddy/imagemarker/base/Padding.kt: -------------------------------------------------------------------------------- 1 | package com.jimmydaddy.imagemarker.base 2 | 3 | import com.facebook.react.bridge.ReadableMap 4 | 5 | open class Padding(paddingData: ReadableMap?) { 6 | private var paddingTop: String = "0" 7 | private var paddingLeft: String = "0" 8 | private var paddingBottom: String = "0" 9 | private var paddingRight: String = "0" 10 | 11 | init { 12 | var topValue = "0" 13 | var leftValue = "0" 14 | var bottomValue = "0" 15 | var rightValue = "0" 16 | 17 | val iterator = paddingData?.entryIterator 18 | 19 | if (iterator != null) { 20 | while (iterator.hasNext()) { 21 | val entry = iterator.next() 22 | var paddingValue = entry.value 23 | when (entry.key) { 24 | "padding" -> { 25 | if (paddingValue is String) { 26 | paddingValue = paddingValue.trim() 27 | if (!Utils.checkSpreadValue(paddingValue, maxLength = 4)) { 28 | throw Exception("padding is invalid") 29 | } 30 | val values = paddingValue.split(" ") 31 | when (values.size) { 32 | 1 -> { 33 | topValue = values[0] 34 | leftValue = values[0] 35 | bottomValue = values[0] 36 | rightValue = values[0] 37 | } 38 | 39 | 2 -> { 40 | topValue = values[0] 41 | leftValue = values[1] 42 | bottomValue = values[0] 43 | rightValue = values[1] 44 | } 45 | 46 | 3 -> { 47 | topValue = values[0] 48 | leftValue = values[1] 49 | bottomValue = values[2] 50 | rightValue = values[1] 51 | } 52 | 53 | 4 -> { 54 | topValue = values[0] 55 | leftValue = values[1] 56 | bottomValue = values[2] 57 | rightValue = values[3] 58 | } 59 | } 60 | } else if (paddingValue is Number) { 61 | topValue = paddingValue.toString() 62 | leftValue = paddingValue.toString() 63 | bottomValue = paddingValue.toString() 64 | rightValue = paddingValue.toString() 65 | } 66 | } 67 | 68 | "paddingLeft" -> { 69 | if (paddingValue is String) { 70 | if (!Utils.checkSpreadValue(paddingValue, maxLength = 1)) { 71 | throw Exception("padding is invalid") 72 | } 73 | leftValue = paddingValue 74 | } else if (paddingValue is Number) { 75 | leftValue = paddingValue.toString() 76 | } 77 | } 78 | 79 | "paddingRight" -> { 80 | if (paddingValue is String) { 81 | if (!Utils.checkSpreadValue(paddingValue, maxLength = 1)) { 82 | throw Exception("padding is invalid") 83 | } 84 | rightValue = paddingValue 85 | } else if (paddingValue is Number) { 86 | rightValue = paddingValue.toString() 87 | } 88 | } 89 | 90 | "paddingTop" -> { 91 | if (paddingValue is String) { 92 | if (!Utils.checkSpreadValue(paddingValue, maxLength = 1)) { 93 | throw Exception("padding is invalid") 94 | } 95 | topValue = paddingValue 96 | } else if (paddingValue is Number) { 97 | topValue = paddingValue.toString() 98 | } 99 | } 100 | 101 | "paddingBottom" -> { 102 | if (paddingValue is String) { 103 | if (!Utils.checkSpreadValue(paddingValue, maxLength = 1)) { 104 | throw Exception("padding is invalid") 105 | } 106 | bottomValue = paddingValue 107 | } else if (paddingValue is Number) { 108 | bottomValue = paddingValue.toString() 109 | } 110 | } 111 | 112 | "paddingHorizontal", "paddingX" -> { 113 | if (paddingValue is String) { 114 | if (!Utils.checkSpreadValue(paddingValue, maxLength = 1)) { 115 | throw Exception("padding is invalid") 116 | } 117 | rightValue = paddingValue 118 | leftValue = paddingValue 119 | } else if (paddingValue is Number) { 120 | leftValue = paddingValue.toString() 121 | rightValue = paddingValue.toString() 122 | } 123 | } 124 | 125 | "paddingVertical", "paddingY" -> { 126 | if (paddingValue is String) { 127 | if (!Utils.checkSpreadValue(paddingValue, maxLength = 1)) { 128 | throw Exception("padding is invalid") 129 | } 130 | topValue = paddingValue 131 | bottomValue = paddingValue 132 | } else if (paddingValue is Number) { 133 | topValue = paddingValue.toString() 134 | bottomValue = paddingValue.toString() 135 | } 136 | } 137 | 138 | else -> {} 139 | } 140 | } 141 | } 142 | 143 | paddingTop = topValue 144 | paddingLeft = leftValue 145 | paddingBottom = bottomValue 146 | paddingRight = rightValue 147 | } 148 | 149 | fun toEdgeInsets(width: Int, height: Int): MarkerInsets { 150 | val topValue = Utils.parseSpreadValue(paddingTop, relativeTo = height.toFloat()) 151 | val leftValue = Utils.parseSpreadValue(paddingLeft, relativeTo = width.toFloat()) 152 | val bottomValue = Utils.parseSpreadValue(paddingBottom, relativeTo = height.toFloat()) 153 | val rightValue = Utils.parseSpreadValue(paddingRight, relativeTo = width.toFloat()) 154 | return MarkerInsets(topValue.toInt(), leftValue.toInt(), bottomValue.toInt(), rightValue.toInt()) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /android/src/main/java/com/jimmydaddy/imagemarker/base/Position.kt: -------------------------------------------------------------------------------- 1 | package com.jimmydaddy.imagemarker.base 2 | 3 | /** 4 | * Created by jimmydaddy on 2017/9/23. 5 | */ 6 | data class Position(var x: Float, var y: Float) { 7 | 8 | companion object { 9 | fun getTextPosition( 10 | position: String?, 11 | margin: Int, 12 | width: Int, 13 | height: Int, 14 | textWidth: Int, 15 | textHeight: Int 16 | ): Position { 17 | return if (position == null) { 18 | Position(margin.toFloat(), margin.toFloat()) 19 | } else when (position) { 20 | "topCenter" -> Position( 21 | ((width - textWidth) / 2).toFloat(), 22 | margin.toFloat() 23 | ) 24 | 25 | "topRight" -> Position( 26 | (width - textWidth).toFloat(), 27 | margin.toFloat() 28 | ) 29 | 30 | "center" -> Position( 31 | ((width - textWidth) / 2).toFloat(), 32 | ((height - textHeight) / 2).toFloat() 33 | ) 34 | 35 | "bottomLeft" -> Position( 36 | 0f, 37 | (height - textHeight - margin).toFloat() 38 | ) 39 | 40 | "bottomCenter" -> Position( 41 | ((width - textWidth) / 2).toFloat(), 42 | (height - textHeight).toFloat() 43 | ) 44 | 45 | else -> Position( 46 | (width - textWidth - margin).toFloat(), 47 | (height - textHeight - margin).toFloat() 48 | ) 49 | } 50 | } 51 | 52 | fun getTextPosition( 53 | position: PositionEnum?, 54 | width: Int, 55 | height: Int, 56 | textWidth: Int, 57 | textHeight: Int 58 | ): Position { 59 | // default margin 60 | val margin = 20 61 | return if (position == null) { 62 | Position(margin.toFloat(), margin.toFloat()) 63 | } else when (position) { 64 | PositionEnum.TOP_CENTER -> Position( 65 | ((width - textWidth) / 2).toFloat(), 66 | margin.toFloat() 67 | ) 68 | 69 | PositionEnum.TOP_RIGHT -> Position( 70 | (width - textWidth).toFloat(), 71 | margin.toFloat() 72 | ) 73 | 74 | PositionEnum.CENTER -> Position( 75 | ((width - textWidth) / 2).toFloat(), 76 | ((height - textHeight) / 2).toFloat() 77 | ) 78 | 79 | PositionEnum.BOTTOM_LEFT -> Position( 80 | margin.toFloat(), 81 | (height - textHeight - margin).toFloat() 82 | ) 83 | 84 | PositionEnum.BOTTOM_CENTER -> Position( 85 | ((width - textWidth) / 2).toFloat(), 86 | (height - textHeight).toFloat() 87 | ) 88 | 89 | PositionEnum.BOTTOM_RIGHT -> Position( 90 | (width - textWidth - margin).toFloat(), 91 | (height - textHeight - margin).toFloat() 92 | ) 93 | 94 | else -> Position(margin.toFloat(), margin.toFloat()) 95 | } 96 | } 97 | 98 | fun getImageRectFromPosition( 99 | position: String?, 100 | width: Int, 101 | height: Int, 102 | imageWidth: Int, 103 | imageHeight: Int 104 | ): Position { 105 | var left = 20 106 | var top = 40 107 | val right = imageWidth - width 108 | val pos = Position(left.toFloat(), top.toFloat()) 109 | if (position == null) { 110 | return pos 111 | } 112 | when (position) { 113 | "topCenter" -> { 114 | left = imageWidth / 2 - width / 2 115 | pos.x = left.toFloat() 116 | } 117 | 118 | "topRight" -> pos.x = (right - 20).toFloat() 119 | "center" -> { 120 | left = imageWidth / 2 - width / 2 121 | top = imageHeight / 2 - height / 2 122 | pos.x = left.toFloat() 123 | pos.y = top.toFloat() 124 | } 125 | 126 | "bottomLeft" -> { 127 | top = imageHeight - height 128 | pos.y = (top - 20).toFloat() 129 | } 130 | 131 | "bottomRight" -> { 132 | top = imageHeight - height 133 | left = imageWidth - width - 20 134 | pos.x = (left - 20).toFloat() 135 | pos.y = (top - 20).toFloat() 136 | } 137 | 138 | "bottomCenter" -> { 139 | top = imageHeight - height 140 | left = imageWidth / 2 - width / 2 141 | pos.x = (left - 20).toFloat() 142 | pos.y = (top - 20).toFloat() 143 | } 144 | } 145 | return pos 146 | } 147 | 148 | @JvmStatic 149 | fun getImageRectFromPosition( 150 | position: PositionEnum?, 151 | width: Int, 152 | height: Int, 153 | imageWidth: Int, 154 | imageHeight: Int 155 | ): Position { 156 | var left = 20 157 | var top = 20 158 | val right = imageWidth - width 159 | val pos = Position(left.toFloat(), top.toFloat()) 160 | if (position == null) { 161 | return pos 162 | } 163 | when (position) { 164 | PositionEnum.TOP_CENTER -> { 165 | left = imageWidth / 2 - width / 2 166 | pos.x = left.toFloat() 167 | } 168 | 169 | PositionEnum.TOP_RIGHT -> pos.x = (right - 20).toFloat() 170 | PositionEnum.CENTER -> { 171 | left = imageWidth / 2 - width / 2 172 | top = imageHeight / 2 - height / 2 173 | pos.x = left.toFloat() 174 | pos.y = top.toFloat() 175 | } 176 | 177 | PositionEnum.BOTTOM_LEFT -> { 178 | top = imageHeight - height 179 | pos.y = (top - 20).toFloat() 180 | } 181 | 182 | PositionEnum.BOTTOM_RIGHT -> { 183 | top = imageHeight - height 184 | left = imageWidth - width - 20 185 | pos.x = (left - 20).toFloat() 186 | pos.y = (top - 20).toFloat() 187 | } 188 | 189 | PositionEnum.BOTTOM_CENTER -> { 190 | top = imageHeight - height 191 | left = imageWidth / 2 - width / 2 192 | pos.x = (left - 20).toFloat() 193 | pos.y = (top - 20).toFloat() 194 | } 195 | 196 | PositionEnum.TOP_LEFT -> { 197 | pos.x = left.toFloat() 198 | pos.y = top.toFloat() 199 | } 200 | } 201 | return pos 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /android/src/main/java/com/jimmydaddy/imagemarker/base/PositionEnum.kt: -------------------------------------------------------------------------------- 1 | package com.jimmydaddy.imagemarker.base 2 | 3 | enum class PositionEnum(val value: String) { 4 | TOP_LEFT("topLeft"), TOP_CENTER("topCenter"), TOP_RIGHT("topRight"), CENTER("center"), BOTTOM_LEFT( 5 | "bottomLeft" 6 | ), 7 | BOTTOM_CENTER("bottomCenter"), BOTTOM_RIGHT("bottomRight"); 8 | 9 | companion object { 10 | fun getPosition(position: String?): PositionEnum { 11 | return when (position) { 12 | "topCenter" -> TOP_CENTER 13 | "topRight" -> TOP_RIGHT 14 | "center" -> CENTER 15 | "bottomLeft" -> BOTTOM_LEFT 16 | "bottomCenter" -> BOTTOM_CENTER 17 | "topLeft" -> TOP_LEFT 18 | else -> BOTTOM_RIGHT 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /android/src/main/java/com/jimmydaddy/imagemarker/base/RNImageSRC.kt: -------------------------------------------------------------------------------- 1 | package com.jimmydaddy.imagemarker.base 2 | 3 | import com.facebook.react.bridge.ReadableMap 4 | 5 | data class RNImageSRC(val options: ReadableMap?) { 6 | 7 | var width: Int = 0 8 | var height: Int = 0 9 | var scale: Int = 1 10 | var uri: String = "" 11 | 12 | init { 13 | width = if (options?.hasKey("width") == true) options.getInt("width")!! else 0 14 | height = if (options?.hasKey("height") == true) options.getInt("height") else 0 15 | scale = if (options?.hasKey("scale") == true) options.getInt("scale") else 1 16 | uri = if (options?.hasKey("uri") == true) options.getString("uri").toString() else "" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /android/src/main/java/com/jimmydaddy/imagemarker/base/Radius.kt: -------------------------------------------------------------------------------- 1 | package com.jimmydaddy.imagemarker.base 2 | 3 | import com.facebook.react.bridge.ReadableMap 4 | 5 | data class Radius(val options: ReadableMap?) { 6 | 7 | var x: String = "0" 8 | var y: String = "0" 9 | 10 | init { 11 | x = Utils.handleDynamicToString(options?.getDynamic("x")) 12 | y = Utils.handleDynamicToString(options?.getDynamic("y")) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /android/src/main/java/com/jimmydaddy/imagemarker/base/SaveFormat.kt: -------------------------------------------------------------------------------- 1 | package com.jimmydaddy.imagemarker.base 2 | 3 | enum class SaveFormat(val value: String) { 4 | PNG("png"), JPG("jpg"), BASE64("base64"); 5 | 6 | companion object { 7 | fun getFormat(format: String?): SaveFormat { 8 | return when (format) { 9 | "jpg", "JPG", "JPEG", "jpeg" -> JPG 10 | "base64", "BASE64" -> BASE64 11 | "png", "PNG" -> PNG 12 | else -> JPG 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /android/src/main/java/com/jimmydaddy/imagemarker/base/ShadowLayerStyle.kt: -------------------------------------------------------------------------------- 1 | package com.jimmydaddy.imagemarker.base 2 | 3 | import android.graphics.Color 4 | import android.util.Log 5 | import com.facebook.react.bridge.ReadableMap 6 | 7 | /** 8 | * Created by jimmydaddy on 2019/2/25. 9 | */ 10 | data class ShadowLayerStyle(val readableMap: ReadableMap?) { 11 | var radius = 0f 12 | var dx = 0f 13 | var dy = 0f 14 | var color = Color.TRANSPARENT 15 | 16 | init { 17 | if (null != readableMap) { 18 | try { 19 | setColor(readableMap.getString("color")) 20 | dx = readableMap.getDouble("dx").toFloat() 21 | dy = readableMap.getDouble("dy").toFloat() 22 | radius = readableMap.getDouble("radius").toFloat() 23 | } catch (e: Exception) { 24 | Log.d(Utils.TAG, "Unknown shadow style options ", e) 25 | } 26 | } 27 | } 28 | 29 | private fun setColor(color: String?) { 30 | try { 31 | val parsedColor = Color.parseColor(Utils.transRGBColor(color)) 32 | this.color = parsedColor 33 | } catch (e: Exception) { 34 | Log.d(Utils.TAG, "Unknown color string ", e) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /android/src/main/java/com/jimmydaddy/imagemarker/base/TextBackgroundStyle.kt: -------------------------------------------------------------------------------- 1 | package com.jimmydaddy.imagemarker.base 2 | 3 | import android.graphics.Color 4 | import android.util.Log 5 | import com.facebook.react.bridge.ReadableMap 6 | 7 | data class TextBackgroundStyle(val readableMap: ReadableMap?): Padding(readableMap) { 8 | var type: String? = "" 9 | var color = Color.TRANSPARENT 10 | var cornerRadius: CornerRadius? = null 11 | 12 | init { 13 | if (null != readableMap) { 14 | try { 15 | type = readableMap.getString("type") 16 | setColor(readableMap.getString("color")) 17 | if(readableMap.hasKey("cornerRadius")) { 18 | cornerRadius = readableMap.getMap("cornerRadius")?.let { CornerRadius(it) }!! 19 | } 20 | } catch (e: Exception) { 21 | Log.d(Utils.TAG, "Unknown text background options ", e) 22 | } 23 | } 24 | } 25 | 26 | private fun setColor(color: String?) { 27 | try { 28 | val parsedColor = Color.parseColor(Utils.transRGBColor(color)) 29 | this.color = parsedColor 30 | } catch (e: Exception) { 31 | Log.d(Utils.TAG, "Unknown color string ", e) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /android/src/main/java/com/jimmydaddy/imagemarker/base/TextOptions.kt: -------------------------------------------------------------------------------- 1 | package com.jimmydaddy.imagemarker.base 2 | 3 | import android.graphics.Canvas 4 | import android.graphics.Color 5 | import android.graphics.Paint 6 | import android.graphics.Path 7 | import android.graphics.RectF 8 | import android.graphics.Typeface 9 | import android.os.Build 10 | import android.text.Layout 11 | import android.text.StaticLayout 12 | import android.text.TextPaint 13 | import android.util.Log 14 | import android.util.TypedValue 15 | import com.facebook.react.bridge.ReactApplicationContext 16 | import com.facebook.react.bridge.ReadableMap 17 | import com.facebook.react.views.text.ReactFontManager 18 | import com.jimmydaddy.imagemarker.base.Constants.DEFAULT_MARGIN 19 | import kotlin.math.ceil 20 | 21 | @Suppress("DEPRECATION") 22 | data class TextOptions(val options: ReadableMap) { 23 | private var text: String? = options.getString("text") 24 | private var x: String? 25 | private var y: String? 26 | private var positionEnum: PositionEnum? 27 | private var style: TextStyle 28 | 29 | init { 30 | if (text == null) { 31 | throw MarkerError(ErrorCode.PARAMS_REQUIRED, "mark text is required") 32 | } 33 | val positionOptions = 34 | if (null != options.getMap("position")) options.getMap("position") else null 35 | x = if (positionOptions!!.hasKey("X")) Utils.handleDynamicToString(positionOptions.getDynamic("X")) else null 36 | y = if (positionOptions.hasKey("Y")) Utils.handleDynamicToString(positionOptions.getDynamic("Y")) else null 37 | positionEnum = 38 | if (null != positionOptions.getString("position")) PositionEnum.getPosition( 39 | positionOptions.getString("position") 40 | ) else null 41 | style = TextStyle(options.getMap("style")) 42 | } 43 | 44 | fun applyStyle( 45 | context: ReactApplicationContext, 46 | canvas: Canvas, 47 | maxWidth: Int, 48 | maxHeight: Int 49 | ) { 50 | val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG or Paint.DEV_KERN_TEXT_FLAG) 51 | textPaint.isAntiAlias = true 52 | if (null != style.shadowLayerStyle) { 53 | textPaint.setShadowLayer( 54 | style.shadowLayerStyle!!.radius, 55 | style.shadowLayerStyle!!.dx, 56 | style.shadowLayerStyle!!.dy, 57 | style.shadowLayerStyle!!.color 58 | ) 59 | } 60 | 61 | var typefaceFamily = Typeface.DEFAULT 62 | if (style.fontName != null) { 63 | typefaceFamily = try { 64 | //设置字体失败时使用默认字体 65 | ReactFontManager.getInstance() 66 | .getTypeface(style.fontName!!, Typeface.NORMAL, context.assets) 67 | } catch (e: Exception) { 68 | Log.e(Constants.IMAGE_MARKER_TAG, "Could not get typeface: " + e.message) 69 | Typeface.DEFAULT 70 | } 71 | } 72 | // val textSize = TypedValue.applyDimension( 73 | // TypedValue.COMPLEX_UNIT_SP, 74 | // style.fontSize, 75 | // context.resources.displayMetrics 76 | // ) 77 | val textSize = style.fontSize 78 | textPaint.isAntiAlias = true 79 | textPaint.textSize = textSize 80 | Log.i(Constants.IMAGE_MARKER_TAG, "textSize: " + textSize + " fontSize: " + style.fontSize + " displayMetrics: " + context.resources.displayMetrics) 81 | textPaint.color = Color.parseColor(Utils.transRGBColor(style.color)) 82 | textPaint.isUnderlineText = style.underline 83 | textPaint.textSkewX = style.skewX!! 84 | var typeface = Typeface.create(typefaceFamily, Typeface.NORMAL) 85 | if (style.italic && style.bold) { 86 | typeface = Typeface.create(typefaceFamily, Typeface.BOLD_ITALIC) 87 | } else if (style.italic) { 88 | typeface = Typeface.create(typefaceFamily, Typeface.ITALIC) 89 | } else if (style.bold) { 90 | typeface = Typeface.create(typefaceFamily, Typeface.BOLD) 91 | } 92 | textPaint.isStrikeThruText = style.strikeThrough 93 | textPaint.typeface = typeface 94 | textPaint.textAlign = style.textAlign 95 | // ALIGN_CENTER, ALIGN_NORMAL, ALIGN_OPPOSITE 96 | val textLayout: StaticLayout = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 97 | val builder = 98 | StaticLayout.Builder.obtain(text!!, 0, text!!.length, textPaint, canvas.width) 99 | builder.setAlignment(Layout.Alignment.ALIGN_NORMAL) 100 | builder.setLineSpacing(0.0f, 1.0f) 101 | builder.setIncludePad(false) 102 | builder.build() 103 | } else { 104 | StaticLayout( 105 | text, 106 | textPaint, 107 | canvas.width, 108 | Layout.Alignment.ALIGN_NORMAL, 109 | 1.0f, 110 | 0.0f, 111 | false 112 | ) 113 | } 114 | 115 | val textHeight = textLayout.height 116 | var textWidth = 0 117 | val count = textLayout.lineCount 118 | for (a in 0 until count) { 119 | textWidth = ceil( 120 | textWidth.toFloat() 121 | .coerceAtLeast(textLayout.getLineWidth(a) + textLayout.getLineLeft(a)).toDouble() 122 | ).toInt() 123 | } 124 | val margin = DEFAULT_MARGIN 125 | var position = Position(margin, margin) 126 | if (positionEnum != null) { 127 | position = Position.getTextPosition( 128 | positionEnum, 129 | maxWidth, 130 | maxHeight, 131 | textWidth, 132 | textHeight 133 | ) 134 | } else { 135 | if (null != x) { 136 | position.x = Utils.parseSpreadValue(x, maxWidth.toFloat()) 137 | } 138 | if (null != y) { 139 | position.y = Utils.parseSpreadValue(y, maxHeight.toFloat()) 140 | } 141 | } 142 | val x = position.x 143 | val y = position.y 144 | 145 | canvas.save() 146 | val textRectWithPosition = RectF(x, y , textWidth.toFloat(), textHeight.toFloat()) 147 | canvas.rotate(style.rotate.toFloat(), textRectWithPosition.centerX(), textRectWithPosition.centerY()) 148 | 149 | // Draw text background 150 | if (null != style.textBackgroundStyle) { 151 | val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.LINEAR_TEXT_FLAG) 152 | paint.style = Paint.Style.FILL 153 | paint.color = style.textBackgroundStyle!!.color 154 | val bgInsets = style.textBackgroundStyle!!.toEdgeInsets(maxWidth, maxHeight) 155 | var bgRect = RectF(x - bgInsets.left, y - bgInsets.top, x + textWidth + bgInsets.right, y + textHeight + bgInsets.bottom) 156 | when (style.textBackgroundStyle!!.type) { 157 | "stretchX" -> { 158 | bgRect = RectF(0f, y - bgInsets.top, maxWidth.toFloat(), 159 | y + textHeight + bgInsets.bottom 160 | ) 161 | } 162 | 163 | "stretchY" -> { 164 | bgRect = RectF(x - bgInsets.left, 0f, 165 | x + textWidth + bgInsets.right, maxHeight.toFloat()) 166 | } 167 | } 168 | 169 | if (style.textBackgroundStyle!!.cornerRadius != null) { 170 | val path = Path() 171 | 172 | path.addRoundRect(bgRect, style.textBackgroundStyle!!.cornerRadius!!.radii(bgRect), Path.Direction.CW) 173 | 174 | canvas.drawPath(path, paint) 175 | } else { 176 | canvas.drawRect(bgRect, paint) 177 | } 178 | } 179 | val textX = when(textPaint.textAlign) { 180 | Paint.Align.RIGHT -> x + textWidth 181 | Paint.Align.CENTER -> x + textWidth / 2 182 | Paint.Align.LEFT -> x 183 | else -> x 184 | } 185 | canvas.translate(textX, y) 186 | textLayout.draw(canvas) 187 | canvas.restore() 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /android/src/main/java/com/jimmydaddy/imagemarker/base/TextStyle.kt: -------------------------------------------------------------------------------- 1 | package com.jimmydaddy.imagemarker.base 2 | 3 | import android.graphics.Paint.Align 4 | import com.facebook.react.bridge.ReadableMap 5 | import com.jimmydaddy.imagemarker.base.Constants.DEFAULT_FONT_SIZE 6 | 7 | data class TextStyle(val options: ReadableMap?) { 8 | var color: String? = if (null != options!!.getString("color")) options.getString("color") else null 9 | var fontName: String? = if (null != options?.getString("fontName")) options.getString("fontName") else null 10 | var fontSize: Float = if (options?.hasKey("fontSize") == true) options.getDouble("fontSize").toFloat() else DEFAULT_FONT_SIZE 11 | var shadowLayerStyle: ShadowLayerStyle? 12 | var textBackgroundStyle: TextBackgroundStyle? 13 | var underline: Boolean = if (options?.hasKey("underline") == true) options.getBoolean("underline") else false 14 | var skewX: Float? = if (options?.hasKey("skewX") == true) options.getDouble("skewX").toFloat() else 0f 15 | var strikeThrough: Boolean = if (options?.hasKey("strikeThrough") == true) options.getBoolean("strikeThrough") else false 16 | var textAlign: Align 17 | var italic: Boolean = if (options?.hasKey("italic") == true) options.getBoolean("italic") else false 18 | var bold: Boolean = if (options?.hasKey("bold") == true) options.getBoolean("bold") else false 19 | var rotate: Int = if (options?.hasKey("rotate") == true) options.getInt("rotate") else 0 20 | 21 | init { 22 | val myShadowStyle = options?.getMap("shadowStyle") 23 | shadowLayerStyle = myShadowStyle?.let { ShadowLayerStyle(it) } 24 | val myTextBackgroundStyle = options?.getMap("textBackgroundStyle") 25 | textBackgroundStyle = myTextBackgroundStyle?.let { TextBackgroundStyle(it) } 26 | textAlign = Align.LEFT 27 | if (options?.hasKey("textAlign") == true) { 28 | textAlign = when (options.getString("textAlign")) { 29 | "center" -> Align.CENTER 30 | "right" -> Align.RIGHT 31 | else -> Align.LEFT 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /android/src/main/java/com/jimmydaddy/imagemarker/base/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.jimmydaddy.imagemarker.base 2 | 3 | import android.graphics.Bitmap 4 | import android.graphics.BitmapFactory 5 | import android.graphics.Matrix 6 | import android.media.ExifInterface 7 | import android.util.Log 8 | import com.facebook.react.bridge.Dynamic 9 | import com.facebook.react.bridge.ReadableType 10 | import java.io.IOException 11 | import java.io.InputStream 12 | import java.net.HttpURLConnection 13 | import java.net.URL 14 | 15 | /** 16 | * Created by jimmydaddy on 2018/4/8. 17 | */ 18 | class Utils { 19 | 20 | companion object { 21 | var TAG = "[ImageMarker]" 22 | 23 | @JvmStatic 24 | fun getBlankBitmap(width: Int, height: Int): Bitmap? { 25 | var icon: Bitmap? = null 26 | try { 27 | icon = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) 28 | } catch (e: OutOfMemoryError) { 29 | print(e.message) 30 | while (icon == null) { 31 | System.gc() 32 | System.runFinalization() 33 | icon = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565) 34 | } 35 | } 36 | return icon 37 | } 38 | 39 | private fun readDegree(path: String?): Int { 40 | var degree = 0 41 | try { 42 | val exifInterface = ExifInterface(path!!) 43 | val orientation = exifInterface.getAttributeInt( 44 | ExifInterface.TAG_ORIENTATION, 45 | ExifInterface.ORIENTATION_NORMAL 46 | ) 47 | when (orientation) { 48 | ExifInterface.ORIENTATION_ROTATE_90 -> degree = 90 49 | ExifInterface.ORIENTATION_ROTATE_180 -> degree = 180 50 | ExifInterface.ORIENTATION_ROTATE_270 -> degree = 270 51 | } 52 | } catch (e: IOException) { 53 | e.printStackTrace() 54 | } 55 | return degree 56 | } 57 | 58 | @JvmStatic 59 | fun scaleBitmap(bitmap: Bitmap, scale: Float): Bitmap? { 60 | val w = bitmap.width 61 | val h = bitmap.height 62 | val mtx = Matrix() 63 | if (scale != 1f && scale >= 0) { 64 | mtx.postScale(scale, scale) 65 | } 66 | var scaledBitmap: Bitmap? = null 67 | try { 68 | scaledBitmap = Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true) 69 | } catch (e: OutOfMemoryError) { 70 | print(e.message) 71 | while (scaledBitmap == null) { 72 | System.gc() 73 | System.runFinalization() 74 | scaledBitmap = Bitmap.createBitmap(bitmap, 0, 0, w, h, mtx, true) 75 | } 76 | } 77 | return scaledBitmap 78 | } 79 | 80 | fun transRGBColor(color: String?): String { 81 | val colorStr = color!!.substring(1) 82 | if (colorStr.length == 3) { 83 | var fullColor = "" 84 | for (i in colorStr.indices) { 85 | val temp = colorStr.substring(i, i + 1) 86 | fullColor += temp + temp 87 | } 88 | return "#$fullColor" 89 | } 90 | if (colorStr.length == 4) { 91 | val alpha = colorStr.substring(3, 4) 92 | val hexColor = colorStr.substring(0, 3) 93 | var fullColor = alpha + alpha 94 | for (i in hexColor.indices) { 95 | val temp = colorStr.substring(i, i + 1) 96 | fullColor += temp + temp 97 | } 98 | return "#$fullColor" 99 | } 100 | return if (colorStr.length == 8) { 101 | val alpha = colorStr.substring(6, 8) 102 | val hexColor = colorStr.substring(0, 6) 103 | "#$alpha$hexColor" 104 | } else { 105 | color 106 | } 107 | } 108 | 109 | @JvmStatic 110 | fun getStringSafe(key: String?, map: Map): String? { 111 | val obj = map[key] 112 | return obj?.toString() 113 | } 114 | 115 | /** 116 | * read stream from remote 117 | * 118 | * @param url 119 | * @return 120 | */ 121 | fun getStreamFromInternet(url: String): InputStream? { 122 | var connection: HttpURLConnection? = null 123 | try { 124 | val mUrl = URL(url) 125 | connection = mUrl.openConnection() as HttpURLConnection 126 | connection.requestMethod = "GET" 127 | // 10 秒超时时间 128 | connection.connectTimeout = 10000 129 | connection.readTimeout = 10000 130 | connection.connect() 131 | val responseCode = connection.responseCode 132 | if (responseCode == 200) { 133 | return connection.inputStream 134 | } else { 135 | Log.d(TAG, "getStreamFromInternet: read stream from remote: $url failed") 136 | } 137 | } catch (e: Exception) { 138 | e.printStackTrace() 139 | } finally { 140 | connection?.disconnect() 141 | } 142 | return null 143 | } 144 | 145 | 146 | fun checkSpreadValue(str: String?, maxLength: Int = 1): Boolean { 147 | if (str == null) return false 148 | val pattern = """^((\d+|\d+%)\s?){1,$maxLength}$""".toRegex() 149 | return pattern.containsMatchIn(str) 150 | } 151 | 152 | fun parseSpreadValue(v: String?, relativeTo: Float): Float { 153 | if (v == null) return 0f 154 | return if (v.endsWith("%")) { 155 | val percent = v.dropLast(1).toFloatOrNull()?.div(100) ?: 0f 156 | relativeTo * percent 157 | } else { 158 | v.toFloatOrNull() ?: 0f 159 | } 160 | } 161 | 162 | fun handleDynamicToString(d: Dynamic?): String { 163 | return if (d == null) "0" 164 | else 165 | when (d.type) { 166 | ReadableType.String -> d.asString() 167 | ReadableType.Number -> d.asDouble().toString() 168 | else -> { 169 | "0" 170 | } 171 | } 172 | } 173 | } 174 | 175 | /** 176 | * 获取最大内存使用 177 | * 178 | * @return 179 | */ 180 | val maxMemory: Int 181 | get() = Runtime.getRuntime().maxMemory().toInt() / 1024 182 | 183 | fun scaleBitmap(path: String?, scale: Float): Bitmap? { 184 | val degree = readDegree(path) 185 | val options = BitmapFactory.Options() 186 | var prePhoto: Bitmap? = null 187 | options.inSampleSize = scale.toInt() 188 | try { 189 | prePhoto = BitmapFactory.decodeFile(path, options) 190 | } catch (e: OutOfMemoryError) { 191 | print(e.message) 192 | while (prePhoto == null) { 193 | System.gc() 194 | System.runFinalization() 195 | prePhoto = BitmapFactory.decodeFile(path, options) 196 | } 197 | } 198 | // 199 | if (prePhoto == null) return null 200 | val w = options.outWidth 201 | val h = options.outHeight 202 | val mtx = Matrix() 203 | mtx.postRotate(degree.toFloat()) 204 | if (scale != 1f && scale >= 0) { 205 | mtx.postScale(scale, scale) 206 | } 207 | var scaledBitmap: Bitmap? = null 208 | try { 209 | scaledBitmap = Bitmap.createBitmap(prePhoto, 0, 0, w, h, mtx, true) 210 | } catch (e: OutOfMemoryError) { 211 | print(e.message) 212 | while (scaledBitmap == null) { 213 | System.gc() 214 | System.runFinalization() 215 | scaledBitmap = Bitmap.createBitmap(prePhoto, 0, 0, w, h, mtx, true) 216 | } 217 | } 218 | return scaledBitmap 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /android/src/main/java/com/jimmydaddy/imagemarker/base/WatermarkImageOptions.kt: -------------------------------------------------------------------------------- 1 | package com.jimmydaddy.imagemarker.base 2 | 3 | import com.facebook.react.bridge.ReadableMap 4 | 5 | data class WatermarkImageOptions(val options: ReadableMap?) { 6 | lateinit var imageOption: ImageOptions 7 | var x: String? = null 8 | var y: String? = null 9 | var positionEnum: PositionEnum? = null 10 | 11 | init { 12 | if (options != null) { 13 | imageOption = ImageOptions(options) 14 | val positionOptions = 15 | if (null != options.getMap("position")) options.getMap("position") else null 16 | x = 17 | if (positionOptions!!.hasKey("X")) Utils.handleDynamicToString(positionOptions.getDynamic("X")) else null 18 | y = 19 | if (positionOptions.hasKey("Y")) Utils.handleDynamicToString(positionOptions.getDynamic("Y")) else null 20 | positionEnum = 21 | if (null != positionOptions.getString("position")) PositionEnum.getPosition( 22 | positionOptions.getString("position") 23 | ) else null 24 | } 25 | } 26 | 27 | constructor(watermarkImage: ImageOptions, x: String?, y: String?, position: PositionEnum?) : this(null) { 28 | imageOption = watermarkImage 29 | this.x = x 30 | this.y = y 31 | this.positionEnum = position 32 | } 33 | 34 | companion object { 35 | @JvmStatic 36 | fun checkWatermarkImageParams(opts: ReadableMap, reject: (String, String, Throwable?) -> Unit): WatermarkImageOptions? { 37 | return try { 38 | WatermarkImageOptions(opts) 39 | } catch (error: Throwable) { 40 | error.localizedMessage?.let { reject(error.message ?: "", it, null) } 41 | null 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /android/src/test/java/com/jimmydaddy/imagemarker/base/CornerRadiusTest.kt: -------------------------------------------------------------------------------- 1 | package com.jimmydaddy.imagemarker.base 2 | 3 | import android.graphics.RectF 4 | import com.facebook.react.bridge.ReadableMap 5 | import com.facebook.react.bridge.ReadableMapKeySetIterator 6 | import org.junit.Assert 7 | import org.junit.Test 8 | import org.mockito.Mockito 9 | 10 | class CornerRadiusTest { 11 | 12 | @Test 13 | fun testRadii() { 14 | // 创建一个 mock 的 ReadableMap 15 | val mockMap = Mockito.mock(ReadableMap::class.java) 16 | val mockMapTopLeft = Mockito.mock(ReadableMap::class.java) 17 | 18 | val mockIterator = Mockito.mock(ReadableMapKeySetIterator::class.java) 19 | 20 | // 设置 mock 对象的行为 21 | Mockito.`when`(mockMap.keySetIterator()).thenReturn(mockIterator) 22 | Mockito.`when`(mockIterator.hasNextKey()).thenReturn(true, false) 23 | Mockito.`when`(mockIterator.nextKey()).thenReturn("topLeft") 24 | Mockito.`when`(mockMap.getMap("topLeft")).thenReturn(mockMapTopLeft) 25 | 26 | // 创建一个 CornerRadius 对象 27 | val cornerRadius = CornerRadius(mockMap) 28 | 29 | // 测试 radii 方法 30 | val rect = RectF(0f, 0f, 100f, 100f) 31 | val radii = cornerRadius.radii(rect) 32 | 33 | // 验证结果 34 | Assert.assertEquals(8, radii.size) 35 | } 36 | } 37 | 38 | -------------------------------------------------------------------------------- /app.plugin.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require('./lib/commonjs/expo-plugin/withImageMarker'); 3 | -------------------------------------------------------------------------------- /assets/AndroidMarker.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/assets/AndroidMarker.gif -------------------------------------------------------------------------------- /assets/IOSMarker.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/assets/IOSMarker.gif -------------------------------------------------------------------------------- /assets/alphabgonly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/assets/alphabgonly.png -------------------------------------------------------------------------------- /assets/alphicononly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/assets/alphicononly.png -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/assets/icon.png -------------------------------------------------------------------------------- /assets/imagewatermark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/assets/imagewatermark.png -------------------------------------------------------------------------------- /assets/multiple_icon_markers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/assets/multiple_icon_markers.png -------------------------------------------------------------------------------- /assets/multipletexts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/assets/multipletexts.png -------------------------------------------------------------------------------- /assets/rotatebg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/assets/rotatebg.png -------------------------------------------------------------------------------- /assets/rotateicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/assets/rotateicon.png -------------------------------------------------------------------------------- /assets/rotateimageicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/assets/rotateimageicon.png -------------------------------------------------------------------------------- /assets/rotatetexts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/assets/rotatetexts.png -------------------------------------------------------------------------------- /assets/rotatetexts_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/assets/rotatetexts_1.png -------------------------------------------------------------------------------- /assets/sample1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/assets/sample1.png -------------------------------------------------------------------------------- /assets/sample2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/assets/sample2.png -------------------------------------------------------------------------------- /assets/shadow.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/assets/shadow.jpeg -------------------------------------------------------------------------------- /assets/shadow_bg_fit.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/assets/shadow_bg_fit.jpeg -------------------------------------------------------------------------------- /assets/shadow_bg_sx.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/assets/shadow_bg_sx.jpeg -------------------------------------------------------------------------------- /assets/shadow_bg_sy.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/assets/shadow_bg_sy.jpeg -------------------------------------------------------------------------------- /assets/textbgcornerradius.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/assets/textbgcornerradius.png -------------------------------------------------------------------------------- /assets/textswihoutbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/assets/textswihoutbg.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/custom.css: -------------------------------------------------------------------------------- 1 | table { 2 | border-collapse: collapse; 3 | } 4 | 5 | table, th, td { 6 | border: 2px solid var(--color-accent); 7 | padding: 16px; 8 | } 9 | 10 | table, th { 11 | font-weight: bold; 12 | } 13 | -------------------------------------------------------------------------------- /docs/latest/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docs/latest/assets/custom.css: -------------------------------------------------------------------------------- 1 | table { 2 | border-collapse: collapse; 3 | } 4 | 5 | table, th, td { 6 | border: 2px solid var(--color-accent); 7 | padding: 16px; 8 | } 9 | 10 | table, th { 11 | font-weight: bold; 12 | } 13 | -------------------------------------------------------------------------------- /docs/latest/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-hl-0: #008000; 3 | --dark-hl-0: #6A9955; 4 | --light-hl-1: #795E26; 5 | --dark-hl-1: #DCDCAA; 6 | --light-hl-2: #000000; 7 | --dark-hl-2: #D4D4D4; 8 | --light-hl-3: #A31515; 9 | --dark-hl-3: #CE9178; 10 | --light-hl-4: #0000FF; 11 | --dark-hl-4: #569CD6; 12 | --light-hl-5: #AF00DB; 13 | --dark-hl-5: #C586C0; 14 | --light-hl-6: #001080; 15 | --dark-hl-6: #9CDCFE; 16 | --light-hl-7: #0070C1; 17 | --dark-hl-7: #4FC1FF; 18 | --light-hl-8: #098658; 19 | --dark-hl-8: #B5CEA8; 20 | --light-hl-9: #EE0000; 21 | --dark-hl-9: #D7BA7D; 22 | --light-hl-10: #000000; 23 | --dark-hl-10: #C8C8C8; 24 | --light-code-background: #FFFFFF; 25 | --dark-code-background: #1E1E1E; 26 | } 27 | 28 | @media (prefers-color-scheme: light) { :root { 29 | --hl-0: var(--light-hl-0); 30 | --hl-1: var(--light-hl-1); 31 | --hl-2: var(--light-hl-2); 32 | --hl-3: var(--light-hl-3); 33 | --hl-4: var(--light-hl-4); 34 | --hl-5: var(--light-hl-5); 35 | --hl-6: var(--light-hl-6); 36 | --hl-7: var(--light-hl-7); 37 | --hl-8: var(--light-hl-8); 38 | --hl-9: var(--light-hl-9); 39 | --hl-10: var(--light-hl-10); 40 | --code-background: var(--light-code-background); 41 | } } 42 | 43 | @media (prefers-color-scheme: dark) { :root { 44 | --hl-0: var(--dark-hl-0); 45 | --hl-1: var(--dark-hl-1); 46 | --hl-2: var(--dark-hl-2); 47 | --hl-3: var(--dark-hl-3); 48 | --hl-4: var(--dark-hl-4); 49 | --hl-5: var(--dark-hl-5); 50 | --hl-6: var(--dark-hl-6); 51 | --hl-7: var(--dark-hl-7); 52 | --hl-8: var(--dark-hl-8); 53 | --hl-9: var(--dark-hl-9); 54 | --hl-10: var(--dark-hl-10); 55 | --code-background: var(--dark-code-background); 56 | } } 57 | 58 | :root[data-theme='light'] { 59 | --hl-0: var(--light-hl-0); 60 | --hl-1: var(--light-hl-1); 61 | --hl-2: var(--light-hl-2); 62 | --hl-3: var(--light-hl-3); 63 | --hl-4: var(--light-hl-4); 64 | --hl-5: var(--light-hl-5); 65 | --hl-6: var(--light-hl-6); 66 | --hl-7: var(--light-hl-7); 67 | --hl-8: var(--light-hl-8); 68 | --hl-9: var(--light-hl-9); 69 | --hl-10: var(--light-hl-10); 70 | --code-background: var(--light-code-background); 71 | } 72 | 73 | :root[data-theme='dark'] { 74 | --hl-0: var(--dark-hl-0); 75 | --hl-1: var(--dark-hl-1); 76 | --hl-2: var(--dark-hl-2); 77 | --hl-3: var(--dark-hl-3); 78 | --hl-4: var(--dark-hl-4); 79 | --hl-5: var(--dark-hl-5); 80 | --hl-6: var(--dark-hl-6); 81 | --hl-7: var(--dark-hl-7); 82 | --hl-8: var(--dark-hl-8); 83 | --hl-9: var(--dark-hl-9); 84 | --hl-10: var(--dark-hl-10); 85 | --code-background: var(--dark-code-background); 86 | } 87 | 88 | .hl-0 { color: var(--hl-0); } 89 | .hl-1 { color: var(--hl-1); } 90 | .hl-2 { color: var(--hl-2); } 91 | .hl-3 { color: var(--hl-3); } 92 | .hl-4 { color: var(--hl-4); } 93 | .hl-5 { color: var(--hl-5); } 94 | .hl-6 { color: var(--hl-6); } 95 | .hl-7 { color: var(--hl-7); } 96 | .hl-8 { color: var(--hl-8); } 97 | .hl-9 { color: var(--hl-9); } 98 | .hl-10 { color: var(--hl-10); } 99 | pre, code { background: var(--code-background); } 100 | -------------------------------------------------------------------------------- /docs/v1.0.x/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docs/v1.0.x/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-hl-0: #000000; 3 | --dark-hl-0: #D4D4D4; 4 | --light-hl-1: #A31515; 5 | --dark-hl-1: #CE9178; 6 | --light-hl-2: #AF00DB; 7 | --dark-hl-2: #C586C0; 8 | --light-hl-3: #001080; 9 | --dark-hl-3: #9CDCFE; 10 | --light-hl-4: #008000; 11 | --dark-hl-4: #6A9955; 12 | --light-hl-5: #0000FF; 13 | --dark-hl-5: #569CD6; 14 | --light-hl-6: #0070C1; 15 | --dark-hl-6: #4FC1FF; 16 | --light-hl-7: #795E26; 17 | --dark-hl-7: #DCDCAA; 18 | --light-hl-8: #098658; 19 | --dark-hl-8: #B5CEA8; 20 | --light-hl-9: #000000; 21 | --dark-hl-9: #C8C8C8; 22 | --light-code-background: #FFFFFF; 23 | --dark-code-background: #1E1E1E; 24 | } 25 | 26 | @media (prefers-color-scheme: light) { :root { 27 | --hl-0: var(--light-hl-0); 28 | --hl-1: var(--light-hl-1); 29 | --hl-2: var(--light-hl-2); 30 | --hl-3: var(--light-hl-3); 31 | --hl-4: var(--light-hl-4); 32 | --hl-5: var(--light-hl-5); 33 | --hl-6: var(--light-hl-6); 34 | --hl-7: var(--light-hl-7); 35 | --hl-8: var(--light-hl-8); 36 | --hl-9: var(--light-hl-9); 37 | --code-background: var(--light-code-background); 38 | } } 39 | 40 | @media (prefers-color-scheme: dark) { :root { 41 | --hl-0: var(--dark-hl-0); 42 | --hl-1: var(--dark-hl-1); 43 | --hl-2: var(--dark-hl-2); 44 | --hl-3: var(--dark-hl-3); 45 | --hl-4: var(--dark-hl-4); 46 | --hl-5: var(--dark-hl-5); 47 | --hl-6: var(--dark-hl-6); 48 | --hl-7: var(--dark-hl-7); 49 | --hl-8: var(--dark-hl-8); 50 | --hl-9: var(--dark-hl-9); 51 | --code-background: var(--dark-code-background); 52 | } } 53 | 54 | :root[data-theme='light'] { 55 | --hl-0: var(--light-hl-0); 56 | --hl-1: var(--light-hl-1); 57 | --hl-2: var(--light-hl-2); 58 | --hl-3: var(--light-hl-3); 59 | --hl-4: var(--light-hl-4); 60 | --hl-5: var(--light-hl-5); 61 | --hl-6: var(--light-hl-6); 62 | --hl-7: var(--light-hl-7); 63 | --hl-8: var(--light-hl-8); 64 | --hl-9: var(--light-hl-9); 65 | --code-background: var(--light-code-background); 66 | } 67 | 68 | :root[data-theme='dark'] { 69 | --hl-0: var(--dark-hl-0); 70 | --hl-1: var(--dark-hl-1); 71 | --hl-2: var(--dark-hl-2); 72 | --hl-3: var(--dark-hl-3); 73 | --hl-4: var(--dark-hl-4); 74 | --hl-5: var(--dark-hl-5); 75 | --hl-6: var(--dark-hl-6); 76 | --hl-7: var(--dark-hl-7); 77 | --hl-8: var(--dark-hl-8); 78 | --hl-9: var(--dark-hl-9); 79 | --code-background: var(--dark-code-background); 80 | } 81 | 82 | .hl-0 { color: var(--hl-0); } 83 | .hl-1 { color: var(--hl-1); } 84 | .hl-2 { color: var(--hl-2); } 85 | .hl-3 { color: var(--hl-3); } 86 | .hl-4 { color: var(--hl-4); } 87 | .hl-5 { color: var(--hl-5); } 88 | .hl-6 { color: var(--hl-6); } 89 | .hl-7 { color: var(--hl-7); } 90 | .hl-8 { color: var(--hl-8); } 91 | .hl-9 { color: var(--hl-9); } 92 | pre, code { background: var(--code-background); } 93 | -------------------------------------------------------------------------------- /example/.bundle/config: -------------------------------------------------------------------------------- 1 | BUNDLE_PATH: "vendor/bundle" 2 | BUNDLE_FORCE_RUBY_PLATFORM: 1 3 | -------------------------------------------------------------------------------- /example/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: '@react-native', 4 | }; 5 | -------------------------------------------------------------------------------- /example/.node-version: -------------------------------------------------------------------------------- 1 | 18 2 | -------------------------------------------------------------------------------- /example/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /example/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # You may use http://rbenv.org/ or https://rvm.io/ to install and use this version 4 | ruby ">= 2.6.10" 5 | gem 'cocoapods', '~> 1.13' 6 | gem 'activesupport', '>= 6.1.7.3', '< 7.1.0' 7 | -------------------------------------------------------------------------------- /example/android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/example/android/app/debug.keystore -------------------------------------------------------------------------------- /example/android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -------------------------------------------------------------------------------- /example/android/app/release.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/example/android/app/release.keystore -------------------------------------------------------------------------------- /example/android/app/src/androidTest/java/com/imagemarkerexample/MainActivityTest.kt: -------------------------------------------------------------------------------- 1 | package com.imagemarkerexample 2 | 3 | import androidx.test.espresso.Espresso.onView 4 | import androidx.test.espresso.action.ViewActions.click 5 | import androidx.test.espresso.assertion.ViewAssertions.matches 6 | import androidx.test.espresso.matcher.ViewMatchers.isDisplayed 7 | import androidx.test.espresso.matcher.ViewMatchers.withText 8 | import androidx.test.ext.junit.runners.AndroidJUnit4 9 | import androidx.test.platform.app.InstrumentationRegistry 10 | import org.junit.Assert 11 | import org.junit.Test 12 | import org.junit.runner.RunWith 13 | 14 | @RunWith(AndroidJUnit4::class) 15 | class MainActivityTest { 16 | @Test 17 | fun test() { 18 | onView(withText("background image format:")).check(matches(isDisplayed())) 19 | onView(withText("topLeft")).perform(click()).check(matches(isDisplayed())) 20 | } 21 | 22 | @Test 23 | fun useAppContext() { 24 | // Context of the app under test. 25 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 26 | Assert.assertEquals("com.imagemarkerexample", appContext.packageName) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /example/android/app/src/main/assets/fonts/MaShanZheng-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/example/android/app/src/main/assets/fonts/MaShanZheng-Regular.ttf -------------------------------------------------------------------------------- /example/android/app/src/main/assets/fonts/RubikBurned-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/example/android/app/src/main/assets/fonts/RubikBurned-Regular.ttf -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/imagemarkerexample/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.imagemarkerexample 2 | 3 | import com.facebook.react.ReactActivity 4 | import com.facebook.react.ReactActivityDelegate 5 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled 6 | import com.facebook.react.defaults.DefaultReactActivityDelegate 7 | 8 | class MainActivity : ReactActivity() { 9 | 10 | /** 11 | * Returns the name of the main component registered from JavaScript. This is used to schedule 12 | * rendering of the component. 13 | */ 14 | override fun getMainComponentName(): String = "ImageMarkerExample" 15 | 16 | /** 17 | * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] 18 | * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] 19 | */ 20 | override fun createReactActivityDelegate(): ReactActivityDelegate = 21 | DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) 22 | } 23 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/imagemarkerexample/MainApplication.kt: -------------------------------------------------------------------------------- 1 | package com.imagemarkerexample 2 | 3 | import android.app.Application 4 | import com.facebook.react.PackageList 5 | import com.facebook.react.ReactApplication 6 | import com.facebook.react.ReactHost 7 | import com.facebook.react.ReactNativeHost 8 | import com.facebook.react.ReactPackage 9 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load 10 | import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost 11 | import com.facebook.react.defaults.DefaultReactNativeHost 12 | import com.facebook.react.flipper.ReactNativeFlipper 13 | import com.facebook.soloader.SoLoader 14 | 15 | class MainApplication : Application(), ReactApplication { 16 | 17 | override val reactNativeHost: ReactNativeHost = 18 | object : DefaultReactNativeHost(this) { 19 | override fun getPackages(): List { 20 | // Packages that cannot be autolinked yet can be added manually here, for example: 21 | // packages.add(new MyReactNativePackage()); 22 | return PackageList(this).packages 23 | } 24 | 25 | override fun getJSMainModuleName(): String = "index" 26 | 27 | override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG 28 | 29 | override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED 30 | override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED 31 | } 32 | 33 | override val reactHost: ReactHost 34 | get() = getDefaultReactHost(this.applicationContext, reactNativeHost) 35 | 36 | override fun onCreate() { 37 | super.onCreate() 38 | SoLoader.init(this, false) 39 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { 40 | // If you opted-in for the New Architecture, we load the native entry point for this app. 41 | load() 42 | } 43 | ReactNativeFlipper.initializeFlipper(this, reactNativeHost.reactInstanceManager) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/rn_edit_text_material.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 21 | 22 | 23 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ImageMarkerExample 3 | 4 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["kotlin_version"] 5 | 6 | ext { 7 | buildToolsVersion = "34.0.0" 8 | minSdkVersion = 21 9 | compileSdkVersion = 34 10 | targetSdkVersion = 34 11 | 12 | // We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP. 13 | ndkVersion = "25.1.8937393" 14 | kotlinVersion = kotlin_version 15 | } 16 | repositories { 17 | google() 18 | mavenCentral() 19 | jcenter() 20 | maven { url 'https://dl.google.com/dl/android/maven2' } 21 | maven { url "https://repository.jboss.org/maven2" } 22 | maven { url 'https://maven.google.com' } 23 | maven { url 'https://maven.fabric.io/public' } 24 | } 25 | dependencies { 26 | classpath("com.android.tools.build:gradle") 27 | classpath("com.facebook.react:react-native-gradle-plugin") 28 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") 29 | } 30 | } 31 | 32 | apply plugin: "com.facebook.react.rootproject" 33 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m 13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | # Automatically convert third-party libraries to use AndroidX 25 | android.enableJetifier=true 26 | 27 | # Use this property to specify which architecture you want to build. 28 | # You can also override it from the CLI using 29 | # ./gradlew -PreactNativeArchitectures=x86_64 30 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 31 | 32 | # Use this property to enable support to the new architecture. 33 | # This will allow you to use TurboModules and the Fabric render in 34 | # your application. You should enable this flag either if you want 35 | # to write custom TurboModules/Fabric components OR use libraries that 36 | # are providing them. 37 | newArchEnabled=false 38 | 39 | # Use this property to enable or disable the Hermes JS engine. 40 | # If set to false, you will be using JSC instead. 41 | hermesEnabled=true 42 | 43 | kotlin_version=1.8.0 44 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/example/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /example/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /example/android/link-assets-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "migIndex": 1, 3 | "data": [ 4 | { 5 | "path": "assets/fonts/MaShanZheng-Regular.ttf", 6 | "sha1": "1376fdd48ab29afd9599101e8991b773f12177e5" 7 | }, 8 | { 9 | "path": "assets/fonts/RubikBurned-Regular.ttf", 10 | "sha1": "546eccbc5a368bd10e6a4fe9570208428c16a036" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'ImageMarkerExample' 2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) 3 | include ':app' 4 | includeBuild('../node_modules/@react-native/gradle-plugin') -------------------------------------------------------------------------------- /example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ImageMarkerExample", 3 | "displayName": "ImageMarkerExample" 4 | } 5 | -------------------------------------------------------------------------------- /example/assets/fonts/MaShanZheng-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/example/assets/fonts/MaShanZheng-Regular.ttf -------------------------------------------------------------------------------- /example/assets/fonts/RubikBurned-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/example/assets/fonts/RubikBurned-Regular.ttf -------------------------------------------------------------------------------- /example/babel.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const pak = require('../package.json'); 3 | 4 | module.exports = { 5 | presets: ['module:@react-native/babel-preset'], 6 | plugins: [ 7 | [ 8 | 'module-resolver', 9 | { 10 | extensions: ['.tsx', '.ts', '.js', '.json'], 11 | alias: { 12 | [pak.name]: path.join(__dirname, '..', pak.source), 13 | }, 14 | }, 15 | ], 16 | ], 17 | }; 18 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | import { AppRegistry } from 'react-native'; 2 | import App from './src/App'; 3 | import { name as appName } from './app.json'; 4 | 5 | AppRegistry.registerComponent(appName, () => App); 6 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | Podfile.lock 2 | -------------------------------------------------------------------------------- /example/ios/.xcode.env: -------------------------------------------------------------------------------- 1 | # This `.xcode.env` file is versioned and is used to source the environment 2 | # used when running script phases inside Xcode. 3 | # To customize your local environment, you can create an `.xcode.env.local` 4 | # file that is not versioned. 5 | 6 | # NODE_BINARY variable contains the PATH to the node executable. 7 | # 8 | # Customize the NODE_BINARY variable here. 9 | # For example, to use nvm with brew, add the following line 10 | # . "$(brew --prefix nvm)/nvm.sh" --no-use 11 | export NODE_BINARY=$(command -v node) 12 | -------------------------------------------------------------------------------- /example/ios/File.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // ImageMarkerExample 4 | // 5 | 6 | import Foundation 7 | -------------------------------------------------------------------------------- /example/ios/ImageMarkerExample-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | -------------------------------------------------------------------------------- /example/ios/ImageMarkerExample.xcodeproj/xcshareddata/xcschemes/ImageMarkerExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 34 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 71 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /example/ios/ImageMarkerExample.xcodeproj/xcshareddata/xcschemes/ImageMarkerExampleUITests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 16 | 19 | 25 | 26 | 27 | 28 | 29 | 39 | 41 | 47 | 48 | 49 | 50 | 56 | 57 | 63 | 64 | 65 | 66 | 68 | 69 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /example/ios/ImageMarkerExample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/ImageMarkerExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/ImageMarkerExample/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : RCTAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/ios/ImageMarkerExample/AppDelegate.mm: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | 3 | #import 4 | 5 | @implementation AppDelegate 6 | 7 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 8 | { 9 | self.moduleName = @"ImageMarkerExample"; 10 | // You can add your custom initial props in the dictionary below. 11 | // They will be passed down to the ViewController used by React Native. 12 | self.initialProps = @{}; 13 | 14 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 15 | } 16 | 17 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 18 | { 19 | return [self getBundleURL]; 20 | } 21 | - (NSURL *)getBundleURL 22 | { 23 | #if DEBUG 24 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; 25 | #else 26 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 27 | #endif 28 | } 29 | 30 | @end 31 | -------------------------------------------------------------------------------- /example/ios/ImageMarkerExample/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ios-marketing", 45 | "scale" : "1x", 46 | "size" : "1024x1024" 47 | } 48 | ], 49 | "info" : { 50 | "author" : "xcode", 51 | "version" : 1 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /example/ios/ImageMarkerExample/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /example/ios/ImageMarkerExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ImageMarkerExample 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(MARKETING_VERSION) 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | $(CURRENT_PROJECT_VERSION) 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | NSAllowsLocalNetworking 32 | 33 | 34 | NSCameraUsageDescription 35 | want to use 36 | NSLocationWhenInUseUsageDescription 37 | 38 | NSPhotoLibraryUsageDescription 39 | want to use 40 | UIAppFonts 41 | 42 | RubikBurned-Regular.ttf 43 | MaShanZheng-Regular.ttf 44 | 45 | UILaunchStoryboardName 46 | LaunchScreen 47 | UIRequiredDeviceCapabilities 48 | 49 | armv7 50 | 51 | UISupportedInterfaceOrientations 52 | 53 | UIInterfaceOrientationPortrait 54 | UIInterfaceOrientationLandscapeLeft 55 | UIInterfaceOrientationLandscapeRight 56 | 57 | UIViewControllerBasedStatusBarAppearance 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /example/ios/ImageMarkerExample/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/ios/ImageMarkerExample/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | @autoreleasepool { 8 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /example/ios/ImageMarkerExampleUITests/ImageMarkerExampleUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageMarkerExampleUITests.swift 3 | // ImageMarkerExampleUITests 4 | // 5 | // Created by Jimmydaddy on 2023/11/28. 6 | // 7 | 8 | import XCTest 9 | 10 | final class ImageMarkerExampleUITests: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | 15 | // In UI tests it is usually best to stop immediately when a failure occurs. 16 | continueAfterFailure = false 17 | 18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 19 | } 20 | 21 | override func tearDownWithError() throws { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | func testApp() throws { 26 | // UI tests must launch the application that they test. 27 | let app = XCUIApplication() 28 | app.launch() 29 | sleep(5) 30 | var ele = app.textFields["100"] 31 | XCTAssert(ele.exists) 32 | ele = app.staticTexts["watermark type:"] 33 | XCTAssert(ele.exists) 34 | ele = app.staticTexts["background image format:"] 35 | XCTAssert(ele.exists) 36 | } 37 | 38 | func testLaunchPerformance() throws { 39 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { 40 | // This measures how long it takes to launch your application. 41 | measure(metrics: [XCTApplicationLaunchMetric()]) { 42 | XCUIApplication().launch() 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /example/ios/ImageMarkerExampleUITests/ImageMarkerExampleUITestsLaunchTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageMarkerExampleUITestsLaunchTests.swift 3 | // ImageMarkerExampleUITests 4 | // 5 | // Created by Jimmydaddy on 2023/11/28. 6 | // 7 | 8 | import XCTest 9 | 10 | final class ImageMarkerExampleUITestsLaunchTests: XCTestCase { 11 | 12 | override class var runsForEachTargetApplicationUIConfiguration: Bool { 13 | true 14 | } 15 | 16 | override func setUpWithError() throws { 17 | continueAfterFailure = false 18 | } 19 | 20 | func testLaunch() throws { 21 | let app = XCUIApplication() 22 | app.launch() 23 | 24 | // Insert steps here to perform after app launch but before taking a screenshot, 25 | // such as logging into a test account or navigating somewhere in the app 26 | 27 | let attachment = XCTAttachment(screenshot: app.screenshot()) 28 | attachment.name = "Launch Screen" 29 | attachment.lifetime = .keepAlways 30 | add(attachment) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Resolve react_native_pods.rb with node to allow for hoisting 2 | require Pod::Executable.execute_command('node', ['-p', 3 | 'require.resolve( 4 | "react-native/scripts/react_native_pods.rb", 5 | {paths: [process.argv[1]]}, 6 | )', __dir__]).strip 7 | 8 | platform :ios, min_ios_version_supported 9 | prepare_react_native_project! 10 | 11 | # If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set. 12 | # because `react-native-flipper` depends on (FlipperKit,...) that will be excluded 13 | # 14 | # To fix this you can also exclude `react-native-flipper` using a `react-native.config.js` 15 | # ```js 16 | # module.exports = { 17 | # dependencies: { 18 | # ...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}), 19 | # ``` 20 | flipper_config = ENV['NO_FLIPPER'] == "1" ? FlipperConfiguration.disabled : FlipperConfiguration.enabled 21 | 22 | linkage = ENV['USE_FRAMEWORKS'] 23 | if linkage != nil 24 | Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green 25 | use_frameworks! :linkage => linkage.to_sym 26 | end 27 | 28 | target 'ImageMarkerExample' do 29 | config = use_native_modules! 30 | 31 | use_react_native!( 32 | :path => config[:reactNativePath], 33 | # Enables Flipper. 34 | # 35 | # Note that if you have use_frameworks! enabled, Flipper will not work and 36 | # you should disable the next line. 37 | :flipper_configuration => flipper_config, 38 | # An absolute path to your application root. 39 | :app_path => "#{Pod::Config.instance.installation_root}/.." 40 | ) 41 | 42 | target 'ImageMarkerExampleUITests' do 43 | inherit! :complete 44 | # Pods for testing 45 | end 46 | 47 | post_install do |installer| 48 | # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 49 | react_native_post_install( 50 | installer, 51 | config[:reactNativePath], 52 | :mac_catalyst_enabled => false 53 | ) 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /example/ios/link-assets-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "migIndex": 1, 3 | "data": [ 4 | { 5 | "path": "assets/fonts/MaShanZheng-Regular.ttf", 6 | "sha1": "1376fdd48ab29afd9599101e8991b773f12177e5" 7 | }, 8 | { 9 | "path": "assets/fonts/RubikBurned-Regular.ttf", 10 | "sha1": "546eccbc5a368bd10e6a4fe9570208428c16a036" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/scripts.sh: -------------------------------------------------------------------------------- 1 | export RCT_METRO_PORT="${RCT_METRO_PORT:=8081}" 2 | echo "export RCT_METRO_PORT=${RCT_METRO_PORT}" > "${SRCROOT}/../node_modules/react-native/scripts/.packager.env" 3 | if [ -z "${RCT_NO_LAUNCH_PACKAGER+xxx}" ] ; then 4 | if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then 5 | if ! curl -s "http://localhost:${RCT_METRO_PORT}/status" | grep -q "packager-status:running" ; then 6 | echo "Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly" 7 | exit 2 8 | fi 9 | else 10 | open "$SRCROOT/../node_modules/react-native/scripts/launchPackager.command" || echo "Can't start packager automatically" 11 | fi 12 | fi 13 | -------------------------------------------------------------------------------- /example/metro.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const escape = require('escape-string-regexp'); 3 | const exclusionList = require('metro-config/src/defaults/exclusionList'); 4 | const pak = require('../package.json'); 5 | const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); 6 | 7 | const root = path.resolve(__dirname, '..'); 8 | 9 | const modules = Object.keys({ 10 | ...pak.peerDependencies, 11 | }); 12 | 13 | const config = { 14 | projectRoot: __dirname, 15 | watchFolders: [root], 16 | 17 | // We need to make sure that only one version is loaded for peerDependencies 18 | // So we block them at the root, and alias them to the versions in example's node_modules 19 | resolver: { 20 | blacklistRE: exclusionList( 21 | modules.map( 22 | (m) => 23 | new RegExp(`^${escape(path.join(root, 'node_modules', m))}\\/.*$`) 24 | ) 25 | ), 26 | 27 | extraNodeModules: modules.reduce((acc, name) => { 28 | acc[name] = path.join(__dirname, 'node_modules', name); 29 | return acc; 30 | }, {}), 31 | }, 32 | 33 | transformer: { 34 | getTransformOptions: async () => ({ 35 | transform: { 36 | experimentalImportSupport: false, 37 | inlineRequires: true, 38 | }, 39 | }), 40 | }, 41 | }; 42 | 43 | module.exports = mergeConfig(getDefaultConfig(__dirname), config); 44 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ImageMarkerExample", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "android": "react-native run-android", 7 | "ios": "react-native run-ios", 8 | "start": "react-native start", 9 | "pods": "pod-install", 10 | "m1-pods": "cd ios && arch -x86_64 pod install" 11 | }, 12 | "dependencies": { 13 | "@expo/react-native-action-sheet": "^4.0.1", 14 | "filesize": "^10.1.0", 15 | "react": "18.2.0", 16 | "react-native": "0.73.3", 17 | "react-native-blob-util": "^0.19.2", 18 | "react-native-image-picker": "^5.6.0", 19 | "react-native-reanimated-table": "^0.0.2", 20 | "react-native-toast-message": "^2.1.6" 21 | }, 22 | "devDependencies": { 23 | "@babel/core": "^7.20.0", 24 | "@babel/preset-env": "^7.20.0", 25 | "@babel/runtime": "^7.20.0", 26 | "@react-native/babel-preset": "^0.73.18", 27 | "@react-native/eslint-config": "^0.73.2", 28 | "@react-native/metro-config": "^0.73.2", 29 | "@react-native/typescript-config": "^0.73.1", 30 | "@types/react": "^18.2.6", 31 | "babel-plugin-module-resolver": "^4.1.0", 32 | "prettier": "2.8.8", 33 | "typescript": "5.0.4", 34 | "postinstall-postinstall": "^2.1.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example/react-native.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const pak = require('../package.json'); 3 | 4 | module.exports = { 5 | dependencies: { 6 | [pak.name]: { 7 | root: path.join(__dirname, '..'), 8 | }, 9 | }, 10 | assets: ['./assets/fonts'], 11 | }; 12 | -------------------------------------------------------------------------------- /example/src/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/example/src/bg.png -------------------------------------------------------------------------------- /example/src/icon.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/example/src/icon.jpeg -------------------------------------------------------------------------------- /example/src/yahaha.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/example/src/yahaha.jpeg -------------------------------------------------------------------------------- /expo-example/.gitignore: -------------------------------------------------------------------------------- 1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # Expo 7 | .expo/ 8 | dist/ 9 | web-build/ 10 | 11 | # Native 12 | *.orig.* 13 | *.jks 14 | *.p8 15 | *.p12 16 | *.key 17 | *.mobileprovision 18 | 19 | # Metro 20 | .metro-health-check* 21 | 22 | # debug 23 | npm-debug.* 24 | yarn-debug.* 25 | yarn-error.* 26 | 27 | # macOS 28 | .DS_Store 29 | *.pem 30 | 31 | # local env files 32 | .env*.local 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | 37 | #expo 38 | android 39 | ios 40 | -------------------------------------------------------------------------------- /expo-example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "expo-example", 4 | "slug": "expo-example", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "userInterfaceStyle": "light", 9 | "splash": { 10 | "image": "./assets/splash.png", 11 | "resizeMode": "contain", 12 | "backgroundColor": "#ffffff" 13 | }, 14 | "assetBundlePatterns": [ 15 | "**/*" 16 | ], 17 | "ios": { 18 | "supportsTablet": true, 19 | "bundleIdentifier": "com.jimmydaddy.expoexample" 20 | }, 21 | "android": { 22 | "adaptiveIcon": { 23 | "foregroundImage": "./assets/adaptive-icon.png", 24 | "backgroundColor": "#ffffff" 25 | }, 26 | "permissions": [ 27 | "android.permission.RECORD_AUDIO" 28 | ], 29 | "package": "com.jimmydaddy.expoexample" 30 | }, 31 | "web": { 32 | "favicon": "./assets/favicon.png" 33 | }, 34 | "plugins": [ 35 | "react-native-image-marker", 36 | [ 37 | "expo-font", 38 | { 39 | "fonts": [ 40 | "../example/assets/fonts/MaShanZheng-Regular.ttf", 41 | "../example/assets/fonts/RubikBurned-Regular.ttf" 42 | ] 43 | } 44 | ], 45 | [ 46 | "expo-image-picker", 47 | { 48 | "photosPermission": "The app accesses your photos to let you share them with your friends." 49 | } 50 | ] 51 | ], 52 | "extra": { 53 | "eas": { 54 | "projectId": "1820e3ee-c630-4a0c-9e8b-84ff586ee097" 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /expo-example/assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/expo-example/assets/adaptive-icon.png -------------------------------------------------------------------------------- /expo-example/assets/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/expo-example/assets/bg.png -------------------------------------------------------------------------------- /expo-example/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/expo-example/assets/favicon.png -------------------------------------------------------------------------------- /expo-example/assets/icon.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/expo-example/assets/icon.jpeg -------------------------------------------------------------------------------- /expo-example/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/expo-example/assets/icon.png -------------------------------------------------------------------------------- /expo-example/assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/expo-example/assets/splash.png -------------------------------------------------------------------------------- /expo-example/assets/yahaha.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JimmyDaddy/react-native-image-marker/d6e8744a6cb097d19a4c4d0368664eb960acc76a/expo-example/assets/yahaha.jpeg -------------------------------------------------------------------------------- /expo-example/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /expo-example/eas.json: -------------------------------------------------------------------------------- 1 | { 2 | "cli": { 3 | "version": ">= 7.1.0" 4 | }, 5 | "build": { 6 | "development": { 7 | "developmentClient": true, 8 | "distribution": "internal" 9 | }, 10 | "preview": { 11 | "distribution": "internal" 12 | }, 13 | "production": {} 14 | }, 15 | "submit": { 16 | "production": {} 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /expo-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "expo-example", 3 | "version": "1.0.0", 4 | "main": "node_modules/expo/AppEntry.js", 5 | "scripts": { 6 | "start": "expo start", 7 | "android": "expo run:android", 8 | "ios": "expo run:ios", 9 | "web": "expo start --web", 10 | "expo-install": "npx expo install" 11 | }, 12 | "dependencies": { 13 | "@expo/react-native-action-sheet": "^4.0.1", 14 | "expo": "~50.0.3", 15 | "expo-file-system": "~16.0.5", 16 | "expo-font": "~11.10.2", 17 | "expo-image-manipulator": "~11.8.0", 18 | "expo-image-picker": "~14.7.1", 19 | "expo-status-bar": "~1.11.1", 20 | "filesize": "^10.1.0", 21 | "react": "18.2.0", 22 | "react-native": "0.73.2", 23 | "react-native-image-marker": "*", 24 | "react-native-toast-message": "^2.2.0" 25 | }, 26 | "devDependencies": { 27 | "@babel/core": "^7.20.0", 28 | "@types/react": "~18.2.45", 29 | "typescript": "^5.1.3" 30 | }, 31 | "private": true 32 | } 33 | -------------------------------------------------------------------------------- /expo-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "expo/tsconfig.base", 3 | "compilerOptions": { 4 | "strict": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | require_relative '../node_modules/react-native/scripts/react_native_pods' 2 | require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' 3 | 4 | platform :ios, min_ios_version_supported 5 | prepare_react_native_project! 6 | 7 | # If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set. 8 | # because `react-native-flipper` depends on (FlipperKit,...) that will be excluded 9 | # 10 | # To fix this you can also exclude `react-native-flipper` using a `react-native.config.js` 11 | # ```js 12 | # module.exports = { 13 | # dependencies: { 14 | # ...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}), 15 | # ``` 16 | flipper_config = ENV['NO_FLIPPER'] == "1" ? FlipperConfiguration.disabled : FlipperConfiguration.enabled 17 | 18 | linkage = ENV['USE_FRAMEWORKS'] 19 | if linkage != nil 20 | Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green 21 | use_frameworks! :linkage => linkage.to_sym 22 | end 23 | 24 | target 'ImageMarkerExample' do 25 | config = use_native_modules! 26 | 27 | # Flags change depending on the env values. 28 | flags = get_default_flags() 29 | 30 | use_react_native!( 31 | :path => config[:reactNativePath], 32 | # Hermes is now enabled by default. Disable by setting this flag to false. 33 | # Upcoming versions of React Native may rely on get_default_flags(), but 34 | # we make it explicit here to aid in the React Native upgrade process. 35 | :hermes_enabled => flags[:hermes_enabled], 36 | :fabric_enabled => flags[:fabric_enabled], 37 | # Enables Flipper. 38 | # 39 | # Note that if you have use_frameworks! enabled, Flipper will not work and 40 | # you should disable the next line. 41 | :flipper_configuration => flipper_config, 42 | # An absolute path to your application root. 43 | :app_path => "#{Pod::Config.instance.installation_root}/.." 44 | ) 45 | 46 | target 'ImageMarkerExampleUITests' do 47 | inherit! :complete 48 | # Pods for testing 49 | end 50 | 51 | post_install do |installer| 52 | react_native_post_install( 53 | installer, 54 | # Set `mac_catalyst_enabled` to `true` in order to apply patches 55 | # necessary for Mac Catalyst builds 56 | :mac_catalyst_enabled => false 57 | ) 58 | __apply_Xcode_12_5_M1_post_install_workaround(installer) 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /ios/RCTImageMarker/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // react-native-image-marker 4 | // 5 | // Created by Jimmydaddy on 2023/8/10. 6 | // 7 | 8 | enum ErrorDomainEnum: String { 9 | case PARAMS_REQUIRED = "com.jimmydaddy.imagemarker.PARAMS_REQUIRED" 10 | case PARAMS_INVALID = "com.jimmydaddy.imagemarker.PARAMS_INVALID" 11 | case BASE = "com.jimmydaddy.imagemarker" 12 | } 13 | -------------------------------------------------------------------------------- /ios/RCTImageMarker/CornerRadius.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CornerRadius.swift 3 | // react-native-image-marker 4 | // 5 | // Created by Jimmydaddy on 2023/9/28. 6 | // 7 | 8 | import Foundation 9 | import React 10 | 11 | class CornerRadius: NSObject { 12 | var topLeft: Radius? 13 | var topRight: Radius? 14 | var bottomLeft: Radius? 15 | var bottomRight: Radius? 16 | var all: Radius? 17 | 18 | init(dicOpts opts: [AnyHashable: Any]) throws { 19 | 20 | for (key, cornerRadius) in opts { 21 | switch key { 22 | case "topLeft" as String: 23 | if Utils.isNULL(cornerRadius) { break; } 24 | self.topLeft = try Radius(dicOpts: cornerRadius as! [AnyHashable : Any]) 25 | case "topRight" as String: 26 | if Utils.isNULL(cornerRadius) { break; } 27 | self.topRight = try Radius(dicOpts: cornerRadius as! [AnyHashable : Any]) 28 | case "bottomLeft" as String: 29 | if Utils.isNULL(cornerRadius) { break; } 30 | self.bottomLeft = try Radius(dicOpts: cornerRadius as! [AnyHashable : Any]) 31 | case "bottomRight" as String: 32 | if Utils.isNULL(cornerRadius) { break; } 33 | self.bottomRight = try Radius(dicOpts: cornerRadius as! [AnyHashable : Any]) 34 | default: 35 | if Utils.isNULL(cornerRadius) { break; } 36 | all = try Radius(dicOpts: cornerRadius as! [AnyHashable : Any]) 37 | } 38 | } 39 | } 40 | 41 | func radiusPath(rect: CGRect) -> UIBezierPath { 42 | let path = UIBezierPath() 43 | let cornerRadii = self.all?.radii(rect: rect) ?? CGSize(width: 0, height: 0) 44 | let topLeftRadii = (self.topLeft != nil) ? self.topLeft!.radii(rect: rect) : cornerRadii 45 | let topRightRadii = (self.topRight != nil) ? self.topRight!.radii(rect: rect) : cornerRadii 46 | let bottomRightRadii = (self.bottomRight != nil) ? self.bottomRight!.radii(rect: rect) : cornerRadii 47 | let bottomLeftRadii = (self.bottomLeft != nil) ? self.bottomLeft!.radii(rect: rect) : cornerRadii 48 | 49 | let topLeft = CGPoint(x: rect.minX, y: rect.minY + topLeftRadii.height / 4) 50 | let topRight = CGPoint(x: rect.maxX, y: rect.minY + topRightRadii.height / 4) 51 | let bottomRight = CGPoint(x: rect.maxX, y: rect.maxY - bottomRightRadii.height / 4) 52 | let bottomLeft = CGPoint(x: rect.minX, y: rect.maxY - bottomLeftRadii.height / 4) 53 | path.move(to: CGPoint(x: rect.minX, y: rect.minY + topLeftRadii.height)) 54 | path.addQuadCurve(to: CGPoint(x: rect.minX + topLeftRadii.width, y: rect.minY), controlPoint: topLeft) 55 | path.addLine(to: CGPoint(x: rect.maxX - topRightRadii.width, y: rect.minY)) 56 | path.addQuadCurve(to: CGPoint(x: rect.maxX, y: rect.minY + topRightRadii.height), controlPoint: topRight) 57 | path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - bottomRightRadii.height)) 58 | path.addQuadCurve(to: CGPoint(x: rect.maxX - bottomRightRadii.width, y: rect.maxY), controlPoint: bottomRight) 59 | path.addLine(to: CGPoint(x: rect.minX + bottomLeftRadii.width, y: rect.maxY)) 60 | path.addQuadCurve(to: CGPoint(x: rect.minX, y: rect.maxY - bottomLeftRadii.height), controlPoint: bottomLeft) 61 | path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + topLeftRadii.height)) 62 | path.close() 63 | return path 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /ios/RCTImageMarker/ImageOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ImageOptions.swift 3 | // react-native-image-marker 4 | // 5 | // Created by Jimmydaddy on 2023/6/25. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | class ImageOptions: NSObject { 12 | var src: [AnyHashable: Any] 13 | var uri: String 14 | var scale: CGFloat = 1.0 15 | var rotate: CGFloat = 0 16 | var alpha: CGFloat = 1.0 17 | var rnSrc: RNImageSRC 18 | 19 | init(dicOpts opts: [AnyHashable: Any]) throws { 20 | guard let src = opts["src"] as? [AnyHashable: Any], !Utils.isNULL(src) else { 21 | throw NSError(domain: ErrorDomainEnum.PARAMS_REQUIRED.rawValue, code: 0, userInfo: [NSLocalizedDescriptionKey: "image is required"]) 22 | } 23 | self.src = src 24 | self.rnSrc = RNImageSRC(dicOpts: src) 25 | self.uri = src["uri"] as! String 26 | self.scale = opts["scale"] as? CGFloat ?? 1.0 27 | self.rotate = opts["rotate"] as? CGFloat ?? 0 28 | self.alpha = opts["alpha"] as? CGFloat ?? 1.0 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ios/RCTImageMarker/MarkImageOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MarkImageOptions.swift 3 | // react-native-image-marker 4 | // 5 | // Created by Jimmydaddy on 2023/6/22. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | import React 11 | 12 | class MarkImageOptions: Options { 13 | var watermarkImages: [WatermarkImageOptions] = [] 14 | 15 | override init(dicOpts opts: [AnyHashable: Any]) throws { 16 | try super.init(dicOpts: opts) 17 | let watermarkImageOpts = opts["watermarkImage"] 18 | let watermarkImagesOpts = opts["watermarkImages"] as? [[AnyHashable: Any]] 19 | if Utils.isNULL(watermarkImageOpts) && (Utils.isNULL(watermarkImagesOpts) || watermarkImagesOpts!.count <= 0) { 20 | throw NSError(domain: ErrorDomainEnum.PARAMS_REQUIRED.rawValue, code: 0, userInfo: [NSLocalizedDescriptionKey: "marker image is required"]) 21 | } 22 | if watermarkImagesOpts!.count > 0 { 23 | self.watermarkImages = try watermarkImagesOpts!.map { try WatermarkImageOptions(dicOpts: $0) } 24 | } 25 | if (!Utils.isNULL(watermarkImageOpts)){ 26 | let singleImageOptions = try ImageOptions(dicOpts: watermarkImageOpts as! [AnyHashable : Any]) 27 | var singleX = "20.0" 28 | var singleY = "20.0" 29 | var singlePosition: MarkerPositionEnum = .none 30 | let singleImagePositionOpts = opts["watermarkPositions"] as? [AnyHashable: Any] 31 | if let positionOpts = singleImagePositionOpts, !Utils.isNULL(singleImagePositionOpts) { 32 | singleX = Utils.handleDynamicToString(v: positionOpts["X"]) 33 | singleY = Utils.handleDynamicToString(v: positionOpts["Y"]) 34 | singlePosition = positionOpts["position"] != nil ? RCTConvert.MarkerPosition(positionOpts["position"]) : .none 35 | } 36 | let watermarkImageOptions = WatermarkImageOptions(watermarkImage: singleImageOptions, X: singleX, Y: singleY, position: singlePosition) 37 | self.watermarkImages.append(watermarkImageOptions) 38 | } 39 | } 40 | 41 | static func checkImageParams(_ opts: [AnyHashable: Any], rejecter reject: @escaping RCTPromiseRejectBlock) -> MarkImageOptions? { 42 | do { 43 | return try MarkImageOptions(dicOpts: opts) 44 | } catch let error as NSError { 45 | reject(error.domain, error.localizedDescription, nil) 46 | return nil 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ios/RCTImageMarker/MarkPosition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MarkPosition.swift 3 | // react-native-image-marker 4 | // 5 | // Created by Jimmydaddy on 2023/7/13. 6 | // 7 | 8 | enum MarkerPositionEnum: NSString { 9 | case topLeft = "topLeft" 10 | case topCenter = "topCenter" 11 | case topRight = "topRight" 12 | case bottomLeft = "bottomLeft" 13 | case bottomCenter = "bottomCenter" 14 | case bottomRight = "bottomRight" 15 | case center = "center" 16 | case none = "none" 17 | } 18 | -------------------------------------------------------------------------------- /ios/RCTImageMarker/MarkTextOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MarkTextOptions.swift 3 | // RCTImageMarker 4 | // 5 | // Created by Jimmydaddy on 2023/6/22. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | class MarkTextOptions: Options { 12 | var watermarkTexts: [TextOptions] = [] 13 | 14 | override init(dicOpts opts: [AnyHashable: Any]) throws { 15 | try super.init(dicOpts: opts) 16 | guard let watermarkTextsOpts = opts["watermarkTexts"] as? [[AnyHashable: Any]], watermarkTextsOpts.count > 0 else { 17 | throw NSError(domain: ErrorDomainEnum.PARAMS_REQUIRED.rawValue, code: 0, userInfo: [NSLocalizedDescriptionKey: "text is required"]) 18 | } 19 | self.watermarkTexts = try watermarkTextsOpts.map { try TextOptions(dicOpts: $0) } 20 | } 21 | 22 | static func checkTextParams(_ opts: [AnyHashable: Any], rejecter reject: @escaping RCTPromiseRejectBlock) -> MarkTextOptions? { 23 | do { 24 | return try MarkTextOptions(dicOpts: opts) 25 | } catch let error as NSError { 26 | reject(error.domain, error.localizedDescription, nil) 27 | return nil 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ios/RCTImageMarker/Options.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Options.swift 3 | // RCTImageMarker 4 | // 5 | // Created by Jimmydaddy on 2023/6/22. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | 11 | class Options: NSObject { 12 | var backgroundImage: ImageOptions 13 | var quality: Int = 100 14 | var saveFormat: String? 15 | var maxSize: Int? 16 | var filename: String? 17 | 18 | init(dicOpts opts: [AnyHashable: Any]) throws { 19 | guard let backgroundImageOpts = opts["backgroundImage"] as? [AnyHashable: Any], !Utils.isNULL(backgroundImageOpts) else { 20 | throw NSError(domain: ErrorDomainEnum.PARAMS_REQUIRED.rawValue, code: 0, userInfo: [NSLocalizedDescriptionKey: "backgroundImage is required"]) 21 | } 22 | self.backgroundImage = try ImageOptions(dicOpts: backgroundImageOpts) 23 | self.quality = opts["quality"] as? Int ?? 100 24 | self.saveFormat = opts["saveFormat"] as? String 25 | self.maxSize = opts["maxSize"] as? Int 26 | self.filename = opts["fileName"] as? String 27 | } 28 | 29 | static func checkParams(_ opts: [AnyHashable: Any], rejecter reject: @escaping RCTPromiseRejectBlock) -> Options? { 30 | do { 31 | return try Options(dicOpts: opts) 32 | } catch let error as NSError { 33 | reject(error.domain, error.localizedDescription, nil) 34 | return nil 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ios/RCTImageMarker/Padding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Padding.swift 3 | // react-native-image-marker 4 | // 5 | // Created by Jimmydaddy on 2023/8/9. 6 | // 7 | 8 | import Foundation 9 | 10 | class Padding { 11 | var paddingTop: String = "0" 12 | var paddingLeft: String = "0" 13 | var paddingBottom: String = "0" 14 | var paddingRight: String = "0" 15 | 16 | init(paddingData: [AnyHashable: Any]) throws { 17 | var topValue: String = "0" 18 | var leftValue: String = "0" 19 | var bottomValue: String = "0" 20 | var rightValue: String = "0" 21 | 22 | for (key, paddingValue) in paddingData { 23 | switch key { 24 | case "padding" as String: 25 | if var paddingValue = paddingValue as? String { 26 | paddingValue = paddingValue.trimmingCharacters(in: .whitespaces) 27 | if !Utils.checkSpreadValue(str: paddingValue, maxLength: 4) { 28 | throw NSError(domain: ErrorDomainEnum.PARAMS_INVALID.rawValue, code: 0, userInfo: [NSLocalizedDescriptionKey: "padding is invalid"]) 29 | } 30 | let values = paddingValue.components(separatedBy: " ") 31 | if values.count == 1 { 32 | topValue = values[0] 33 | leftValue = values[0] 34 | bottomValue = values[0] 35 | rightValue = values[0] 36 | } else if values.count == 2 { 37 | topValue = values[0] 38 | leftValue = values[1] 39 | bottomValue = values[0] 40 | rightValue = values[1] 41 | } else if values.count == 3 { 42 | topValue = values[0] 43 | leftValue = values[1] 44 | bottomValue = values[2] 45 | rightValue = values[1] 46 | } else if values.count == 4 { 47 | topValue = values[0] 48 | leftValue = values[1] 49 | bottomValue = values[2] 50 | rightValue = values[3] 51 | } 52 | break 53 | } else if let paddingValue = paddingValue as? CGFloat { 54 | topValue = String(format: "%f", paddingValue) 55 | leftValue = String(format: "%f", paddingValue) 56 | bottomValue = String(format: "%f", paddingValue) 57 | rightValue = String(format: "%f", paddingValue) 58 | } 59 | case "paddingLeft" as String: 60 | if let paddingValue = paddingValue as? String { 61 | if !Utils.checkSpreadValue(str: paddingValue, maxLength: 1) { 62 | throw NSError(domain: ErrorDomainEnum.PARAMS_INVALID.rawValue, code: 0, userInfo: [NSLocalizedDescriptionKey: "padding is invalid"]) 63 | } 64 | leftValue = paddingValue; 65 | } else if let paddingValue = paddingValue as? CGFloat { 66 | leftValue = String(format: "%f", paddingValue) 67 | } 68 | case "paddingRight" as String: 69 | if let paddingValue = paddingValue as? String { 70 | if !Utils.checkSpreadValue(str: paddingValue, maxLength: 1) { 71 | throw NSError(domain: ErrorDomainEnum.PARAMS_INVALID.rawValue, code: 0, userInfo: [NSLocalizedDescriptionKey: "padding is invalid"]) 72 | } 73 | rightValue = paddingValue; 74 | } else if let paddingValue = paddingValue as? CGFloat { 75 | rightValue = String(format: "%f", paddingValue) 76 | } 77 | case "paddingTop" as String: 78 | if let paddingValue = paddingValue as? String { 79 | if !Utils.checkSpreadValue(str: paddingValue, maxLength: 1) { 80 | throw NSError(domain: ErrorDomainEnum.PARAMS_INVALID.rawValue, code: 0, userInfo: [NSLocalizedDescriptionKey: "padding is invalid"]) 81 | } 82 | topValue = paddingValue; 83 | } else if let paddingValue = paddingValue as? CGFloat { 84 | topValue = String(format: "%f", paddingValue) 85 | } 86 | case "paddingBottom" as String: 87 | if let paddingValue = paddingValue as? String { 88 | if !Utils.checkSpreadValue(str: paddingValue, maxLength: 1) { 89 | throw NSError(domain: ErrorDomainEnum.PARAMS_INVALID.rawValue, code: 0, userInfo: [NSLocalizedDescriptionKey: "padding is invalid"]) 90 | } 91 | bottomValue = paddingValue; 92 | } else if let paddingValue = paddingValue as? CGFloat { 93 | bottomValue = String(format: "%f", paddingValue) 94 | } 95 | case "paddingHorizontal" as String, "paddingX" as String: 96 | if let paddingValue = paddingValue as? String { 97 | if !Utils.checkSpreadValue(str: paddingValue, maxLength: 1) { 98 | throw NSError(domain: ErrorDomainEnum.PARAMS_INVALID.rawValue, code: 0, userInfo: [NSLocalizedDescriptionKey: "padding is invalid"]) 99 | } 100 | rightValue = paddingValue; 101 | leftValue = paddingValue; 102 | } else if let paddingValue = paddingValue as? CGFloat { 103 | leftValue = String(format: "%f", paddingValue) 104 | rightValue = String(format: "%f", paddingValue) 105 | } 106 | case "paddingVertical" as String, "paddingY" as String: 107 | if let paddingValue = paddingValue as? String { 108 | if !Utils.checkSpreadValue(str: paddingValue, maxLength: 1) { 109 | throw NSError(domain: ErrorDomainEnum.PARAMS_INVALID.rawValue, code: 0, userInfo: [NSLocalizedDescriptionKey: "padding is invalid"]) 110 | } 111 | topValue = paddingValue; 112 | bottomValue = paddingValue; 113 | } else if let paddingValue = paddingValue as? CGFloat { 114 | topValue = String(format: "%f", paddingValue) 115 | bottomValue = String(format: "%f", paddingValue) 116 | } 117 | default: 118 | break 119 | } 120 | } 121 | 122 | self.paddingTop = topValue 123 | self.paddingLeft = leftValue 124 | self.paddingBottom = bottomValue 125 | self.paddingRight = rightValue 126 | } 127 | 128 | func toEdgeInsets(width: CGFloat, height: CGFloat) -> UIEdgeInsets { 129 | let topValue = Utils.parseSpreadValue(v: self.paddingTop, relativeTo: height) ?? 0 130 | let leftValue = Utils.parseSpreadValue(v: self.paddingLeft, relativeTo: width) ?? 0 131 | let bottomValue = Utils.parseSpreadValue(v: self.paddingBottom, relativeTo: height) ?? 0 132 | let rightValue = Utils.parseSpreadValue(v: self.paddingRight, relativeTo: width) ?? 0 133 | return UIEdgeInsets(top: topValue, left: leftValue, bottom: bottomValue, right: rightValue) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /ios/RCTImageMarker/RCTConvert+ImageMarker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RCTConvert+ImageMarker.swift 3 | // RCTImageMarker 4 | // 5 | // Created by Jimmy on 16/7/19. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | import CoreFoundation 11 | import React 12 | import CoreGraphics 13 | 14 | extension RCTConvert { 15 | static func CGSize(_ json: Any, offset: Int) -> CGSize { 16 | let arr = self.nsArray(json) 17 | if arr!.count < offset + 2 { 18 | NSLog("Too few elements in array (expected at least %zd): %@", 2 + offset, arr!) 19 | return CoreGraphics.CGSize.zero 20 | } 21 | return CoreGraphics.CGSize(width: self.cgFloat(arr![offset]), height: self.cgFloat(arr![offset + 1])) 22 | } 23 | 24 | static func CGPoint(_ json: Any, offset: Int) -> CGPoint { 25 | let arr = self.nsArray(json) 26 | if arr!.count < offset + 2 { 27 | NSLog("Too few elements in array (expected at least %zd): %@", 2 + offset, arr!) 28 | return CoreGraphics.CGPoint.zero 29 | } 30 | return CoreGraphics.CGPoint(x: self.cgFloat(arr?[offset]), y: self.cgFloat(arr![offset + 1])) 31 | } 32 | 33 | static func CGRect(_ json: Any, offset: Int) -> CGRect { 34 | let arr = self.nsArray(json) 35 | if arr!.count < offset + 4 { 36 | NSLog("Too few elements in array (expected at least %zd): %@", 4 + offset, arr!) 37 | return CoreGraphics.CGRect.zero 38 | } 39 | return CoreGraphics.CGRect(x: self.cgFloat(arr![offset]), y: self.cgFloat(arr![offset + 1]), width: self.cgFloat(arr![offset + 2]), height: self.cgFloat(arr![offset + 3])) 40 | } 41 | 42 | static func CGColor(_ json: Any, offset: Int) -> CGColor? { 43 | let arr = self.nsArray(json) 44 | if arr!.count < offset + 4 { 45 | NSLog("Too few elements in array (expected at least %zd): %@", 4 + offset, arr!) 46 | return nil 47 | } 48 | return self.cgColor(arr?[offset..<4]) 49 | } 50 | 51 | static func MarkerPosition(_ value: Any?) -> MarkerPositionEnum { 52 | let MyEnumMap: [String: MarkerPositionEnum] = [ 53 | "topLeft": MarkerPositionEnum.topLeft, 54 | "topRight": MarkerPositionEnum.topRight, 55 | "topCenter": MarkerPositionEnum.topCenter, 56 | "center": MarkerPositionEnum.center, 57 | "bottomCenter": MarkerPositionEnum.bottomCenter, 58 | "bottomLeft": MarkerPositionEnum.bottomLeft, 59 | "bottomRight": MarkerPositionEnum.bottomRight, 60 | ] 61 | guard let value = value as? String, let mv = MyEnumMap[value] else { 62 | return MarkerPositionEnum.topLeft 63 | } 64 | return mv 65 | } 66 | 67 | static func UIRectCorner(_ value: [Any] = []) -> UIRectCorner { 68 | let MyEnumMap: [String: UIRectCorner] = [ 69 | "topLeft": .topLeft, 70 | "topRight": .topRight, 71 | "bottomLeft": .bottomLeft, 72 | "bottomRight": .bottomRight, 73 | "all": .allCorners, 74 | ] 75 | if value.isEmpty { return [.allCorners] } 76 | var corners: UIRectCorner = [] 77 | for item in value { 78 | if let corner = item as? String, let rectCorner = MyEnumMap[corner] { 79 | corners.insert(rectCorner) 80 | } 81 | } 82 | if corners.isEmpty { return [.allCorners] } 83 | return corners 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /ios/RCTImageMarker/RCTImageMarkerBridge.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSObject+RCTImageMarker.m 3 | // react-native-image-marker 4 | // 5 | // Created by Jimmydaddy on 2023/7/13. 6 | // 7 | 8 | #import 9 | 10 | @interface RCT_EXTERN_MODULE(ImageMarker, NSObject) 11 | 12 | RCT_EXTERN_METHOD(markWithImage: (nonnull NSDictionary *) opts 13 | resolver:(RCTPromiseResolveBlock)resolve 14 | rejecter:(RCTPromiseRejectBlock)reject) 15 | RCT_EXTERN_METHOD(markWithText: (nonnull NSDictionary *) opts 16 | resolver:(RCTPromiseResolveBlock)resolve 17 | rejecter:(RCTPromiseRejectBlock)reject) 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /ios/RCTImageMarker/RNImageSRC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RNImageSRC.swift 3 | // react-native-image-marker 4 | // 5 | // Created by Jimmydaddy on 2023/12/21. 6 | // 7 | 8 | import Foundation 9 | 10 | struct RNImageSRC { 11 | var width: CGFloat = 0 12 | var height: CGFloat = 0 13 | var scale: CGFloat = 1 14 | var uri: String = "" 15 | 16 | init(dicOpts opts: [AnyHashable: Any]?) { 17 | width = opts?["width"] as? CGFloat ?? 0 18 | height = opts?["height"] as? CGFloat ?? 0 19 | scale = opts?["scale"] as? CGFloat ?? 1 20 | uri = opts?["uri"] as? String ?? "" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ios/RCTImageMarker/Radius.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Radius.swift 3 | // react-native-image-marker 4 | // 5 | // Created by Jimmydaddy on 2023/9/28. 6 | // 7 | 8 | import Foundation 9 | import React 10 | 11 | class Radius: NSObject { 12 | var x: String = "0" 13 | var y: String = "0" 14 | 15 | init(dicOpts opts: [AnyHashable: Any]) throws { 16 | self.x = Utils.handleDynamicToString(v: opts["x"]) 17 | self.y = Utils.handleDynamicToString(v: opts["y"]) 18 | } 19 | 20 | convenience override init() { 21 | self.init() 22 | } 23 | 24 | func radii(rect: CGRect) -> CGSize { 25 | let mxRadius = Utils.parseSpreadValue(v: self.x, relativeTo: rect.width) 26 | let myRadius = Utils.parseSpreadValue(v: self.y, relativeTo: rect.height) 27 | return CGSize(width: mxRadius ?? 0, height: myRadius ?? 0) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ios/RCTImageMarker/TextBackground.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextBackground.swift 3 | // RCTImageMarker 4 | // 5 | // Created by Jimmydaddy on 2023/6/22. 6 | // Copyright © 2023 Jimmy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import React 12 | 13 | class TextBackground: Padding { 14 | var typeBg: String? 15 | var colorBg: UIColor? 16 | var cornerRadius: CornerRadius? 17 | 18 | init?(textBackgroundStyle textBackground: [AnyHashable: Any]?) throws { 19 | guard let textBackground = textBackground, !Utils.isNULL(textBackground) else { 20 | return nil 21 | } 22 | try super.init(paddingData: textBackground) 23 | self.typeBg = textBackground["type"] as? String 24 | self.colorBg = UIColor(hex: textBackground["color"] as! String) ?? UIColor.clear 25 | if textBackground.keys.contains("cornerRadius") { 26 | self.cornerRadius = try CornerRadius(dicOpts: textBackground["cornerRadius"] as! [AnyHashable : Any]) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /ios/RCTImageMarker/TextOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextOptions.swift 3 | // react-native-image-marker 4 | // 5 | // Created by Jimmydaddy on 2023/6/24. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | import React 11 | 12 | class TextOptions: NSObject { 13 | var X: String? 14 | var Y: String? 15 | var position: MarkerPositionEnum = .none 16 | var text: String 17 | var style: TextStyle? 18 | 19 | init(dicOpts opts: [AnyHashable: Any]) throws { 20 | guard let text = opts["text"] as? String else { 21 | throw NSError(domain: ErrorDomainEnum.PARAMS_REQUIRED.rawValue, code: 0, userInfo: [NSLocalizedDescriptionKey: "text is required"]) 22 | } 23 | 24 | if let positionOpts = opts["position"] as? [AnyHashable: Any] { 25 | self.X = Utils.handleDynamicToString(v: positionOpts["X"]) 26 | self.Y = Utils.handleDynamicToString(v: positionOpts["Y"]) 27 | self.position = positionOpts["position"] != nil ? RCTConvert.MarkerPosition(positionOpts["position"]) : .none 28 | } 29 | 30 | self.text = text 31 | self.style = try? TextStyle(dicOpts: (opts["style"] as? [AnyHashable: Any])!) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ios/RCTImageMarker/TextStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TextStyle.swift 3 | // react-native-image-marker 4 | // 5 | // Created by Jimmydaddy on 2023/6/24. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | import React 11 | 12 | class TextStyle: NSObject { 13 | var color: UIColor? 14 | var shadow: NSShadow? 15 | var textBackground: TextBackground? 16 | var font: UIFont? 17 | var skewX: CGFloat = 0.0 18 | var underline: Bool = false 19 | var strikeThrough: Bool = false 20 | var italic: Bool = false 21 | var bold: Bool = false 22 | var rotate: CGFloat = 0 23 | var textAlign: String? 24 | 25 | init(dicOpts opts: [AnyHashable: Any]) throws { 26 | self.color = UIColor(hex: opts["color"] as! String) ?? UIColor.clear 27 | if let shadowStyle = opts["shadowStyle"] as? [AnyHashable: Any] { 28 | self.shadow = Utils.getShadowStyle(shadowStyle) 29 | } else { 30 | self.shadow = nil 31 | } 32 | self.textBackground = try TextBackground(textBackgroundStyle: (opts["textBackgroundStyle"] as? [AnyHashable : Any])) 33 | let fontSize = opts["fontSize"] != nil ? RCTConvert.cgFloat(opts["fontSize"]) : 14.0 34 | self.font = UIFont(name: opts["fontName"] as? String ?? "", size: fontSize) 35 | if self.font == nil { 36 | self.font = UIFont.systemFont(ofSize: fontSize) 37 | } 38 | self.skewX = RCTConvert.cgFloat(opts["skewX"]) 39 | self.underline = RCTConvert.bool(opts["underline"]) 40 | self.strikeThrough = RCTConvert.bool(opts["strikeThrough"]) 41 | self.italic = RCTConvert.bool(opts["italic"]) 42 | self.bold = RCTConvert.bool(opts["bold"]) 43 | self.rotate = RCTConvert.cgFloat(opts["rotate"]) 44 | self.textAlign = opts["textAlign"] as? String 45 | 46 | super.init() 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ios/RCTImageMarker/UIColorHex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColorHex.swift 3 | // react-native-image-marker 4 | // 5 | // Created by Jimmydaddy on 2023/7/14. 6 | // 7 | 8 | extension UIColor { 9 | public convenience init?(hex: String) { 10 | let str: String? = hex 11 | if let str = str, str.isEmpty { 12 | return nil 13 | } else { 14 | let r, g, b, a: CGFloat 15 | 16 | if hex.hasPrefix("#") { 17 | let start = hex.index(hex.startIndex, offsetBy: 1) 18 | let hexColor = String(hex[start...]) 19 | 20 | if hexColor.count == 8 || hexColor.count == 4 { 21 | var newHexStr = "" 22 | if hexColor.count == 4 { 23 | for char in hexColor { 24 | newHexStr += String(repeating: String(char), count: 2) 25 | } 26 | } else { 27 | newHexStr = hexColor 28 | } 29 | let scanner = Scanner(string: newHexStr) 30 | var hexNumber: UInt64 = 0 31 | 32 | if scanner.scanHexInt64(&hexNumber) { 33 | r = CGFloat((hexNumber & 0xff000000) >> 24) / 255 34 | g = CGFloat((hexNumber & 0x00ff0000) >> 16) / 255 35 | b = CGFloat((hexNumber & 0x0000ff00) >> 8) / 255 36 | a = CGFloat(hexNumber & 0x000000ff) / 255 37 | 38 | self.init(red: r, green: g, blue: b, alpha: a) 39 | return 40 | } 41 | } else if hexColor.count == 6 || hexColor.count == 3 { 42 | var newHexStr = "" 43 | if hexColor.count == 3 { 44 | for char in hexColor { 45 | newHexStr += String(repeating: String(char), count: 2) 46 | } 47 | } else { 48 | newHexStr = hexColor 49 | } 50 | let scanner = Scanner(string: newHexStr) 51 | var hexNumber: UInt64 = 0 52 | 53 | if scanner.scanHexInt64(&hexNumber) { 54 | r = CGFloat((hexNumber & 0xff0000) >> 16) / 255 55 | g = CGFloat((hexNumber & 0x00ff00) >> 8) / 255 56 | b = CGFloat((hexNumber & 0x0000ff)) / 255 57 | a = 1.0 58 | self.init(red: r, green: g, blue: b, alpha: a) 59 | return 60 | } 61 | } 62 | } 63 | return nil 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /ios/RCTImageMarker/UIImageEdit.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage.swift 3 | // react-native-image-marker 4 | // 5 | // Created by Jimmydaddy on 2023/7/13. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UIImage { 11 | func rotatedImageWithTransformAndCorp(_ rotate: CGFloat, croppedToRect rect: CGRect) -> UIImage { 12 | let rotation = CGAffineTransform(rotationAngle: rotate * .pi / 180) 13 | let rotatedImage = rotatedImageWithTransform(rotation) 14 | 15 | let scale = rotatedImage.scale 16 | let cropRect = rect.applying(CGAffineTransform(scaleX: scale, y: scale)) 17 | 18 | let croppedImage = rotatedImage.cgImage?.cropping(to: cropRect) 19 | let image = UIImage(cgImage: croppedImage!, scale: self.scale, orientation: rotatedImage.imageOrientation) 20 | return image 21 | } 22 | 23 | func rotatedImageWithTransform(_ rotate: CGFloat) -> UIImage { 24 | if rotate == 0 || rotate.isNaN { 25 | return self; 26 | } 27 | let rotation = CGAffineTransform(rotationAngle: rotate * .pi / 180) 28 | let rotatedImage = rotatedImageWithTransform(rotation) 29 | return rotatedImage 30 | } 31 | 32 | fileprivate func rotatedImageWithTransform(_ transform: CGAffineTransform) -> UIImage { 33 | // draw image with transparent background 34 | let rotatedSize = CGRect(origin: .zero, size: size).applying(transform).integral.size 35 | let renderer = UIGraphicsImageRenderer(size: rotatedSize, format: UIGraphicsImageRendererFormat()) 36 | let image = renderer.image { context in 37 | context.cgContext.setFillColor(UIColor.clear.cgColor) 38 | context.cgContext.fill(CGRect(origin: .zero, size: rotatedSize)) 39 | context.cgContext.setFillColor(UIColor.clear.cgColor) 40 | context.cgContext.translateBy(x: rotatedSize.width / 2, y: rotatedSize.height / 2) 41 | context.cgContext.concatenate(transform) 42 | draw(in: CGRect(x: -size.width / 2, y: -size.height / 2, width: size.width, height: size.height)) 43 | } 44 | return image 45 | } 46 | 47 | static func transBase64(_ base64Str: String) -> UIImage? { 48 | let trimmedString = base64Str.trimmingCharacters(in: .whitespacesAndNewlines) 49 | guard let encodedString = trimmedString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed), 50 | let imgURL = URL(string: encodedString), 51 | let imageData = try? Data(contentsOf: imgURL), 52 | let image = UIImage(data: imageData) else { 53 | return nil 54 | } 55 | return image 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ios/RCTImageMarker/Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utils.swift 3 | // RCTImageMarker 4 | // 5 | // Created by Jimmydaddy on 2023/6/22. 6 | // Copyright © 2023 Jimmy. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | import React 12 | 13 | class Utils: NSObject { 14 | static func getColor(_ hexColor: String) -> UIColor { 15 | var cString: String = hexColor.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() 16 | 17 | if cString.hasPrefix("0X") { 18 | cString = String(cString.dropFirst(2)) 19 | } 20 | if cString.hasPrefix("#") { 21 | cString = String(cString.dropFirst()) 22 | } 23 | 24 | if cString.count != 8 && cString.count != 6 && cString.count != 3 && cString.count != 4 { 25 | return UIColor.clear 26 | } 27 | 28 | var red: UInt32 = 0 29 | var green: UInt32 = 0 30 | var blue: UInt32 = 0 31 | var alpha: CGFloat = 1.0 32 | 33 | if cString.count == 8 { 34 | let aString = String(cString.suffix(2)) 35 | if let a = UInt32(aString, radix: 16) { 36 | alpha = CGFloat(a) / 255.0 37 | } 38 | cString = String(cString.prefix(6)) 39 | } else if cString.count == 4 { 40 | let aString = String(cString.suffix(1)) 41 | if let a = UInt32(aString, radix: 16) { 42 | alpha = CGFloat(a) / 15.0 43 | } 44 | cString = String(cString.prefix(3)) 45 | } 46 | 47 | let hex6 = cString.count == 6 ? true : false 48 | var range = NSRange(location: 0, length: hex6 ? 2 : 1) 49 | 50 | /* 调用下面的方法处理字符串 */ 51 | let redStr = (cString as NSString).substring(with: range) 52 | red = stringToInt(redStr) 53 | 54 | range.location = hex6 ? 2 : 1 55 | let greenStr = (cString as NSString).substring(with: range) 56 | green = stringToInt(greenStr) 57 | 58 | range.location = hex6 ? 4 : 2 59 | let blueStr = (cString as NSString).substring(with: range) 60 | blue = stringToInt(blueStr) 61 | 62 | return UIColor(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: alpha) 63 | } 64 | 65 | static func stringToInt(_ string: String) -> UInt32 { 66 | if string.count == 1 { 67 | let hexChar = string[string.startIndex] 68 | var intCh: UInt32 = getCharInt(hexChar) 69 | return intCh * 2 70 | } else { 71 | let hexChar1 = string[string.startIndex] 72 | var intCh1: UInt32 = getCharInt(hexChar1) 73 | let hexChar2 = string[string.index(after: string.startIndex)] 74 | var intCh2: UInt32 = getCharInt(hexChar2) 75 | return intCh1 + intCh2 76 | } 77 | } 78 | 79 | static func getCharInt(_ char: Character) -> UInt32 { 80 | var charInt: UInt32 = 0 81 | if let asciiValue = char.asciiValue { 82 | switch asciiValue { 83 | case 48...57: // '0'...'9' 84 | charInt = UInt32(asciiValue) - 48 85 | case 65...70: // 'A'...'F' 86 | charInt = UInt32(asciiValue) - 55 87 | case 97...102: // 'a'...'f' 88 | charInt = UInt32(asciiValue) - 87 89 | default: 90 | print("Invalid hex character") 91 | } 92 | } 93 | return charInt 94 | } 95 | 96 | static func getShadowStyle(_ shadowStyle: [AnyHashable: Any]?) -> NSShadow? { 97 | if let shadowStyle = shadowStyle { 98 | let shadow = NSShadow() 99 | shadow.shadowBlurRadius = CGFloat(truncating: RCTConvert.nsNumber(shadowStyle["radius"])) 100 | shadow.shadowOffset = CGSize(width: CGFloat(truncating: RCTConvert.nsNumber(shadowStyle["dx"])), height: CGFloat(truncating: RCTConvert.nsNumber(shadowStyle["dy"]))) 101 | let color = getColor(RCTConvert.nsString(shadowStyle["color"])) 102 | shadow.shadowColor = color != nil ? color : UIColor.gray 103 | return shadow 104 | } else { 105 | return nil 106 | } 107 | } 108 | 109 | static func isPng(_ saveFormat: String?) -> Bool { 110 | return saveFormat != nil && (saveFormat!.caseInsensitiveCompare("png") == .orderedSame) 111 | } 112 | 113 | static func getExt(_ saveFormat: String?) -> String { 114 | let ext = saveFormat != nil && (saveFormat!.caseInsensitiveCompare("png") == .orderedSame) ? ".png" : ".jpg" 115 | return ext 116 | } 117 | 118 | static func isBase64(_ uri: String?) -> Bool { 119 | return uri != nil ? uri!.hasPrefix("data:") : false 120 | } 121 | 122 | static func isNULL(_ obj: Any?) -> Bool { 123 | return obj == nil || obj is NSNull 124 | } 125 | 126 | static func checkSpreadValue(str: String?, maxLength: Int = 1) -> Bool { 127 | if str == nil { return false } 128 | let pattern = #"^((\d+|\d+%)\s?){1,\#(maxLength)}$"# 129 | if (str?.range(of: pattern, options: .regularExpression)) != nil { 130 | return true 131 | } else { 132 | return false 133 | } 134 | } 135 | 136 | static func parseSpreadValue(v: String?, relativeTo length: CGFloat) -> CGFloat? { 137 | if v == nil { return nil } 138 | if v?.hasSuffix(String(describing: "%")) ?? false { 139 | let percent = CGFloat(Double(v!.dropLast()) ?? 0) / 100 140 | return length * percent 141 | } else { 142 | return CGFloat(Double(v!) ?? 0) 143 | } 144 | } 145 | 146 | static func handleDynamicToString(v: Any?) -> String { 147 | if (isNULL(v)) { return "0" } 148 | else { 149 | switch v { 150 | case is NSString: return RCTConvert.nsString(v) 151 | case is NSNumber: return RCTConvert.nsNumber(v).stringValue 152 | default: return "0" 153 | } 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /ios/RCTImageMarker/WatermarkImageOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WatermarkImageOptions.swift 3 | // react-native-image-marker 4 | // 5 | // Created by Jimmydaddy on 2023/7/31. 6 | // 7 | 8 | import Foundation 9 | import UIKit 10 | import React 11 | 12 | class WatermarkImageOptions: NSObject { 13 | var imageOption: ImageOptions 14 | var X: String? 15 | var Y: String? 16 | var position: MarkerPositionEnum = .none 17 | 18 | init(dicOpts opts: [AnyHashable: Any]) throws { 19 | self.imageOption = try ImageOptions(dicOpts: opts) 20 | let positionOpts = opts["position"] as? [AnyHashable: Any] 21 | if let positionOpts = positionOpts, !Utils.isNULL(positionOpts) { 22 | self.X = Utils.handleDynamicToString(v: positionOpts["X"]) 23 | self.Y = Utils.handleDynamicToString(v: positionOpts["Y"]) 24 | self.position = positionOpts["position"] != nil ? RCTConvert.MarkerPosition(positionOpts["position"]) : .none 25 | } 26 | } 27 | 28 | init(watermarkImage: ImageOptions, X: String?, Y: String?, position: MarkerPositionEnum) { 29 | self.imageOption = watermarkImage; 30 | self.X = X; 31 | self.Y = Y; 32 | self.position = position; 33 | } 34 | 35 | static func checkWatermarkImageParams(_ opts: [AnyHashable: Any], rejecter reject: @escaping RCTPromiseRejectBlock) -> WatermarkImageOptions? { 36 | do { 37 | return try WatermarkImageOptions(dicOpts: opts) 38 | } catch let error as NSError { 39 | reject(error.domain, error.localizedDescription, nil) 40 | return nil 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ios/RCTImageMarker/react-native-image-marker-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import 6 | #import 7 | #import 8 | #import 9 | -------------------------------------------------------------------------------- /lefthook.yml: -------------------------------------------------------------------------------- 1 | pre-commit: 2 | parallel: true 3 | commands: 4 | lint: 5 | files: git diff --name-only @{push} 6 | glob: "*.{js,ts,jsx,tsx}" 7 | run: npx eslint {files} 8 | types: 9 | files: git diff --name-only @{push} 10 | glob: "*.{js,ts, jsx, tsx}" 11 | run: npx tsc --noEmit 12 | commit-msg: 13 | parallel: true 14 | commands: 15 | commitlint: 16 | run: npx commitlint --edit 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-image-marker", 3 | "version": "1.2.6", 4 | "description": "Add text or icon watermark to your images", 5 | "main": "lib/commonjs/index", 6 | "module": "lib/module/index", 7 | "types": "lib/typescript/index.d.ts", 8 | "react-native": "src/index", 9 | "source": "src/index", 10 | "files": [ 11 | "src", 12 | "lib", 13 | "android", 14 | "ios", 15 | "cpp", 16 | "app.plugin.js", 17 | "*.podspec", 18 | "!lib/typescript/example", 19 | "!ios/build", 20 | "!android/build", 21 | "!android/gradle", 22 | "!android/gradlew", 23 | "!android/gradlew.bat", 24 | "!android/local.properties", 25 | "!**/__tests__", 26 | "!**/__fixtures__", 27 | "!**/__mocks__", 28 | "!**/.*" 29 | ], 30 | "scripts": { 31 | "test": "jest", 32 | "typecheck": "tsc --noEmit", 33 | "lint": "eslint \"**/*.{js,ts,tsx}\"", 34 | "prepack": "bob build", 35 | "release": "release-it", 36 | "example": "yarn --cwd example", 37 | "expo-example": "yarn --cwd expo-example", 38 | "bootstrap": "yarn example && yarn install && yarn example pods && yarn expo-example && yarn expo-example expo-install", 39 | "bootstrap-m1": "yarn example && yarn install && yarn example m1-pods", 40 | "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build", 41 | "docs": "typedoc --excludePrivate --tsconfig tsconfig.doc.json", 42 | "expo-install": "yarn expo-example expo-install" 43 | }, 44 | "keywords": [ 45 | "react-native", 46 | "ios", 47 | "android", 48 | "image", 49 | "marker", 50 | "text", 51 | "watermark", 52 | "icon" 53 | ], 54 | "repository": "https://github.com/JimmyDaddy/react-native-image-marker", 55 | "author": "jimmydaddy (https://github.com/JimmyDaddy)", 56 | "license": "MIT", 57 | "bugs": { 58 | "url": "https://github.com/JimmyDaddy/react-native-image-marker/issues" 59 | }, 60 | "homepage": "https://jimmydaddy.github.io/react-native-image-marker", 61 | "publishConfig": { 62 | "registry": "https://registry.npmjs.org/" 63 | }, 64 | "devDependencies": { 65 | "@commitlint/config-conventional": "^17.0.2", 66 | "@evilmartians/lefthook": "^1.2.2", 67 | "@expo/config-plugins": "^7.8.4", 68 | "@react-native-community/eslint-config": "^3.0.2", 69 | "@release-it/conventional-changelog": "^7.0.2", 70 | "@types/jest": "^28.1.2", 71 | "@types/react": "~17.0.21", 72 | "@types/react-native": "0.70.0", 73 | "commitlint": "^17.0.2", 74 | "del-cli": "^5.0.0", 75 | "eslint": "^8.4.1", 76 | "eslint-config-prettier": "^8.5.0", 77 | "eslint-plugin-prettier": "^4.0.0", 78 | "husky": "^8.0.3", 79 | "jest": "^28.1.1", 80 | "metro-react-native-babel-preset": "^0.77.0", 81 | "pod-install": "^0.1.0", 82 | "prettier": "^2.0.5", 83 | "react": "18.2.0", 84 | "react-native": "0.73.3", 85 | "react-native-builder-bob": "^0.20.0", 86 | "release-it": "^16.2.0", 87 | "typedoc": "^0.24.8", 88 | "typedoc-plugin-localization": "^3.0.1", 89 | "typedoc-plugin-rename-defaults": "^0.6.5", 90 | "typescript": "^4.5.2" 91 | }, 92 | "resolutions": { 93 | "@types/react": "17.0.21" 94 | }, 95 | "peerDependencies": { 96 | "react": "*", 97 | "react-native": "*" 98 | }, 99 | "engines": { 100 | "node": ">= 16.0.0" 101 | }, 102 | "jest": { 103 | "preset": "react-native", 104 | "modulePathIgnorePatterns": [ 105 | "/example/node_modules", 106 | "/lib/" 107 | ] 108 | }, 109 | "commitlint": { 110 | "extends": [ 111 | "@commitlint/config-conventional" 112 | ] 113 | }, 114 | "release-it": { 115 | "git": { 116 | "commitMessage": "chore: release ${version}", 117 | "tagName": "v${version}" 118 | }, 119 | "npm": { 120 | "publish": false 121 | }, 122 | "github": { 123 | "release": true 124 | }, 125 | "plugins": { 126 | "@release-it/conventional-changelog": { 127 | "preset": "angular", 128 | "infile": "CHANGELOG.md" 129 | } 130 | } 131 | }, 132 | "eslintConfig": { 133 | "root": true, 134 | "extends": [ 135 | "@react-native-community", 136 | "prettier" 137 | ], 138 | "rules": { 139 | "prettier/prettier": [ 140 | "error", 141 | { 142 | "quoteProps": "consistent", 143 | "singleQuote": true, 144 | "tabWidth": 2, 145 | "trailingComma": "es5", 146 | "useTabs": false 147 | } 148 | ] 149 | } 150 | }, 151 | "eslintIgnore": [ 152 | "node_modules/", 153 | "lib/" 154 | ], 155 | "prettier": { 156 | "quoteProps": "consistent", 157 | "singleQuote": true, 158 | "tabWidth": 2, 159 | "trailingComma": "es5", 160 | "useTabs": false 161 | }, 162 | "react-native-builder-bob": { 163 | "source": "src", 164 | "output": "lib", 165 | "targets": [ 166 | "commonjs", 167 | "module", 168 | [ 169 | "typescript", 170 | { 171 | "project": "tsconfig.build.json" 172 | } 173 | ] 174 | ] 175 | }, 176 | "dependencies": { 177 | "patch-package": "^8.0.0", 178 | "postinstall-postinstall": "^2.1.0" 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /react-native-image-marker.podspec: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, "package.json"))) 4 | folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' 5 | 6 | Pod::Spec.new do |s| 7 | s.name = "react-native-image-marker" 8 | s.version = package["version"] 9 | s.summary = package["description"] 10 | s.homepage = package["homepage"] 11 | s.license = package["license"] 12 | s.authors = package["author"] 13 | 14 | s.platforms = { :ios => "11.0" } 15 | s.source = { :git => "https://github.com/JimmyDaddy/react-native-image-marker.git", :tag => "#{s.version}" } 16 | 17 | s.source_files = "ios/**/*.{h,m,mm,swift}" 18 | 19 | s.dependency "React-Core" 20 | 21 | # Don't install the dependencies when we run `pod install` in the old architecture. 22 | if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then 23 | s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" 24 | s.pod_target_xcconfig = { 25 | "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", 26 | "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1", 27 | "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" 28 | } 29 | s.dependency "React-Codegen" 30 | s.dependency "RCT-Folly" 31 | s.dependency "RCTRequired" 32 | s.dependency "RCTTypeSafety" 33 | s.dependency "ReactCommon/turbomodule/core" 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /scripts/bootstrap.js: -------------------------------------------------------------------------------- 1 | const os = require('os'); 2 | const path = require('path'); 3 | const child_process = require('child_process'); 4 | 5 | const root = path.resolve(__dirname, '..'); 6 | const args = process.argv.slice(2); 7 | const options = { 8 | cwd: process.cwd(), 9 | env: process.env, 10 | stdio: 'inherit', 11 | encoding: 'utf-8', 12 | }; 13 | 14 | if (os.type() === 'Windows_NT') { 15 | options.shell = true; 16 | } 17 | 18 | let result; 19 | 20 | if (process.cwd() !== root || args.length) { 21 | // We're not in the root of the project, or additional arguments were passed 22 | // In this case, forward the command to `yarn` 23 | result = child_process.spawnSync('yarn', args, options); 24 | } else { 25 | if (os.platform() === 'darwin' && os.arch() === 'arm64') { 26 | // If `yarn` is run without arguments, perform bootstrap 27 | result = child_process.spawnSync('yarn', ['bootstrap-m1'], options); 28 | } else { 29 | result = child_process.spawnSync('yarn', ['bootstrap'], options); 30 | } 31 | } 32 | 33 | process.exitCode = result.status; 34 | -------------------------------------------------------------------------------- /src/__tests__/index.test.ts: -------------------------------------------------------------------------------- 1 | it.todo('write a test'); 2 | -------------------------------------------------------------------------------- /src/expo-plugin/withImageMarker.ts: -------------------------------------------------------------------------------- 1 | import { createRunOncePlugin, withPlugins } from '@expo/config-plugins'; 2 | 3 | const pkg = require('../../../package.json'); 4 | 5 | const withImageMarker = (config: any) => withPlugins(config, []); 6 | 7 | export default createRunOncePlugin(withImageMarker, pkg.name, pkg.version); 8 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "./tsconfig", 4 | "exclude": ["example", "expo-example"] 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.doc.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "./tsconfig", 4 | "exclude": ["example", "expo-example"], 5 | "typedocOptions": { 6 | "exclude": [ "**/node_modules/**/*.*", "example/", "expo-example/" ], 7 | "entryPoints": ["src/index.ts"], 8 | "readme": "./README.MD", 9 | "name": "image marker for rn", 10 | "out": "./docs/latest", 11 | "excludeExternals": true, 12 | "excludePrivate": true, 13 | "plugin": ["typedoc-plugin-rename-defaults"], 14 | "customCss": "./docs/custom.css", 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "react-native-image-marker": ["./src/index"] 6 | }, 7 | "allowUnreachableCode": false, 8 | "allowUnusedLabels": false, 9 | "esModuleInterop": true, 10 | "ignoreDeprecations": "5.0", 11 | "importsNotUsedAsValues": "error", 12 | "forceConsistentCasingInFileNames": true, 13 | "jsx": "react", 14 | "lib": ["esnext"], 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "noFallthroughCasesInSwitch": true, 18 | "noImplicitReturns": true, 19 | "noImplicitUseStrict": false, 20 | "noStrictGenericChecks": false, 21 | "noUncheckedIndexedAccess": true, 22 | "noUnusedLocals": true, 23 | "noUnusedParameters": true, 24 | "resolveJsonModule": true, 25 | "skipLibCheck": true, 26 | "strict": true, 27 | "target": "esnext" 28 | } 29 | } 30 | --------------------------------------------------------------------------------