├── .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 |
--------------------------------------------------------------------------------