├── .clang-format ├── .eslintrc.js ├── .github ├── FUNDING.yml ├── dependabot.yml ├── funding-octocat.svg └── workflows │ ├── build-android.yml │ ├── build-ios.yml │ ├── validate-cpp.yml │ └── validate-js.yml ├── .gitignore ├── .gitmodules ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── android ├── CMakeLists.txt ├── build.gradle ├── gradle.properties └── src │ └── main │ ├── AndroidManifest.xml │ ├── cpp │ └── Tflite.cpp │ └── java │ └── com │ └── tflite │ ├── TfliteModule.java │ └── TflitePackage.java ├── app.plugin.js ├── cpp ├── TensorHelpers.cpp ├── TensorHelpers.h ├── TensorflowPlugin.cpp ├── TensorflowPlugin.h └── jsi │ ├── Promise.cpp │ ├── Promise.h │ ├── TypedArray.cpp │ └── TypedArray.h ├── example ├── .bundle │ └── config ├── .eslintrc.js ├── README.md ├── android │ ├── app │ │ ├── build.gradle │ │ ├── debug.keystore │ │ ├── proguard-rules.pro │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── tfliteexample │ │ │ │ ├── MainActivity.java │ │ │ │ └── MainApplication.java │ │ │ └── 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 │ └── settings.gradle ├── app.json ├── assets │ └── efficientdet.tflite ├── babel.config.js ├── index.js ├── ios │ ├── .xcode.env │ ├── File.swift │ ├── Podfile │ ├── Podfile.lock │ ├── TfliteExample-Bridging-Header.h │ ├── TfliteExample.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── TfliteExample.xcscheme │ ├── TfliteExample.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── TfliteExample │ │ ├── AppDelegate.h │ │ ├── AppDelegate.mm │ │ ├── Images.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Info.plist │ │ ├── LaunchScreen.storyboard │ │ └── main.m │ └── TfliteExampleTests │ │ ├── Info.plist │ │ └── TfliteExampleTests.m ├── metro.config.js ├── package.json ├── react-native.config.js ├── src │ └── App.tsx ├── tsconfig.json └── yarn.lock ├── img ├── banner-dark.png ├── banner-light.png ├── ios-coreml-guide.png ├── netron-inspect-model.png └── tfhub-description.png ├── ios ├── Tflite.h ├── Tflite.mm └── Tflite.xcodeproj │ └── project.pbxproj ├── package.json ├── react-native-fast-tflite.podspec ├── scripts ├── check-all.sh └── clang-format.sh ├── src ├── NativeRNTflite.ts ├── TensorflowLite.ts ├── TensorflowModule.ts ├── __tests__ │ └── index.test.tsx ├── expo-plugin │ ├── @types.ts │ ├── withAndroidGpuLibraries.ts │ ├── withCoreMLDelegate.ts │ └── withFastTFLite.ts └── index.ts ├── tsconfig.json └── yarn.lock /.clang-format: -------------------------------------------------------------------------------- 1 | # Config for clang-format version 16 2 | 3 | # Standard 4 | BasedOnStyle: llvm 5 | Standard: c++14 6 | 7 | # Indentation 8 | IndentWidth: 2 9 | ColumnLimit: 100 10 | 11 | # Includes 12 | SortIncludes: true 13 | SortUsingDeclarations: true 14 | 15 | # Pointer and reference alignment 16 | PointerAlignment: Left 17 | ReferenceAlignment: Left 18 | ReflowComments: true 19 | 20 | # Line breaking options 21 | BreakBeforeBraces: Attach 22 | BreakConstructorInitializers: BeforeColon 23 | AllowShortFunctionsOnASingleLine: Empty 24 | IndentCaseLabels: true 25 | NamespaceIndentation: Inner 26 | 27 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parser: '@typescript-eslint/parser', 4 | parserOptions: { 5 | tsconfigRootDir: __dirname, 6 | project: ['./tsconfig.json'], 7 | ecmaFeatures: { 8 | jsx: true, 9 | }, 10 | ecmaVersion: 2018, 11 | sourceType: 'module', 12 | }, 13 | ignorePatterns: [ 14 | 'scripts', 15 | 'lib', 16 | 'docs', 17 | 'example', 18 | 'tensorflow', 19 | 'app.plugin.js', 20 | ], 21 | plugins: ['@typescript-eslint'], 22 | extends: ['plugin:@typescript-eslint/recommended', '@react-native-community'], 23 | rules: { 24 | // eslint 25 | 'semi': 'off', 26 | 'curly': ['warn', 'multi-or-nest', 'consistent'], 27 | 'no-mixed-spaces-and-tabs': ['warn', 'smart-tabs'], 28 | 'no-async-promise-executor': 'warn', 29 | 'require-await': 'warn', 30 | 'no-return-await': 'warn', 31 | 'no-await-in-loop': 'warn', 32 | 'comma-dangle': 'off', // prettier already detects this 33 | 'no-restricted-syntax': [ 34 | 'error', 35 | { 36 | selector: 'TSEnumDeclaration', 37 | message: 38 | "Enums have various disadvantages, use TypeScript's union types instead.", 39 | }, 40 | ], 41 | // prettier 42 | 'prettier/prettier': ['warn'], 43 | // typescript 44 | '@typescript-eslint/no-use-before-define': 'off', 45 | '@typescript-eslint/no-unused-vars': [ 46 | 'error', 47 | { 48 | vars: 'all', 49 | args: 'after-used', 50 | ignoreRestSiblings: false, 51 | varsIgnorePattern: '^_', 52 | argsIgnorePattern: '^_', 53 | }, 54 | ], 55 | '@typescript-eslint/explicit-function-return-type': [ 56 | 'warn', 57 | { 58 | allowExpressions: true, 59 | }, 60 | ], 61 | '@typescript-eslint/no-namespace': 'off', 62 | '@typescript-eslint/ban-ts-comment': 'off', 63 | '@typescript-eslint/no-unsafe-assignment': 'error', 64 | // react plugin 65 | 'react/no-unescaped-entities': 'off', 66 | // react native plugin 67 | 'react-native/no-unused-styles': 'warn', 68 | 'react-native/split-platform-components': 'off', 69 | 'react-native/no-inline-styles': 'warn', 70 | 'react-native/no-color-literals': 'off', 71 | 'react-native/no-raw-text': 'off', 72 | 'react-native/no-single-element-style-arrays': 'warn', 73 | '@typescript-eslint/strict-boolean-expressions': [ 74 | 'error', 75 | { 76 | allowString: false, 77 | allowNullableObject: false, 78 | allowNumber: false, 79 | allowNullableBoolean: true, 80 | }, 81 | ], 82 | '@typescript-eslint/no-non-null-assertion': 'error', 83 | '@typescript-eslint/no-unnecessary-condition': 'error', 84 | }, 85 | env: { 86 | node: true, 87 | }, 88 | } 89 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: mrousavy 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: mrousavy 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "weekly" 8 | labels: 9 | - "dependencies" 10 | # - package-ecosystem: "npm" 11 | # directory: "./" 12 | # schedule: 13 | # interval: "monthly" 14 | # labels: 15 | # - "dependencies" 16 | # - package-ecosystem: "npm" 17 | # directory: "./example/" 18 | # schedule: 19 | # interval: "monthly" 20 | # labels: 21 | # - "dependencies" 22 | -------------------------------------------------------------------------------- /.github/funding-octocat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | 15 | 19 | 23 | 27 | 31 | 35 | 39 | 43 | 47 | 51 | 55 | 59 | 63 | 67 | 71 | 75 | 79 | 83 | 87 | 91 | 95 | 99 | 100 | 101 | 105 | 109 | 113 | 117 | 121 | 125 | 129 | 133 | 134 | 137 | 140 | 143 | 144 | 148 | 152 | 153 | 154 | 155 | 168 | 169 |
170 | This library helped you?
Consider sponsoring!
171 |
172 |
173 |
174 | -------------------------------------------------------------------------------- /.github/workflows/build-android.yml: -------------------------------------------------------------------------------- 1 | name: Build Android 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - '.github/workflows/build-android.yml' 9 | - 'cpp/**' 10 | - 'android/**' 11 | - 'example/android/**' 12 | - 'yarn.lock' 13 | - 'example/yarn.lock' 14 | pull_request: 15 | paths: 16 | - '.github/workflows/build-android.yml' 17 | - 'cpp/**' 18 | - 'android/**' 19 | - 'example/android/**' 20 | - 'yarn.lock' 21 | - 'example/yarn.lock' 22 | 23 | jobs: 24 | build: 25 | name: Build Android Example App 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/checkout@v4 29 | 30 | - name: Setup JDK 17 31 | uses: actions/setup-java@v4 32 | with: 33 | distribution: 'zulu' 34 | java-version: 17 35 | java-package: jdk 36 | 37 | - name: Get yarn cache directory path 38 | id: yarn-cache-dir-path 39 | run: echo "::set-output name=dir::$(yarn cache dir)" 40 | - name: Restore node_modules from cache 41 | uses: actions/cache@v4 42 | id: yarn-cache 43 | with: 44 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 45 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 46 | restore-keys: | 47 | ${{ runner.os }}-yarn- 48 | - name: Install node_modules 49 | run: yarn install --frozen-lockfile 50 | - name: Install node_modules for example/ 51 | run: yarn install --frozen-lockfile --cwd example 52 | 53 | - name: Restore Gradle cache 54 | uses: actions/cache@v4 55 | with: 56 | path: | 57 | ~/.gradle/caches 58 | ~/.gradle/wrapper 59 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 60 | restore-keys: | 61 | ${{ runner.os }}-gradle- 62 | - name: Run Gradle Build for example/android/ 63 | run: cd example/android && ./gradlew assembleDebug --build-cache && cd ../.. 64 | -------------------------------------------------------------------------------- /.github/workflows/build-ios.yml: -------------------------------------------------------------------------------- 1 | name: Build iOS 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - '.github/workflows/build-ios.yml' 9 | - 'cpp/**' 10 | - 'ios/**' 11 | - '*.podspec' 12 | - 'example/ios/**' 13 | pull_request: 14 | paths: 15 | - '.github/workflows/build-ios.yml' 16 | - 'cpp/**' 17 | - 'ios/**' 18 | - '*.podspec' 19 | - 'example/ios/**' 20 | 21 | jobs: 22 | build: 23 | name: Build iOS Example App 24 | runs-on: macOS-latest 25 | defaults: 26 | run: 27 | working-directory: example/ios 28 | steps: 29 | - uses: actions/checkout@v4 30 | 31 | - name: Get yarn cache directory path 32 | id: yarn-cache-dir-path 33 | run: echo "::set-output name=dir::$(yarn cache dir)" 34 | - name: Restore node_modules from cache 35 | uses: actions/cache@v4 36 | id: yarn-cache 37 | with: 38 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 39 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 40 | restore-keys: | 41 | ${{ runner.os }}-yarn- 42 | - name: Install node_modules for example/ 43 | run: yarn install --frozen-lockfile --cwd .. 44 | 45 | - name: Restore buildcache 46 | uses: mikehardy/buildcache-action@v2 47 | continue-on-error: true 48 | 49 | - name: Setup Ruby (bundle) 50 | uses: ruby/setup-ruby@v1 51 | with: 52 | ruby-version: 2.6.10 53 | bundler-cache: true 54 | working-directory: example/ios 55 | 56 | - name: Restore Pods cache 57 | uses: actions/cache@v4 58 | with: 59 | path: | 60 | example/ios/Pods 61 | ~/Library/Caches/CocoaPods 62 | ~/.cocoapods 63 | key: ${{ runner.os }}-cocoapods-${{ hashFiles('**/Podfile.lock') }} 64 | restore-keys: | 65 | ${{ runner.os }}-cocoapods- 66 | - name: Install Pods 67 | run: pod install 68 | - name: Install xcpretty 69 | run: gem install xcpretty 70 | - name: Build App 71 | run: "set -o pipefail && xcodebuild \ 72 | CC=clang CPLUSPLUS=clang++ LD=clang LDPLUSPLUS=clang++ \ 73 | -derivedDataPath build -UseModernBuildSystem=YES \ 74 | -workspace TfliteExample.xcworkspace \ 75 | -scheme TfliteExample \ 76 | -sdk iphonesimulator \ 77 | -configuration Debug \ 78 | -destination 'platform=iOS Simulator,name=iPhone 15 Pro' \ 79 | build \ 80 | CODE_SIGNING_ALLOWED=NO | xcpretty" 81 | -------------------------------------------------------------------------------- /.github/workflows/validate-cpp.yml: -------------------------------------------------------------------------------- 1 | name: Validate C++ 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - '.github/workflows/validate-cpp.yml' 9 | - 'cpp/**' 10 | - 'android/src/main/cpp/**' 11 | - 'ios/**' 12 | pull_request: 13 | paths: 14 | - '.github/workflows/validate-cpp.yml' 15 | - 'cpp/**' 16 | - 'android/src/main/cpp/**' 17 | - 'ios/**' 18 | 19 | jobs: 20 | lint: 21 | name: Check clang-format 22 | runs-on: ubuntu-latest 23 | strategy: 24 | matrix: 25 | path: 26 | - 'cpp' 27 | - 'android/src/main/cpp' 28 | - 'ios' 29 | steps: 30 | - uses: actions/checkout@v4 31 | - name: Remove lib folders before linting 32 | run: | 33 | rm -rf ios/*.framework 34 | rm -rf android/src/main/cpp/lib 35 | - name: Run clang-format style check 36 | uses: jidicula/clang-format-action@v4.14.0 37 | with: 38 | clang-format-version: '16' 39 | check-path: ${{ matrix.path }} 40 | 41 | -------------------------------------------------------------------------------- /.github/workflows/validate-js.yml: -------------------------------------------------------------------------------- 1 | name: Validate JS 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - '.github/workflows/validate-js.yml' 9 | - 'src/**' 10 | - '*.json' 11 | - '*.js' 12 | - '*.lock' 13 | - 'example/src/**' 14 | - 'example/*.json' 15 | - 'example/*.js' 16 | - 'example/*.lock' 17 | - 'example/*.tsx' 18 | pull_request: 19 | paths: 20 | - '.github/workflows/validate-js.yml' 21 | - 'src/**' 22 | - '*.json' 23 | - '*.js' 24 | - '*.lock' 25 | - 'example/src/**' 26 | - 'example/*.json' 27 | - 'example/*.js' 28 | - 'example/*.lock' 29 | - 'example/*.tsx' 30 | 31 | jobs: 32 | compile: 33 | name: Compile JS (tsc) 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/checkout@v4 37 | 38 | - name: Install reviewdog 39 | uses: reviewdog/action-setup@v1 40 | 41 | - name: Get yarn cache directory path 42 | id: yarn-cache-dir-path 43 | run: echo "::set-output name=dir::$(yarn cache dir)" 44 | - name: Restore node_modules from cache 45 | uses: actions/cache@v4 46 | id: yarn-cache 47 | with: 48 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 49 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 50 | restore-keys: | 51 | ${{ runner.os }}-yarn- 52 | 53 | - name: Install node_modules 54 | run: yarn install --frozen-lockfile 55 | - name: Install node_modules (example/) 56 | run: yarn install --frozen-lockfile --cwd example 57 | 58 | - name: Run TypeScript # Reviewdog tsc errorformat: %f:%l:%c - error TS%n: %m 59 | run: | 60 | yarn typescript | reviewdog -name="tsc" -efm="%f(%l,%c): error TS%n: %m" -reporter="github-pr-review" -filter-mode="nofilter" -fail-on-error -tee 61 | env: 62 | REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} 63 | 64 | - name: Run TypeScript in example/ # Reviewdog tsc errorformat: %f:%l:%c - error TS%n: %m 65 | run: | 66 | cd example && yarn typescript | reviewdog -name="tsc" -efm="%f(%l,%c): error TS%n: %m" -reporter="github-pr-review" -filter-mode="nofilter" -fail-on-error -tee && cd .. 67 | env: 68 | REVIEWDOG_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }} 69 | 70 | lint: 71 | name: Lint JS (eslint, prettier) 72 | runs-on: ubuntu-latest 73 | steps: 74 | - uses: actions/checkout@v4 75 | 76 | - name: Get yarn cache directory path 77 | id: yarn-cache-dir-path 78 | run: echo "::set-output name=dir::$(yarn cache dir)" 79 | - name: Restore node_modules from cache 80 | uses: actions/cache@v4 81 | id: yarn-cache 82 | with: 83 | path: ${{ steps.yarn-cache-dir-path.outputs.dir }} 84 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 85 | restore-keys: | 86 | ${{ runner.os }}-yarn- 87 | 88 | - name: Install node_modules 89 | run: yarn install --frozen-lockfile 90 | - name: Install node_modules (example/) 91 | run: yarn install --frozen-lockfile --cwd example 92 | 93 | - name: Run ESLint 94 | run: yarn lint -f @jamesacarr/github-actions 95 | 96 | - name: Run ESLint with auto-fix 97 | run: yarn lint --fix 98 | 99 | - name: Run ESLint in example/ 100 | run: cd example && yarn lint -f @jamesacarr/github-actions && cd .. 101 | 102 | - name: Run ESLint in example/ with auto-fix 103 | run: cd example && yarn lint --fix && cd .. 104 | 105 | - name: Verify no files have changed after auto-fix 106 | run: git diff --exit-code HEAD 107 | -------------------------------------------------------------------------------- /.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 | 32 | # Android/IJ 33 | # 34 | .classpath 35 | .cxx 36 | .gradle 37 | .idea 38 | .project 39 | .settings 40 | local.properties 41 | android.iml 42 | 43 | # Cocoapods 44 | # 45 | example/ios/Pods 46 | 47 | # Ruby 48 | example/vendor/ 49 | 50 | # node.js 51 | # 52 | node_modules/ 53 | npm-debug.log 54 | yarn-debug.log 55 | yarn-error.log 56 | 57 | # BUCK 58 | buck-out/ 59 | \.buckd/ 60 | android/app/libs 61 | android/keystores/debug.keystore 62 | 63 | # Expo 64 | .expo/ 65 | 66 | # Turborepo 67 | .turbo/ 68 | 69 | # generated by bob 70 | lib/ 71 | 72 | # Generated Tensorflow Lite lib 73 | android/src/main/cpp/lib 74 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/react-native-fast-tflite/15663b60d620b1b833a841655f53e650789374b6/.gitmodules -------------------------------------------------------------------------------- /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. 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/TfliteExample.xcworkspace` in XCode and find the source files at `Pods > Development Pods > react-native-fast-tflite`. 57 | 58 | To edit the Java or Kotlin files, open `example/android` in Android studio and find the source files at `react-native-fast-tflite` 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 Marc Rousavy 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Fast TFLite 6 | 7 | 8 | 9 |
10 | 11 | A high-performance [TensorFlow Lite](https://www.tensorflow.org/lite) library for React Native. 12 | 13 | - 🔥 Powered by JSI 14 | - 💨 Zero-copy ArrayBuffers 15 | - 🔧 Uses the low-level C/C++ TensorFlow Lite core API for direct memory access 16 | - 🔄 Supports swapping out TensorFlow Models at runtime 17 | - 🖥️ Supports GPU-accelerated delegates (CoreML/Metal/OpenGL) 18 | - 📸 Easy [VisionCamera](https://github.com/mrousavy/react-native-vision-camera) integration 19 | 20 | ## Installation 21 | 22 | 1. Add the npm package 23 | ```sh 24 | yarn add react-native-fast-tflite 25 | ``` 26 | 2. In `metro.config.js`, add `tflite` as a supported asset extension: 27 | ```js 28 | module.exports = { 29 | // ... 30 | resolver: { 31 | assetExts: ['tflite', // ... 32 | // ... 33 | ``` 34 | This allows you to drop `.tflite` files into your app and swap them out at runtime without having to rebuild anything! 🔥 35 | 3. (Optional) If you want to enable the GPU Delegate, see ["Using GPU Delegates"](#using-gpu-delegates) down below. 36 | 4. Run your app (`yarn android` / `npx pod-install && yarn ios`) 37 | 38 | ## Usage 39 | 40 | 1. Find a TensorFlow Lite (`.tflite`) model you want to use. There's thousands of public models on [tfhub.dev](https://tfhub.dev). 41 | 2. Drag your TensorFlow Lite model into your React Native app's asset folder (e.g. `src/assets/my-model.tflite`) 42 | 3. Load the Model: 43 | 44 | ```ts 45 | // Option A: Standalone Function 46 | const model = await loadTensorflowModel(require('assets/my-model.tflite')) 47 | 48 | // Option B: Hook in a Function Component 49 | const plugin = useTensorflowModel(require('assets/my-model.tflite')) 50 | ``` 51 | 52 | 4. Call the Model: 53 | ```ts 54 | const inputData = ... 55 | const outputData = await model.run(inputData) 56 | console.log(outputData) 57 | ``` 58 | 59 | ### Loading Models 60 | 61 | Models can be loaded either from the React Native bundle using a `require(..)` statement, or any kind of URI/URL (`http://..` or `file://..`): 62 | 63 | ```ts 64 | // Asset from React Native Bundle 65 | loadTensorflowModel(require('assets/my-model.tflite')) 66 | // File on the local filesystem 67 | loadTensorflowModel({ url: 'file:///var/mobile/.../my-model.tflite' }) 68 | // Remote URL 69 | loadTensorflowModel({ 70 | url: 'https://tfhub.dev/google/lite-model/object_detection_v1.tflite', 71 | }) 72 | ``` 73 | 74 | Loading a Model is asynchronous since Buffers need to be allocated. Make sure to check for any potential errors when loading a Model. 75 | 76 | ### Input and Output data 77 | 78 | TensorFlow uses _tensors_ as input and output formats. Since TensorFlow Lite is optimized to run on fixed array sized byte buffers, you are responsible for interpreting the raw data yourself. 79 | 80 | To inspect the input and output tensors on your TensorFlow Lite model, open it in [Netron](https://netron.app). 81 | 82 | For example, the `object_detection_mobile_object_localizer_v1_1_default_1.tflite` model I found on [tfhub.dev](https://tfhub.dev) has **1 input tensor** and **4 output tensors**: 83 | 84 | ![Screenshot of netron.app inspecting the model](./img/netron-inspect-model.png) 85 | 86 | In the description on [tfhub.dev](https://tfhub.dev) we can find the description of all tensors: 87 | 88 | ![Screenshot of tfhub.dev inspecting the model](./img/tfhub-description.png) 89 | 90 | From that we now know that we need a 192 x 192 input image with 3 bytes per pixel (meaning RGB). 91 | 92 | #### Usage (VisionCamera) 93 | 94 | If you were to use this model with a [VisionCamera](https://github.com/mrousavy/react-native-vision-camera) Frame Processor, you would need to convert the Frame to a 192 x 192 x 3 byte array. 95 | To do the conversion, use [vision-camera-resize-plugin](https://github.com/mrousavy/vision-camera-resize-plugin): 96 | 97 | ```tsx 98 | const objectDetection = useTensorflowModel(require('object_detection.tflite')) 99 | const model = 100 | objectDetection.state === 'loaded' ? objectDetection.model : undefined 101 | 102 | const { resize } = useResizePlugin() 103 | 104 | const frameProcessor = useFrameProcessor( 105 | (frame) => { 106 | 'worklet' 107 | if (model == null) return 108 | 109 | // 1. Resize 4k Frame to 192x192x3 using vision-camera-resize-plugin 110 | const resized = resize(frame, { 111 | scale: { 112 | width: 192, 113 | height: 192, 114 | }, 115 | pixelFormat: 'rgb', 116 | dataType: 'uint8', 117 | }) 118 | 119 | // 2. Run model with given input buffer synchronously 120 | const outputs = model.runSync([resized]) 121 | 122 | // 3. Interpret outputs accordingly 123 | const detection_boxes = outputs[0] 124 | const detection_classes = outputs[1] 125 | const detection_scores = outputs[2] 126 | const num_detections = outputs[3] 127 | console.log(`Detected ${num_detections[0]} objects!`) 128 | 129 | for (let i = 0; i < detection_boxes.length; i += 4) { 130 | const confidence = detection_scores[i / 4] 131 | if (confidence > 0.7) { 132 | // 4. Draw a red box around the detected object! 133 | const left = detection_boxes[i] 134 | const top = detection_boxes[i + 1] 135 | const right = detection_boxes[i + 2] 136 | const bottom = detection_boxes[i + 3] 137 | const rect = SkRect.Make(left, top, right, bottom) 138 | canvas.drawRect(rect, SkColors.Red) 139 | } 140 | } 141 | }, 142 | [model] 143 | ) 144 | 145 | return 146 | ``` 147 | 148 | ### Using GPU Delegates 149 | 150 | GPU Delegates offer faster, GPU accelerated computation. There's multiple different GPU delegates which you can enable: 151 | 152 | #### CoreML (iOS) 153 | 154 | To enable the CoreML Delegate, you must configure react-native-fast-tflite to include it in the build. 155 | 156 | ##### Expo 157 | 158 | For Expo, just use the config plugin in your expo config (`app.json`, `app.config.json` or `app.config.js`): 159 | 160 | ```json 161 | { 162 | "name": "my app", 163 | "plugins": [ 164 | [ 165 | "react-native-fast-tflite", 166 | { 167 | "enableCoreMLDelegate": true 168 | } 169 | ] 170 | ] 171 | } 172 | ``` 173 | 174 | ##### Bare React Native 175 | 176 | If you are on bare React Native, you need to include the CoreML/Metal code in your project: 177 | 178 | 1. Set `$EnableCoreMLDelegate` to true in your `Podfile`: 179 | 180 | ```ruby 181 | $EnableCoreMLDelegate=true 182 | 183 | # rest of your podfile... 184 | ``` 185 | 186 | 2. Open your iOS project in Xcode and add the `CoreML` framework to your project: 187 | ![Xcode > xcodeproj > General > Frameworks, Libraries and Embedded Content > CoreML](ios/../img/ios-coreml-guide.png) 188 | 3. Re-install Pods and build your app: 189 | ```sh 190 | cd ios && pod install && cd .. 191 | yarn ios 192 | ``` 193 | 4. Use the CoreML Delegate: 194 | ```ts 195 | const model = await loadTensorflowModel( 196 | require('assets/my-model.tflite'), 197 | 'core-ml' 198 | ) 199 | ``` 200 | 201 | > [!NOTE] 202 | > Since some operations aren't supported on the CoreML delegate, make sure your Model is able to use the CoreML GPU delegate. 203 | 204 | #### Android GPU/NNAPI (Android) 205 | 206 | To enable GPU or NNAPI delegate in Android, you **may** need to include some native libraries, starting from Android 12. 207 | 208 | ##### Expo 209 | 210 | For Expo, just use the config plugin in your expo config (`app.json`, `app.config.json` or `app.config.js`) with `enableAndroidGpuLibraries`: 211 | 212 | ```json 213 | { 214 | "name": "my app", 215 | "plugins": [ 216 | [ 217 | "react-native-fast-tflite", 218 | { 219 | "enableAndroidGpuLibraries": true 220 | } 221 | ] 222 | ] 223 | } 224 | ``` 225 | 226 | By default, when enabled, `libOpenCl.so` will be included in your AndroidManifest.xml. You can also include more libraries by passing an array of string: 227 | 228 | ```json 229 | { 230 | "name": "my app", 231 | "plugins": [ 232 | [ 233 | "react-native-fast-tflite", 234 | { 235 | "enableAndroidGpuLibraries": ["libOpenCL-pixel.so", "libGLES_mali.so"] 236 | } 237 | ] 238 | ] 239 | } 240 | ``` 241 | 242 | > [!NOTE] 243 | > For expo app, remember to run prebuild if the cpu library is not yet included in your AndroidManifest.xml. 244 | 245 | ##### Bare React Native 246 | 247 | If you are on bare React Native, you will need to include all needed libraries with `uses-native-library` on `application` scope in AndroidManifest.xml. 248 | 249 | ```xml 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | ``` 258 | 259 | Then, you can just use it: 260 | 261 | ```ts 262 | const model = await loadTensorflowModel( 263 | require('assets/my-model.tflite'), 264 | 'android-gpu' 265 | ) 266 | // or 267 | const model = await loadTensorflowModel( 268 | require('assets/my-model.tflite'), 269 | 'nnapi' 270 | ) 271 | ``` 272 | 273 | > [!WARNING] 274 | > NNAPI is deprecated on Android 15. Hence, it is not recommended in future projects. 275 | > Both has similiar performance, but GPU delegate has better initial loading time. 276 | 277 | > [!NOTE] 278 | > Android does not provide support for OpenCL officially, however, most gpu vendors do provide support for it. 279 | 280 | ## Community Discord 281 | 282 | [Join the Margelo Community Discord](https://discord.gg/6CSHz2qAvA) to chat about react-native-fast-tflite or other Margelo libraries. 283 | 284 | ## Adopting at scale 285 | 286 | 287 | This library helped you? Consider sponsoring! 288 | 289 | 290 | This library is provided _as is_, I work on it in my free time. 291 | 292 | If you're integrating react-native-fast-tflite in a production app, consider [funding this project](https://github.com/sponsors/mrousavy) and contact me to receive premium enterprise support, help with issues, prioritize bugfixes, request features, help at integrating react-native-fast-tflite and/or VisionCamera Frame Processors, and more. 293 | 294 | ## Contributing 295 | 296 | 1. Clone the repo 297 | 2. Make sure you have installed Xcode CLI tools such as `gcc`, `cmake` and `python`/`python3`. See the TensorFlow documentation on what you need exactly. 298 | 3. Run `yarn bootstrap` and select `y` (yes) on all iOS and Android related questions. 299 | 4. Open the Example app and start developing 300 | - iOS: `example/ios/TfliteExample.xcworkspace` 301 | - Android: `example/android` 302 | 303 | See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow. 304 | 305 | ## License 306 | 307 | MIT 308 | -------------------------------------------------------------------------------- /android/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(VisionCameraTflite) 2 | cmake_minimum_required(VERSION 3.9.0) 3 | 4 | set (CMAKE_VERBOSE_MAKEFILE ON) 5 | set (PACKAGE_NAME "VisionCameraTflite") 6 | set (CMAKE_CXX_STANDARD 17) 7 | 8 | find_package(ReactAndroid REQUIRED CONFIG) 9 | find_package(fbjni REQUIRED CONFIG) 10 | 11 | find_library( 12 | TFLITE 13 | tensorflowlite_jni 14 | PATHS "./src/main/cpp/lib/litert/jni/${ANDROID_ABI}" 15 | NO_DEFAULT_PATH 16 | NO_CMAKE_FIND_ROOT_PATH 17 | ) 18 | 19 | find_library( 20 | TFLITE_GPU 21 | tensorflowlite_gpu_jni 22 | PATHS "./src/main/cpp/lib/litert/jni/${ANDROID_ABI}" 23 | NO_DEFAULT_PATH 24 | NO_CMAKE_FIND_ROOT_PATH 25 | ) 26 | 27 | string(APPEND CMAKE_CXX_FLAGS " -DANDROID") 28 | 29 | add_library( 30 | ${PACKAGE_NAME} 31 | SHARED 32 | ../cpp/jsi/Promise.cpp 33 | ../cpp/jsi/TypedArray.cpp 34 | ../cpp/TensorflowPlugin.cpp 35 | ../cpp/TensorHelpers.cpp 36 | src/main/cpp/Tflite.cpp 37 | ) 38 | 39 | # Specifies a path to native header files. 40 | target_include_directories( 41 | ${PACKAGE_NAME} 42 | PRIVATE 43 | "../cpp" 44 | "src/main/cpp" 45 | "src/main/cpp/lib/litert/headers" 46 | "${NODE_MODULES_DIR}/react-native/ReactCommon" 47 | "${NODE_MODULES_DIR}/react-native/ReactCommon/callinvoker" 48 | "${NODE_MODULES_DIR}/react-native/ReactAndroid/src/main/jni/react/turbomodule" # <-- CallInvokerHolder JNI wrapper 49 | ) 50 | 51 | set_target_properties(${PACKAGE_NAME} PROPERTIES LINKER_LANGUAGE CXX) 52 | 53 | target_link_libraries( 54 | ${PACKAGE_NAME} 55 | android # <-- log 56 | ReactAndroid::jsi # <-- jsi.h 57 | fbjni::fbjni # <-- fbjni.h 58 | ${TFLITE} 59 | ${TFLITE_GPU} 60 | ) 61 | 62 | if(ReactAndroid_VERSION_MINOR GREATER_EQUAL 76) 63 | target_link_libraries( 64 | ${PACKAGE_NAME} 65 | ReactAndroid::reactnative # <-- RN merged so 66 | ) 67 | else() 68 | target_link_libraries( 69 | ${PACKAGE_NAME} 70 | ReactAndroid::reactnativejni # <-- CallInvokerImpl 71 | ) 72 | endif() 73 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | import java.nio.file.Paths 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | mavenCentral() 7 | } 8 | 9 | dependencies { 10 | classpath "com.android.tools.build:gradle:7.2.2" 11 | } 12 | } 13 | 14 | def isNewArchitectureEnabled() { 15 | return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true" 16 | } 17 | 18 | apply plugin: "com.android.library" 19 | 20 | 21 | def appProject = rootProject.allprojects.find { it.plugins.hasPlugin('com.android.application') } 22 | 23 | if (isNewArchitectureEnabled()) { 24 | apply plugin: "com.facebook.react" 25 | } 26 | 27 | def getExtOrDefault(name) { 28 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["Tflite_" + name] 29 | } 30 | 31 | def getExtOrIntegerDefault(name) { 32 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["Tflite_" + name]).toInteger() 33 | } 34 | 35 | static def findNodeModules(baseDir) { 36 | def basePath = baseDir.toPath().normalize() 37 | // Node's module resolution algorithm searches up to the root directory, 38 | // after which the base path will be null 39 | while (basePath) { 40 | def nodeModulesPath = Paths.get(basePath.toString(), "node_modules") 41 | def reactNativePath = Paths.get(nodeModulesPath.toString(), "react-native") 42 | if (nodeModulesPath.toFile().exists() && reactNativePath.toFile().exists()) { 43 | return nodeModulesPath.toString() 44 | } 45 | basePath = basePath.getParent() 46 | } 47 | throw new GradleException("react-native-fast-tflite: Failed to find node_modules/ path!") 48 | } 49 | 50 | def nodeModules = findNodeModules(projectDir) 51 | 52 | android { 53 | ndkVersion getExtOrDefault("ndkVersion") 54 | compileSdkVersion getExtOrIntegerDefault("compileSdkVersion") 55 | namespace "com.tflite" 56 | 57 | defaultConfig { 58 | minSdkVersion getExtOrIntegerDefault("minSdkVersion") 59 | targetSdkVersion getExtOrIntegerDefault("targetSdkVersion") 60 | buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() 61 | externalNativeBuild { 62 | cmake { 63 | arguments "-DANDROID_STL=c++_shared", 64 | "-DNODE_MODULES_DIR=${nodeModules}", 65 | "-DIS_NEW_ARCHITECTURE_ENABLED=${isNewArchitectureEnabled()}" 66 | cppFlags "-O2 -frtti -fexceptions -Wall -fstack-protector-all" 67 | abiFilters "x86", "x86_64", "armeabi-v7a", "arm64-v8a" 68 | } 69 | } 70 | } 71 | externalNativeBuild { 72 | cmake { 73 | path "CMakeLists.txt" 74 | } 75 | } 76 | packagingOptions { 77 | excludes = [ 78 | "META-INF", 79 | "META-INF/**", 80 | "**/libc++_shared.so", 81 | "**/libfbjni.so", 82 | "**/libjsi.so", 83 | "**/libreactnative.so", 84 | "**/libreactnativejni.so", 85 | "**/libturbomodulejsijni.so", 86 | "**/libreact_nativemodule_core.so", 87 | "**/libtensorflowlite_jni.so", 88 | "**/libtensorflowlite_gpu_jni.so", 89 | ] 90 | } 91 | buildTypes { 92 | release { 93 | minifyEnabled false 94 | } 95 | } 96 | 97 | buildFeatures { 98 | prefab true 99 | } 100 | 101 | lintOptions { 102 | disable "GradleCompatible" 103 | } 104 | 105 | compileOptions { 106 | sourceCompatibility JavaVersion.VERSION_1_8 107 | targetCompatibility JavaVersion.VERSION_1_8 108 | } 109 | 110 | configurations { 111 | extractHeaders 112 | extractSO 113 | } 114 | } 115 | 116 | repositories { 117 | mavenCentral() 118 | google() 119 | } 120 | 121 | 122 | dependencies { 123 | // For < 0.71, this will be from the local maven repo 124 | // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin 125 | //noinspection GradleDynamicVersion 126 | implementation "com.facebook.react:react-native:+" 127 | 128 | // Tensorflow Lite .aar (includes C API via prefabs) 129 | implementation "com.google.ai.edge.litert:litert:1.0.1" 130 | extractSO("com.google.ai.edge.litert:litert:1.0.1") 131 | extractHeaders("com.google.ai.edge.litert:litert:1.0.1") 132 | 133 | // Tensorflow Lite GPU delegate 134 | implementation "com.google.ai.edge.litert:litert-gpu:1.0.1" 135 | extractSO("com.google.ai.edge.litert:litert-gpu:1.0.1") 136 | extractHeaders("com.google.ai.edge.litert:litert-gpu:1.0.1") 137 | } 138 | 139 | task extractAARHeaders { 140 | doLast { 141 | configurations.extractHeaders.files.each { 142 | def file = it.absoluteFile 143 | def packageName = file.name.tokenize('-')[0] 144 | copy { 145 | from zipTree(file) 146 | into "src/main/cpp/lib/$packageName/" 147 | include "**/*.h" 148 | } 149 | } 150 | } 151 | } 152 | 153 | task extractSOFiles { 154 | doLast { 155 | configurations.extractSO.files.each { 156 | def file = it.absoluteFile 157 | def packageName = file.name.tokenize('-')[0] 158 | copy { 159 | from zipTree(file) 160 | into "src/main/cpp/lib/$packageName/" 161 | include "jni/**/*.so" 162 | } 163 | } 164 | } 165 | } 166 | 167 | if (isNewArchitectureEnabled()) { 168 | react { 169 | jsRootDir = file("../src/") 170 | libraryName = "Tflite" 171 | codegenJavaPackageName = "com.tflite" 172 | } 173 | } 174 | 175 | 176 | def nativeBuildDependsOn(dependsOnTask) { 177 | def buildTasks = tasks.findAll({ task -> ( 178 | !task.name.contains("Clean") 179 | && (task.name.contains("externalNative") 180 | || task.name.contains("CMake") 181 | || task.name.contains("generateJsonModel") 182 | ) 183 | ) }) 184 | buildTasks.forEach { task -> task.dependsOn(dependsOnTask) } 185 | } 186 | 187 | afterEvaluate { 188 | nativeBuildDependsOn(extractAARHeaders) 189 | nativeBuildDependsOn(extractSOFiles) 190 | } 191 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | Tflite_kotlinVersion=1.7.0 2 | Tflite_minSdkVersion=21 3 | Tflite_targetSdkVersion=31 4 | Tflite_compileSdkVersion=31 5 | Tflite_ndkversion=21.4.7075529 6 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/src/main/cpp/Tflite.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "TensorflowPlugin.h" 8 | #include 9 | #include 10 | 11 | namespace mrousavy { 12 | 13 | JavaVM* java_machine; 14 | 15 | using namespace facebook; 16 | using namespace facebook::jni; 17 | 18 | // Java Installer 19 | struct TfliteModule : public jni::JavaClass { 20 | public: 21 | static constexpr auto kJavaDescriptor = "Lcom/tflite/TfliteModule;"; 22 | 23 | static jboolean 24 | nativeInstall(jni::alias_ref, jlong runtimePtr, 25 | jni::alias_ref jsCallInvokerHolder) { 26 | auto runtime = reinterpret_cast(runtimePtr); 27 | if (runtime == nullptr) { 28 | // Runtime was null! 29 | return false; 30 | } 31 | auto jsCallInvoker = jsCallInvokerHolder->cthis()->getCallInvoker(); 32 | 33 | auto fetchByteDataFromUrl = [](std::string url) { 34 | // Attaching Current Thread to JVM 35 | JNIEnv* env = nullptr; 36 | int getEnvStat = java_machine->GetEnv((void**)&env, JNI_VERSION_1_6); 37 | if (getEnvStat == JNI_EDETACHED) { 38 | if (java_machine->AttachCurrentThread(&env, nullptr) != 0) { 39 | throw std::runtime_error("Failed to attach thread to JVM"); 40 | } 41 | } 42 | 43 | static const auto cls = javaClassStatic(); 44 | static const auto method = 45 | cls->getStaticMethod("fetchByteDataFromUrl"); 46 | 47 | auto byteData = method(cls, url); 48 | 49 | auto size = byteData->size(); 50 | auto bytes = byteData->getRegion(0, size); 51 | void* data = malloc(size); 52 | memcpy(data, bytes.get(), size); 53 | 54 | return Buffer{.data = data, .size = size}; 55 | }; 56 | 57 | try { 58 | TensorflowPlugin::installToRuntime(*runtime, jsCallInvoker, fetchByteDataFromUrl); 59 | } catch (std::exception& exc) { 60 | return false; 61 | } 62 | 63 | return true; 64 | } 65 | 66 | static void registerNatives() { 67 | javaClassStatic()->registerNatives({ 68 | makeNativeMethod("nativeInstall", TfliteModule::nativeInstall), 69 | }); 70 | } 71 | }; 72 | 73 | } // namespace mrousavy 74 | 75 | JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) { 76 | java_machine = vm; 77 | return facebook::jni::initialize(vm, [] { mrousavy::TfliteModule::registerNatives(); }); 78 | } 79 | -------------------------------------------------------------------------------- /android/src/main/java/com/tflite/TfliteModule.java: -------------------------------------------------------------------------------- 1 | package com.tflite; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.net.Uri; 6 | import android.util.Log; 7 | 8 | import androidx.annotation.NonNull; 9 | 10 | import com.facebook.proguard.annotations.DoNotStrip; 11 | import com.facebook.react.bridge.JavaScriptContextHolder; 12 | import com.facebook.react.bridge.ReactApplicationContext; 13 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 14 | import com.facebook.react.bridge.ReactMethod; 15 | import com.facebook.react.module.annotations.ReactModule; 16 | import com.facebook.react.turbomodule.core.CallInvokerHolderImpl; 17 | 18 | import java.io.ByteArrayOutputStream; 19 | import java.io.File; 20 | import java.io.FileInputStream; 21 | import java.io.IOException; 22 | import java.io.InputStream; 23 | import java.lang.ref.WeakReference; 24 | import java.util.Objects; 25 | 26 | import okhttp3.OkHttpClient; 27 | import okhttp3.Request; 28 | import okhttp3.Response; 29 | 30 | /** @noinspection JavaJniMissingFunction*/ 31 | @ReactModule(name = TfliteModule.NAME) 32 | public class TfliteModule extends ReactContextBaseJavaModule { 33 | public static final String NAME = "Tflite"; 34 | private static WeakReference weakContext; 35 | private static final OkHttpClient client = new OkHttpClient(); 36 | 37 | public TfliteModule(ReactApplicationContext reactContext) { 38 | super(reactContext); 39 | weakContext = new WeakReference<>(reactContext); 40 | } 41 | 42 | @Override 43 | @NonNull 44 | public String getName() { 45 | return NAME; 46 | } 47 | 48 | @SuppressLint("DiscouragedApi") 49 | private static int getResourceId(Context context, String name) { 50 | return context.getResources().getIdentifier( 51 | name, 52 | "raw", 53 | context.getPackageName() 54 | ); 55 | } 56 | 57 | /** @noinspection unused*/ 58 | @DoNotStrip 59 | public static byte[] fetchByteDataFromUrl(String url) throws Exception { 60 | Log.i(NAME, "Loading byte data from URL: " + url + "..."); 61 | 62 | Uri uri = null; 63 | Integer resourceId = null; 64 | if (url.contains("://")) { 65 | Log.i(NAME, "Parsing URL..."); 66 | uri = Uri.parse(url); 67 | Log.i(NAME, "Parsed URL: " + uri.toString()); 68 | } else { 69 | Log.i(NAME, "Parsing resourceId..."); 70 | resourceId = getResourceId(weakContext.get(), url); 71 | Log.i(NAME, "Parsed resourceId: " + resourceId); 72 | } 73 | 74 | if (uri != null) { 75 | if (Objects.equals(uri.getScheme(), "file")) { 76 | // It's a file URL 77 | String path = Objects.requireNonNull(uri.getPath(), "File path cannot be null"); 78 | File file = new File(path); 79 | 80 | // Check if file exists and is readable 81 | if (!file.exists() || !file.canRead()) { 82 | throw new IOException("File does not exist or is not readable: " + path); 83 | } 84 | 85 | // Check if the file has a .tflite extension 86 | if (!file.getName().toLowerCase().endsWith(".tflite")) { 87 | throw new SecurityException("Only .tflite files are allowed"); 88 | } 89 | 90 | // Read the file 91 | try (FileInputStream stream = new FileInputStream(file)) { 92 | return getLocalFileBytes(stream, file); 93 | } catch (IOException e) { 94 | Log.e(NAME, "Error reading file: " + path, e); 95 | throw new RuntimeException("Failed to read file: " + path, e); 96 | } 97 | } else { 98 | // It's a network URL/http resource 99 | Request request = new Request.Builder().url(uri.toString()).build(); 100 | try (Response response = client.newCall(request).execute()) { 101 | if (response.isSuccessful() && response.body() != null) { 102 | return response.body().bytes(); 103 | } else { 104 | throw new RuntimeException("Response was not successful!"); 105 | } 106 | } catch (Exception ex) { 107 | Log.e(NAME, "Failed to fetch URL " + url + "!", ex); 108 | throw ex; 109 | } 110 | } 111 | } else if (resourceId != null) { 112 | // It's bundled into the Android resources/assets 113 | Context context = weakContext.get(); 114 | if (context == null) { 115 | throw new Exception("React Context has already been destroyed!"); 116 | } 117 | try (InputStream stream = context.getResources().openRawResource(resourceId)) { 118 | ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); 119 | byte[] buffer = new byte[2048]; 120 | int length; 121 | while ((length = stream.read(buffer)) != -1) { 122 | byteStream.write(buffer, 0, length); 123 | } 124 | return byteStream.toByteArray(); 125 | } 126 | } else { 127 | // It's a bird? it's a plane? not it's an error 128 | throw new Exception("Input is neither a valid URL, nor a resourceId - " + 129 | "cannot load TFLite model! (Input: " + url + ")"); 130 | } 131 | } 132 | 133 | @ReactMethod(isBlockingSynchronousMethod = true) 134 | public boolean install() { 135 | try { 136 | Log.i(NAME, "Loading C++ library..."); 137 | System.loadLibrary("VisionCameraTflite"); 138 | 139 | JavaScriptContextHolder jsContext = getReactApplicationContext().getJavaScriptContextHolder(); 140 | CallInvokerHolderImpl callInvoker = (CallInvokerHolderImpl) getReactApplicationContext().getCatalystInstance().getJSCallInvokerHolder(); 141 | 142 | Log.i(NAME, "Installing JSI Bindings for VisionCamera Tflite plugin..."); 143 | boolean successful = nativeInstall(jsContext.get(), callInvoker); 144 | if (successful) { 145 | Log.i(NAME, "Successfully installed JSI Bindings!"); 146 | return true; 147 | } else { 148 | Log.e(NAME, "Failed to install JSI Bindings for VisionCamera Tflite plugin!"); 149 | return false; 150 | } 151 | } catch (Exception exception) { 152 | Log.e(NAME, "Failed to install JSI Bindings!", exception); 153 | return false; 154 | } 155 | } 156 | 157 | private static native boolean nativeInstall(long jsiPtr, CallInvokerHolderImpl jsCallInvoker); 158 | 159 | private static byte[] getLocalFileBytes(InputStream stream, File file) throws IOException { 160 | long fileSize = file.length(); 161 | 162 | if (fileSize > Integer.MAX_VALUE) { 163 | throw new IOException("File is too large to read into memory"); 164 | } 165 | 166 | byte[] data = new byte[(int) fileSize]; 167 | 168 | int bytesRead = 0; 169 | int chunk; 170 | while (bytesRead < fileSize && (chunk = stream.read(data, bytesRead, (int)fileSize - bytesRead)) != -1) { 171 | bytesRead += chunk; 172 | } 173 | 174 | if (bytesRead != fileSize) { 175 | throw new IOException("Could not completely read file " + file.getName()); 176 | } 177 | 178 | return data; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /android/src/main/java/com/tflite/TflitePackage.java: -------------------------------------------------------------------------------- 1 | package com.tflite; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.ReactPackage; 6 | import com.facebook.react.bridge.NativeModule; 7 | import com.facebook.react.bridge.ReactApplicationContext; 8 | import com.facebook.react.uimanager.ViewManager; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | public class TflitePackage implements ReactPackage { 15 | @NonNull 16 | @Override 17 | public List createNativeModules(@NonNull ReactApplicationContext reactContext) { 18 | List modules = new ArrayList<>(); 19 | modules.add(new TfliteModule(reactContext)); 20 | return modules; 21 | } 22 | 23 | @NonNull 24 | @Override 25 | public List createViewManagers(@NonNull ReactApplicationContext reactContext) { 26 | return Collections.emptyList(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app.plugin.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/commonjs/expo-plugin/withFastTFLite') 2 | -------------------------------------------------------------------------------- /cpp/TensorHelpers.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // TensorHelpers.mm 3 | // VisionCamera 4 | // 5 | // Created by Marc Rousavy on 29.06.23. 6 | // Copyright © 2023 mrousavy. All rights reserved. 7 | // 8 | 9 | #include "TensorHelpers.h" 10 | 11 | #ifdef ANDROID 12 | #include 13 | #else 14 | #include 15 | #endif 16 | 17 | using namespace mrousavy; 18 | 19 | typedef float float32_t; 20 | typedef double float64_t; 21 | 22 | std::string dataTypeToString(TfLiteType dataType) { 23 | switch (dataType) { 24 | case kTfLiteFloat32: 25 | return "float32"; 26 | case kTfLiteFloat64: 27 | return "float64"; 28 | case kTfLiteInt4: 29 | return "int4"; 30 | case kTfLiteInt8: 31 | return "int8"; 32 | case kTfLiteInt16: 33 | return "int16"; 34 | case kTfLiteInt32: 35 | return "int32"; 36 | case kTfLiteInt64: 37 | return "int64"; 38 | case kTfLiteUInt8: 39 | return "uint8"; 40 | case kTfLiteUInt16: 41 | return "uint16"; 42 | case kTfLiteUInt32: 43 | return "uint32"; 44 | case kTfLiteUInt64: 45 | return "uint64"; 46 | case kTfLiteNoType: 47 | return "none"; 48 | case kTfLiteString: 49 | return "string"; 50 | case kTfLiteBool: 51 | return "bool"; 52 | case kTfLiteComplex64: 53 | return "complex64"; 54 | case kTfLiteComplex128: 55 | return "complex128"; 56 | case kTfLiteResource: 57 | return "resource"; 58 | case kTfLiteVariant: 59 | return "variant"; 60 | default: 61 | [[unlikely]]; 62 | return "invalid"; 63 | } 64 | } 65 | 66 | TfLiteType getTFLDataTypeForTypedArrayKind(TypedArrayKind kind) { 67 | switch (kind) { 68 | case TypedArrayKind::Int8Array: 69 | return kTfLiteInt8; 70 | case TypedArrayKind::Int16Array: 71 | return kTfLiteInt16; 72 | case TypedArrayKind::Int32Array: 73 | return kTfLiteInt32; 74 | case TypedArrayKind::Uint8Array: 75 | return kTfLiteUInt8; 76 | case TypedArrayKind::Uint8ClampedArray: 77 | return kTfLiteUInt8; 78 | case TypedArrayKind::Uint16Array: 79 | return kTfLiteUInt16; 80 | case TypedArrayKind::Uint32Array: 81 | return kTfLiteUInt32; 82 | case TypedArrayKind::Float32Array: 83 | return kTfLiteFloat32; 84 | case TypedArrayKind::Float64Array: 85 | return kTfLiteFloat64; 86 | case TypedArrayKind::BigInt64Array: 87 | return kTfLiteInt64; 88 | case TypedArrayKind::BigUint64Array: 89 | return kTfLiteUInt64; 90 | } 91 | } 92 | 93 | size_t TensorHelpers::getTFLTensorDataTypeSize(TfLiteType dataType) { 94 | switch (dataType) { 95 | case kTfLiteFloat32: 96 | return sizeof(float32_t); 97 | case kTfLiteInt32: 98 | return sizeof(int32_t); 99 | case kTfLiteUInt8: 100 | return sizeof(uint8_t); 101 | case kTfLiteInt64: 102 | return sizeof(int64_t); 103 | case kTfLiteInt16: 104 | return sizeof(int16_t); 105 | case kTfLiteInt8: 106 | return sizeof(int8_t); 107 | case kTfLiteFloat64: 108 | return sizeof(float64_t); 109 | case kTfLiteUInt64: 110 | return sizeof(uint64_t); 111 | case kTfLiteUInt32: 112 | return sizeof(uint32_t); 113 | case kTfLiteUInt16: 114 | return sizeof(uint16_t); 115 | default: 116 | [[unlikely]]; 117 | throw std::runtime_error("TFLite: Unsupported output data type! " + 118 | dataTypeToString(dataType)); 119 | } 120 | } 121 | 122 | int getTensorTotalLength(const TfLiteTensor* tensor) { 123 | int dimensions = TfLiteTensorNumDims(tensor); 124 | if (dimensions < 1) { 125 | // TODO: Handle error here, there is something wrong with this tensor... 126 | return 0; 127 | } 128 | 129 | int size = 1; 130 | for (size_t i = 0; i < dimensions; i++) { 131 | size *= TfLiteTensorDim(tensor, i); 132 | } 133 | return size; 134 | } 135 | 136 | TypedArrayBase TensorHelpers::createJSBufferForTensor(jsi::Runtime& runtime, 137 | const TfLiteTensor* tensor) { 138 | int size = getTensorTotalLength(tensor); 139 | 140 | auto dataType = TfLiteTensorType(tensor); 141 | switch (dataType) { 142 | case kTfLiteFloat32: 143 | return TypedArray(runtime, size); 144 | case kTfLiteFloat64: 145 | return TypedArray(runtime, size); 146 | case kTfLiteInt8: 147 | return TypedArray(runtime, size); 148 | case kTfLiteInt16: 149 | return TypedArray(runtime, size); 150 | case kTfLiteInt32: 151 | return TypedArray(runtime, size); 152 | case kTfLiteUInt8: 153 | return TypedArray(runtime, size); 154 | case kTfLiteUInt16: 155 | return TypedArray(runtime, size); 156 | case kTfLiteUInt32: 157 | return TypedArray(runtime, size); 158 | case kTfLiteInt64: 159 | return TypedArray(runtime, size); 160 | case kTfLiteUInt64: 161 | return TypedArray(runtime, size); 162 | default: 163 | [[unlikely]]; 164 | throw std::runtime_error("TFLite: Unsupported tensor data type! " + 165 | dataTypeToString(dataType)); 166 | } 167 | } 168 | 169 | void TensorHelpers::updateJSBufferFromTensor(jsi::Runtime& runtime, TypedArrayBase& jsBuffer, 170 | const TfLiteTensor* tensor) { 171 | auto name = std::string(TfLiteTensorName(tensor)); 172 | auto dataType = TfLiteTensorType(tensor); 173 | 174 | void* data = TfLiteTensorData(tensor); 175 | if (data == nullptr) { 176 | [[unlikely]]; 177 | throw std::runtime_error("TFLite: Failed to get data from tensor \"" + name + "\"!"); 178 | } 179 | 180 | // count of bytes, may be larger than count of numbers (e.g. for float32) 181 | int size = getTensorTotalLength(tensor) * getTFLTensorDataTypeSize(dataType); 182 | 183 | switch (dataType) { 184 | case kTfLiteFloat32: 185 | getTypedArray(runtime, jsBuffer) 186 | .as(runtime) 187 | .updateUnsafe(runtime, (float32_t*)data, size); 188 | break; 189 | case kTfLiteFloat64: 190 | getTypedArray(runtime, jsBuffer) 191 | .as(runtime) 192 | .updateUnsafe(runtime, (float64_t*)data, size); 193 | break; 194 | case kTfLiteInt8: 195 | getTypedArray(runtime, jsBuffer) 196 | .as(runtime) 197 | .updateUnsafe(runtime, (int8_t*)data, size); 198 | break; 199 | case kTfLiteInt16: 200 | getTypedArray(runtime, jsBuffer) 201 | .as(runtime) 202 | .updateUnsafe(runtime, (int16_t*)data, size); 203 | break; 204 | case kTfLiteInt32: 205 | getTypedArray(runtime, jsBuffer) 206 | .as(runtime) 207 | .updateUnsafe(runtime, (int32_t*)data, size); 208 | break; 209 | case kTfLiteUInt8: 210 | getTypedArray(runtime, jsBuffer) 211 | .as(runtime) 212 | .updateUnsafe(runtime, (uint8_t*)data, size); 213 | break; 214 | case kTfLiteUInt16: 215 | getTypedArray(runtime, jsBuffer) 216 | .as(runtime) 217 | .updateUnsafe(runtime, (uint16_t*)data, size); 218 | break; 219 | case kTfLiteUInt32: 220 | getTypedArray(runtime, jsBuffer) 221 | .as(runtime) 222 | .updateUnsafe(runtime, (uint32_t*)data, size); 223 | break; 224 | case kTfLiteInt64: 225 | getTypedArray(runtime, jsBuffer) 226 | .as(runtime) 227 | .updateUnsafe(runtime, (int64_t*)data, size); 228 | break; 229 | case kTfLiteUInt64: 230 | getTypedArray(runtime, jsBuffer) 231 | .as(runtime) 232 | .updateUnsafe(runtime, (uint64_t*)data, size); 233 | break; 234 | default: 235 | [[unlikely]]; 236 | throw jsi::JSError(runtime, 237 | "TFLite: Unsupported output data type! " + dataTypeToString(dataType)); 238 | } 239 | } 240 | 241 | void TensorHelpers::updateTensorFromJSBuffer(jsi::Runtime& runtime, TfLiteTensor* tensor, 242 | TypedArrayBase& jsBuffer) { 243 | #if DEBUG 244 | // Validate data-type 245 | TypedArrayKind kind = jsBuffer.getKind(runtime); 246 | TfLiteType receivedType = getTFLDataTypeForTypedArrayKind(kind); 247 | TfLiteType expectedType = TfLiteTensorType(tensor); 248 | if (receivedType != expectedType) { 249 | [[unlikely]]; 250 | throw std::runtime_error("TFLite: Invalid input type! Model expected " + 251 | dataTypeToString(expectedType) + ", but received " + 252 | dataTypeToString(receivedType) + "!"); 253 | } 254 | #endif 255 | 256 | std::string name = TfLiteTensorName(tensor); 257 | jsi::ArrayBuffer buffer = jsBuffer.getBuffer(runtime); 258 | 259 | #if DEBUG 260 | // Validate size 261 | int inputBufferSize = buffer.size(runtime); 262 | int tensorSize = getTensorTotalLength(tensor) * getTFLTensorDataTypeSize(tensor->type); 263 | if (tensorSize != inputBufferSize) { 264 | [[unlikely]]; 265 | throw std::runtime_error("TFLite: Input Buffer size (" + std::to_string(inputBufferSize) + 266 | ") does not match the Input Tensor's expected size (" + 267 | std::to_string(tensorSize) + 268 | ")! Make sure to resize the input values accordingly."); 269 | } 270 | #endif 271 | 272 | TfLiteTensorCopyFromBuffer(tensor, buffer.data(runtime) + jsBuffer.byteOffset(runtime), 273 | buffer.size(runtime)); 274 | } 275 | 276 | jsi::Object TensorHelpers::tensorToJSObject(jsi::Runtime& runtime, const TfLiteTensor* tensor) { 277 | jsi::Object result(runtime); 278 | result.setProperty(runtime, "name", 279 | jsi::String::createFromUtf8(runtime, TfLiteTensorName(tensor))); 280 | result.setProperty( 281 | runtime, "dataType", 282 | jsi::String::createFromUtf8(runtime, dataTypeToString(TfLiteTensorType(tensor)))); 283 | 284 | int dimensions = TfLiteTensorNumDims(tensor); 285 | jsi::Array shapeArray(runtime, dimensions); 286 | for (size_t i = 0; i < dimensions; i++) { 287 | int size = TfLiteTensorDim(tensor, i); 288 | shapeArray.setValueAtIndex(runtime, i, jsi::Value(size)); 289 | } 290 | result.setProperty(runtime, "shape", shapeArray); 291 | 292 | return result; 293 | } 294 | -------------------------------------------------------------------------------- /cpp/TensorHelpers.h: -------------------------------------------------------------------------------- 1 | // 2 | // TensorHelpers.h 3 | // VisionCamera 4 | // 5 | // Created by Marc Rousavy on 29.06.23. 6 | // Copyright © 2023 mrousavy. All rights reserved. 7 | // 8 | 9 | #pragma once 10 | 11 | #include "jsi/TypedArray.h" 12 | #include 13 | 14 | #ifdef ANDROID 15 | #include 16 | #else 17 | #include 18 | #endif 19 | 20 | using namespace facebook; 21 | 22 | class TensorHelpers { 23 | public: 24 | /** 25 | Get the size of a value of the given `TFLTensorDataType`. 26 | */ 27 | static size_t getTFLTensorDataTypeSize(TfLiteType dataType); 28 | /** 29 | Create a pre-allocated TypedArray for the given TFLTensor. 30 | */ 31 | static mrousavy::TypedArrayBase createJSBufferForTensor(jsi::Runtime& runtime, 32 | const TfLiteTensor* tensor); 33 | /** 34 | Copies the Tensor's data into a jsi::TypedArray and correctly casts to the given type. 35 | */ 36 | static void updateJSBufferFromTensor(jsi::Runtime& runtime, mrousavy::TypedArrayBase& jsBuffer, 37 | const TfLiteTensor* outputTensor); 38 | /** 39 | Copies the data from the jsi::TypedArray into the given input buffer. 40 | */ 41 | static void updateTensorFromJSBuffer(jsi::Runtime& runtime, TfLiteTensor* inputTensor, 42 | mrousavy::TypedArrayBase& jsBuffer); 43 | /** 44 | Convert a tensor to a JS Object 45 | */ 46 | static jsi::Object tensorToJSObject(jsi::Runtime& runtime, const TfLiteTensor* tensor); 47 | }; 48 | -------------------------------------------------------------------------------- /cpp/TensorflowPlugin.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // TensorflowPlugin.m 3 | // VisionCamera 4 | // 5 | // Created by Marc Rousavy on 26.06.23. 6 | // Copyright © 2023 mrousavy. All rights reserved. 7 | // 8 | 9 | #include "TensorflowPlugin.h" 10 | 11 | #include "TensorHelpers.h" 12 | #include "jsi/Promise.h" 13 | #include "jsi/TypedArray.h" 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #ifdef ANDROID 21 | #include 22 | #include 23 | #include 24 | #else 25 | #include 26 | 27 | #if FAST_TFLITE_ENABLE_CORE_ML 28 | #include 29 | #endif 30 | #endif 31 | 32 | using namespace facebook; 33 | using namespace mrousavy; 34 | 35 | void log(std::string string...) { 36 | // TODO: Figure out how to log to console 37 | } 38 | 39 | void TensorflowPlugin::installToRuntime(jsi::Runtime& runtime, 40 | std::shared_ptr callInvoker, 41 | FetchURLFunc fetchURL) { 42 | 43 | auto func = jsi::Function::createFromHostFunction( 44 | runtime, jsi::PropNameID::forAscii(runtime, "__loadTensorflowModel"), 1, 45 | [=](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, 46 | size_t count) -> jsi::Value { 47 | auto start = std::chrono::steady_clock::now(); 48 | auto modelPath = arguments[0].asString(runtime).utf8(runtime); 49 | 50 | log("Loading TensorFlow Lite Model from \"%s\"...", modelPath.c_str()); 51 | 52 | // TODO: Figure out how to use Metal/CoreML delegates 53 | Delegate delegateType = Delegate::Default; 54 | if (count > 1 && arguments[1].isString()) { 55 | // user passed a custom delegate command 56 | auto delegate = arguments[1].asString(runtime).utf8(runtime); 57 | if (delegate == "core-ml") { 58 | delegateType = Delegate::CoreML; 59 | } else if (delegate == "metal") { 60 | delegateType = Delegate::Metal; 61 | } else if (delegate == "nnapi") { 62 | delegateType = Delegate::NnApi; 63 | } else if (delegate == "android-gpu") { 64 | delegateType = Delegate::AndroidGPU; 65 | } else { 66 | delegateType = Delegate::Default; 67 | } 68 | } 69 | 70 | auto promise = Promise::createPromise(runtime, [=, &runtime]( 71 | std::shared_ptr promise) { 72 | // Launch async thread 73 | std::async(std::launch::async, [=, &runtime]() { 74 | try { 75 | // Fetch model from URL (JS bundle) 76 | Buffer buffer = fetchURL(modelPath); 77 | 78 | // Load Model into Tensorflow 79 | auto model = TfLiteModelCreate(buffer.data, buffer.size); 80 | if (model == nullptr) { 81 | callInvoker->invokeAsync( 82 | [=]() { promise->reject("Failed to load model from \"" + modelPath + "\"!"); }); 83 | return; 84 | } 85 | 86 | // Create TensorFlow Interpreter 87 | auto options = TfLiteInterpreterOptionsCreate(); 88 | 89 | switch (delegateType) { 90 | case Delegate::CoreML: { 91 | #if FAST_TFLITE_ENABLE_CORE_ML 92 | TfLiteCoreMlDelegateOptions delegateOptions; 93 | auto delegate = TfLiteCoreMlDelegateCreate(&delegateOptions); 94 | TfLiteInterpreterOptionsAddDelegate(options, delegate); 95 | break; 96 | #else 97 | callInvoker->invokeAsync([=]() { 98 | promise->reject("CoreML Delegate is not enabled! Set $EnableCoreMLDelegate to true in Podfile and rebuild."); 99 | }); 100 | return; 101 | #endif 102 | } 103 | case Delegate::Metal: { 104 | callInvoker->invokeAsync( 105 | [=]() { promise->reject("Metal Delegate is not supported!"); }); 106 | return; 107 | } 108 | #ifdef ANDROID 109 | case Delegate::NnApi: { 110 | TfLiteNnapiDelegateOptions delegateOptions = TfLiteNnapiDelegateOptionsDefault(); 111 | auto delegate = TfLiteNnapiDelegateCreate(&delegateOptions); 112 | TfLiteInterpreterOptionsAddDelegate(options, delegate); 113 | break; 114 | } 115 | case Delegate::AndroidGPU: { 116 | TfLiteGpuDelegateOptionsV2 delegateOptions = TfLiteGpuDelegateOptionsV2Default(); 117 | auto delegate = TfLiteGpuDelegateV2Create(&delegateOptions); 118 | TfLiteInterpreterOptionsAddDelegate(options, delegate); 119 | break; 120 | } 121 | #else 122 | case Delegate::NnApi: { 123 | callInvoker->invokeAsync([=]() { 124 | promise->reject("Nnapi Delegate is only supported on Android!"); 125 | }); 126 | } 127 | case Delegate::AndroidGPU: { 128 | callInvoker->invokeAsync([=]() { 129 | promise->reject("Android-Gpu Delegate is not supported on Android!"); 130 | }); 131 | } 132 | #endif 133 | default: { 134 | // use default CPU delegate. 135 | } 136 | } 137 | 138 | auto interpreter = TfLiteInterpreterCreate(model, options); 139 | 140 | if (interpreter == nullptr) { 141 | callInvoker->invokeAsync([=]() { 142 | promise->reject("Failed to create TFLite interpreter from model \"" + modelPath + 143 | "\"!"); 144 | }); 145 | return; 146 | } 147 | 148 | // Initialize Model and allocate memory buffers 149 | auto plugin = std::make_shared(interpreter, buffer, delegateType, 150 | callInvoker); 151 | 152 | callInvoker->invokeAsync([=, &runtime]() { 153 | auto result = jsi::Object::createFromHostObject(runtime, plugin); 154 | promise->resolve(std::move(result)); 155 | }); 156 | 157 | auto end = std::chrono::steady_clock::now(); 158 | log("Successfully loaded Tensorflow Model in %i ms!", 159 | std::chrono::duration_cast(end - start).count()); 160 | } catch (std::exception& error) { 161 | std::string message = error.what(); 162 | callInvoker->invokeAsync([=]() { promise->reject(message); }); 163 | } 164 | }); 165 | }); 166 | return promise; 167 | }); 168 | 169 | runtime.global().setProperty(runtime, "__loadTensorflowModel", func); 170 | } 171 | 172 | std::string tfLiteStatusToString(TfLiteStatus status) { 173 | switch (status) { 174 | case kTfLiteOk: 175 | return "ok"; 176 | case kTfLiteError: 177 | return "error"; 178 | case kTfLiteDelegateError: 179 | return "delegate-error"; 180 | case kTfLiteApplicationError: 181 | return "application-error"; 182 | case kTfLiteDelegateDataNotFound: 183 | return "delegate-data-not-found"; 184 | case kTfLiteDelegateDataWriteError: 185 | return "delegate-data-write-error"; 186 | case kTfLiteDelegateDataReadError: 187 | return "delegate-data-read-error"; 188 | case kTfLiteUnresolvedOps: 189 | return "unresolved-ops"; 190 | case kTfLiteCancelled: 191 | return "cancelled"; 192 | default: 193 | return "unknown"; 194 | } 195 | } 196 | 197 | TensorflowPlugin::TensorflowPlugin(TfLiteInterpreter* interpreter, Buffer model, Delegate delegate, 198 | std::shared_ptr callInvoker) 199 | : _interpreter(interpreter), _delegate(delegate), _model(model), _callInvoker(callInvoker) { 200 | // Allocate memory for the model's input/output `TFLTensor`s. 201 | TfLiteStatus status = TfLiteInterpreterAllocateTensors(_interpreter); 202 | if (status != kTfLiteOk) { 203 | [[unlikely]]; 204 | throw std::runtime_error( 205 | "TFLite: Failed to allocate memory for input/output tensors! Status: " + 206 | tfLiteStatusToString(status)); 207 | } 208 | 209 | log("Successfully created Tensorflow Plugin!"); 210 | } 211 | 212 | TensorflowPlugin::~TensorflowPlugin() { 213 | if (_model.data != nullptr) { 214 | free(_model.data); 215 | _model.data = nullptr; 216 | _model.size = 0; 217 | } 218 | if (_interpreter != nullptr) { 219 | TfLiteInterpreterDelete(_interpreter); 220 | _interpreter = nullptr; 221 | } 222 | } 223 | 224 | std::shared_ptr 225 | TensorflowPlugin::getOutputArrayForTensor(jsi::Runtime& runtime, const TfLiteTensor* tensor) { 226 | auto name = std::string(TfLiteTensorName(tensor)); 227 | if (_outputBuffers.find(name) == _outputBuffers.end()) { 228 | _outputBuffers[name] = 229 | std::make_shared(TensorHelpers::createJSBufferForTensor(runtime, tensor)); 230 | } 231 | return _outputBuffers[name]; 232 | } 233 | 234 | void TensorflowPlugin::copyInputBuffers(jsi::Runtime& runtime, jsi::Object inputValues) { 235 | // Input has to be array in input tensor size 236 | #if DEBUG 237 | if (!inputValues.isArray(runtime)) { 238 | [[unlikely]]; 239 | throw jsi::JSError(runtime, 240 | "TFLite: Input Values must be an array, one item for each input tensor!"); 241 | } 242 | #endif 243 | 244 | jsi::Array array = inputValues.asArray(runtime); 245 | size_t count = array.size(runtime); 246 | if (count != TfLiteInterpreterGetInputTensorCount(_interpreter)) { 247 | [[unlikely]]; 248 | throw jsi::JSError(runtime, 249 | "TFLite: Input Values have different size than there are input tensors!"); 250 | } 251 | 252 | for (size_t i = 0; i < count; i++) { 253 | TfLiteTensor* tensor = TfLiteInterpreterGetInputTensor(_interpreter, i); 254 | jsi::Object object = array.getValueAtIndex(runtime, i).asObject(runtime); 255 | 256 | #if DEBUG 257 | if (!isTypedArray(runtime, object)) { 258 | [[unlikely]]; 259 | throw jsi::JSError( 260 | runtime, 261 | "TFLite: Input value is not a TypedArray! (Uint8Array, Uint16Array, Float32Array, etc.)"); 262 | } 263 | #endif 264 | 265 | TypedArrayBase inputBuffer = getTypedArray(runtime, std::move(object)); 266 | TensorHelpers::updateTensorFromJSBuffer(runtime, tensor, inputBuffer); 267 | } 268 | } 269 | 270 | jsi::Value TensorflowPlugin::copyOutputBuffers(jsi::Runtime& runtime) { 271 | // Copy output to result process the inference results. 272 | int outputTensorsCount = TfLiteInterpreterGetOutputTensorCount(_interpreter); 273 | jsi::Array result(runtime, outputTensorsCount); 274 | for (size_t i = 0; i < outputTensorsCount; i++) { 275 | const TfLiteTensor* outputTensor = TfLiteInterpreterGetOutputTensor(_interpreter, i); 276 | auto outputBuffer = getOutputArrayForTensor(runtime, outputTensor); 277 | TensorHelpers::updateJSBufferFromTensor(runtime, *outputBuffer, outputTensor); 278 | result.setValueAtIndex(runtime, i, *outputBuffer); 279 | } 280 | return result; 281 | } 282 | 283 | void TensorflowPlugin::run() { 284 | // Run Model 285 | TfLiteStatus status = TfLiteInterpreterInvoke(_interpreter); 286 | if (status != kTfLiteOk) { 287 | [[unlikely]]; 288 | throw std::runtime_error("TFLite: Failed to run TFLite Model! Status: " + 289 | tfLiteStatusToString(status)); 290 | } 291 | } 292 | 293 | jsi::Value TensorflowPlugin::get(jsi::Runtime& runtime, const jsi::PropNameID& propNameId) { 294 | auto propName = propNameId.utf8(runtime); 295 | 296 | if (propName == "runSync") { 297 | return jsi::Function::createFromHostFunction( 298 | runtime, jsi::PropNameID::forAscii(runtime, "runModel"), 1, 299 | [=](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, 300 | size_t count) -> jsi::Value { 301 | // 1. 302 | copyInputBuffers(runtime, arguments[0].asObject(runtime)); 303 | // 2. 304 | this->run(); 305 | // 3. 306 | return copyOutputBuffers(runtime); 307 | }); 308 | } else if (propName == "run") { 309 | return jsi::Function::createFromHostFunction( 310 | runtime, jsi::PropNameID::forAscii(runtime, "runModel"), 1, 311 | [=](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, 312 | size_t count) -> jsi::Value { 313 | // 1. 314 | copyInputBuffers(runtime, arguments[0].asObject(runtime)); 315 | auto promise = 316 | Promise::createPromise(runtime, [=, &runtime](std::shared_ptr promise) { 317 | std::async(std::launch::async, [=, &runtime]() { 318 | // 2. 319 | try { 320 | this->run(); 321 | 322 | this->_callInvoker->invokeAsync([=, &runtime]() { 323 | // 3. 324 | auto result = this->copyOutputBuffers(runtime); 325 | promise->resolve(std::move(result)); 326 | }); 327 | } catch (std::exception& error) { 328 | promise->reject(error.what()); 329 | } 330 | }); 331 | }); 332 | return promise; 333 | }); 334 | } else if (propName == "inputs") { 335 | int size = TfLiteInterpreterGetInputTensorCount(_interpreter); 336 | jsi::Array tensors(runtime, size); 337 | for (size_t i = 0; i < size; i++) { 338 | TfLiteTensor* tensor = TfLiteInterpreterGetInputTensor(_interpreter, i); 339 | if (tensor == nullptr) { 340 | [[unlikely]]; 341 | throw jsi::JSError(runtime, 342 | "TFLite: Failed to get input tensor " + std::to_string(i) + "!"); 343 | } 344 | 345 | jsi::Object object = TensorHelpers::tensorToJSObject(runtime, tensor); 346 | tensors.setValueAtIndex(runtime, i, object); 347 | } 348 | return tensors; 349 | } else if (propName == "outputs") { 350 | int size = TfLiteInterpreterGetOutputTensorCount(_interpreter); 351 | jsi::Array tensors(runtime, size); 352 | for (size_t i = 0; i < size; i++) { 353 | const TfLiteTensor* tensor = TfLiteInterpreterGetOutputTensor(_interpreter, i); 354 | if (tensor == nullptr) { 355 | [[unlikely]]; 356 | throw jsi::JSError(runtime, 357 | "TFLite: Failed to get output tensor " + std::to_string(i) + "!"); 358 | } 359 | 360 | jsi::Object object = TensorHelpers::tensorToJSObject(runtime, tensor); 361 | tensors.setValueAtIndex(runtime, i, object); 362 | } 363 | return tensors; 364 | } else if (propName == "delegate") { 365 | switch (_delegate) { 366 | case Delegate::Default: 367 | return jsi::String::createFromUtf8(runtime, "default"); 368 | case Delegate::CoreML: 369 | return jsi::String::createFromUtf8(runtime, "core-ml"); 370 | case Delegate::Metal: 371 | return jsi::String::createFromUtf8(runtime, "metal"); 372 | case Delegate::NnApi: 373 | return jsi::String::createFromUtf8(runtime, "nnapi"); 374 | case Delegate::AndroidGPU: 375 | return jsi::String::createFromUtf8(runtime, "android-gpu"); 376 | } 377 | } 378 | 379 | return jsi::HostObject::get(runtime, propNameId); 380 | } 381 | 382 | std::vector TensorflowPlugin::getPropertyNames(jsi::Runtime& runtime) { 383 | std::vector result; 384 | result.push_back(jsi::PropNameID::forAscii(runtime, "run")); 385 | result.push_back(jsi::PropNameID::forAscii(runtime, "runSync")); 386 | result.push_back(jsi::PropNameID::forAscii(runtime, "inputs")); 387 | result.push_back(jsi::PropNameID::forAscii(runtime, "outputs")); 388 | result.push_back(jsi::PropNameID::forAscii(runtime, "delegate")); 389 | return result; 390 | } 391 | -------------------------------------------------------------------------------- /cpp/TensorflowPlugin.h: -------------------------------------------------------------------------------- 1 | // 2 | // TensorflowPlugin.h 3 | // VisionCamera 4 | // 5 | // Created by Marc Rousavy on 26.06.23. 6 | // Copyright © 2023 mrousavy. All rights reserved. 7 | // 8 | 9 | #pragma once 10 | 11 | #include "jsi/TypedArray.h" 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #ifdef ANDROID 18 | #include 19 | #include 20 | #else 21 | #include 22 | #include 23 | #endif 24 | 25 | using namespace facebook; 26 | using namespace mrousavy; 27 | 28 | struct Buffer { 29 | void* data; 30 | size_t size; 31 | }; 32 | typedef std::function FetchURLFunc; 33 | 34 | class TensorflowPlugin : public jsi::HostObject { 35 | public: 36 | // TFL Delegate Type 37 | enum Delegate { Default, Metal, CoreML, NnApi, AndroidGPU }; 38 | 39 | public: 40 | explicit TensorflowPlugin(TfLiteInterpreter* interpreter, Buffer model, Delegate delegate, 41 | std::shared_ptr callInvoker); 42 | ~TensorflowPlugin(); 43 | 44 | jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& name) override; 45 | std::vector getPropertyNames(jsi::Runtime& runtime) override; 46 | 47 | static void installToRuntime(jsi::Runtime& runtime, 48 | std::shared_ptr callInvoker, 49 | FetchURLFunc fetchURL); 50 | 51 | private: 52 | void copyInputBuffers(jsi::Runtime& runtime, jsi::Object inputValues); 53 | void run(); 54 | jsi::Value copyOutputBuffers(jsi::Runtime& runtime); 55 | 56 | std::shared_ptr getOutputArrayForTensor(jsi::Runtime& runtime, 57 | const TfLiteTensor* tensor); 58 | 59 | private: 60 | TfLiteInterpreter* _interpreter = nullptr; 61 | Delegate _delegate = Delegate::Default; 62 | Buffer _model; 63 | std::shared_ptr _callInvoker; 64 | 65 | std::unordered_map> _outputBuffers; 66 | }; 67 | -------------------------------------------------------------------------------- /cpp/jsi/Promise.cpp: -------------------------------------------------------------------------------- 1 | #include "Promise.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace mrousavy { 8 | 9 | using namespace facebook; 10 | 11 | Promise::Promise(jsi::Runtime& runtime, jsi::Value resolver, jsi::Value rejecter) 12 | : runtime(runtime), _resolver(std::move(resolver)), _rejecter(std::move(rejecter)) {} 13 | 14 | jsi::Value Promise::createPromise(jsi::Runtime& runtime, 15 | std::function promise)> run) { 16 | // Get Promise ctor from global 17 | auto promiseCtor = runtime.global().getPropertyAsFunction(runtime, "Promise"); 18 | 19 | auto promiseCallback = jsi::Function::createFromHostFunction( 20 | runtime, jsi::PropNameID::forUtf8(runtime, "PromiseCallback"), 2, 21 | [=](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments, 22 | size_t count) -> jsi::Value { 23 | // Call function 24 | auto promise = std::make_shared(runtime, arguments[0].asObject(runtime), 25 | arguments[1].asObject(runtime)); 26 | run(promise); 27 | 28 | return jsi::Value::undefined(); 29 | }); 30 | 31 | return promiseCtor.callAsConstructor(runtime, promiseCallback); 32 | } 33 | 34 | void Promise::resolve(jsi::Value&& result) { 35 | _resolver.asObject(runtime).asFunction(runtime).call(runtime, std::move(result)); 36 | } 37 | 38 | void Promise::reject(std::string message) { 39 | jsi::JSError error(runtime, message); 40 | _rejecter.asObject(runtime).asFunction(runtime).call(runtime, error.value()); 41 | } 42 | 43 | } // namespace mrousavy 44 | -------------------------------------------------------------------------------- /cpp/jsi/Promise.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #ifdef ANDROID 8 | #include 9 | #else 10 | #include 11 | #endif 12 | 13 | namespace mrousavy { 14 | 15 | using namespace facebook; 16 | 17 | class Promise { 18 | public: 19 | Promise(jsi::Runtime& runtime, jsi::Value resolver, jsi::Value rejecter); 20 | 21 | void resolve(jsi::Value&& result); 22 | void reject(std::string error); 23 | 24 | public: 25 | jsi::Runtime& runtime; 26 | 27 | private: 28 | jsi::Value _resolver; 29 | jsi::Value _rejecter; 30 | 31 | public: 32 | /** 33 | Create a new Promise and runs the given `run` function. 34 | */ 35 | static jsi::Value createPromise(jsi::Runtime& runtime, 36 | std::function promise)> run); 37 | }; 38 | 39 | } // namespace mrousavy 40 | -------------------------------------------------------------------------------- /cpp/jsi/TypedArray.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // TypedArray.cpp 3 | // VisionCamera 4 | // 5 | // Created by Marc Rousavy on 21.02.23. 6 | // Copyright © 2023 mrousavy. All rights reserved. 7 | // 8 | 9 | // Copied & Adapted from 10 | // https://github.com/expo/expo/blob/main/packages/expo-gl/common/EXTypedArrayApi.cpp Credits to 11 | // Expo 12 | 13 | #include "TypedArray.h" 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | namespace mrousavy { 23 | 24 | template using ContentType = typename typedArrayTypeMap::type; 25 | 26 | enum class Prop { 27 | Buffer, // "buffer" 28 | Constructor, // "constructor" 29 | Name, // "name" 30 | Proto, // "__proto__" 31 | Length, // "length" 32 | ByteLength, // "byteLength" 33 | ByteOffset, // "offset" 34 | IsView, // "isView" 35 | ArrayBuffer, // "ArrayBuffer" 36 | Int8Array, // "Int8Array" 37 | Int16Array, // "Int16Array" 38 | Int32Array, // "Int32Array" 39 | Uint8Array, // "Uint8Array" 40 | Uint8ClampedArray, // "Uint8ClampedArray" 41 | Uint16Array, // "Uint16Array" 42 | Uint32Array, // "Uint32Array" 43 | Float32Array, // "Float32Array" 44 | Float64Array, // "Float64Array" 45 | BigInt64Array, // "BigInt64Array" 46 | BigUint64Array, // "BigUint64Array" 47 | }; 48 | 49 | class PropNameIDCache { 50 | public: 51 | const jsi::PropNameID& get(jsi::Runtime& runtime, Prop prop) { 52 | auto key = reinterpret_cast(&runtime); 53 | if (this->props.find(key) == this->props.end()) { 54 | this->props[key] = std::unordered_map>(); 55 | } 56 | if (!this->props[key][prop]) { 57 | this->props[key][prop] = std::make_unique(createProp(runtime, prop)); 58 | } 59 | return *(this->props[key][prop]); 60 | } 61 | 62 | const jsi::PropNameID& getConstructorNameProp(jsi::Runtime& runtime, TypedArrayKind kind); 63 | 64 | void invalidate(uintptr_t key) { 65 | if (props.find(key) != props.end()) { 66 | props[key].clear(); 67 | } 68 | } 69 | 70 | private: 71 | std::unordered_map>> props; 72 | 73 | jsi::PropNameID createProp(jsi::Runtime& runtime, Prop prop); 74 | }; 75 | 76 | PropNameIDCache propNameIDCache; 77 | 78 | InvalidateCacheOnDestroy::InvalidateCacheOnDestroy(jsi::Runtime& runtime) { 79 | key = reinterpret_cast(&runtime); 80 | } 81 | InvalidateCacheOnDestroy::~InvalidateCacheOnDestroy() { 82 | propNameIDCache.invalidate(key); 83 | } 84 | 85 | TypedArrayKind getTypedArrayKindForName(const std::string& name); 86 | 87 | TypedArrayBase::TypedArrayBase(jsi::Runtime& runtime, size_t size, TypedArrayKind kind) 88 | : TypedArrayBase( 89 | runtime, runtime.global() 90 | .getProperty(runtime, propNameIDCache.getConstructorNameProp(runtime, kind)) 91 | .asObject(runtime) 92 | .asFunction(runtime) 93 | .callAsConstructor(runtime, {static_cast(size)}) 94 | .asObject(runtime)) {} 95 | 96 | TypedArrayBase::TypedArrayBase(jsi::Runtime& runtime, const jsi::Object& obj) 97 | : jsi::Object(jsi::Value(runtime, obj).asObject(runtime)) {} 98 | 99 | TypedArrayKind TypedArrayBase::getKind(jsi::Runtime& runtime) const { 100 | auto constructorName = this->getProperty(runtime, propNameIDCache.get(runtime, Prop::Constructor)) 101 | .asObject(runtime) 102 | .getProperty(runtime, propNameIDCache.get(runtime, Prop::Name)) 103 | .asString(runtime) 104 | .utf8(runtime); 105 | return getTypedArrayKindForName(constructorName); 106 | } 107 | 108 | size_t TypedArrayBase::size(jsi::Runtime& runtime) const { 109 | return getProperty(runtime, propNameIDCache.get(runtime, Prop::Length)).asNumber(); 110 | } 111 | 112 | size_t TypedArrayBase::length(jsi::Runtime& runtime) const { 113 | return getProperty(runtime, propNameIDCache.get(runtime, Prop::Length)).asNumber(); 114 | } 115 | 116 | size_t TypedArrayBase::byteLength(jsi::Runtime& runtime) const { 117 | return getProperty(runtime, propNameIDCache.get(runtime, Prop::ByteLength)).asNumber(); 118 | } 119 | 120 | size_t TypedArrayBase::byteOffset(jsi::Runtime& runtime) const { 121 | return getProperty(runtime, propNameIDCache.get(runtime, Prop::ByteOffset)).asNumber(); 122 | } 123 | 124 | bool TypedArrayBase::hasBuffer(jsi::Runtime& runtime) const { 125 | auto buffer = getProperty(runtime, propNameIDCache.get(runtime, Prop::Buffer)); 126 | return buffer.isObject() && buffer.asObject(runtime).isArrayBuffer(runtime); 127 | } 128 | 129 | std::vector TypedArrayBase::toVector(jsi::Runtime& runtime) { 130 | auto start = reinterpret_cast(getBuffer(runtime).data(runtime) + byteOffset(runtime)); 131 | auto end = start + byteLength(runtime); 132 | return std::vector(start, end); 133 | } 134 | 135 | jsi::ArrayBuffer TypedArrayBase::getBuffer(jsi::Runtime& runtime) const { 136 | auto buffer = getProperty(runtime, propNameIDCache.get(runtime, Prop::Buffer)); 137 | if (buffer.isObject() && buffer.asObject(runtime).isArrayBuffer(runtime)) { 138 | return buffer.asObject(runtime).getArrayBuffer(runtime); 139 | } else { 140 | throw std::runtime_error("no ArrayBuffer attached"); 141 | } 142 | } 143 | 144 | bool isTypedArray(jsi::Runtime& runtime, const jsi::Object& jsObj) { 145 | auto jsVal = runtime.global() 146 | .getProperty(runtime, propNameIDCache.get(runtime, Prop::ArrayBuffer)) 147 | .asObject(runtime) 148 | .getProperty(runtime, propNameIDCache.get(runtime, Prop::IsView)) 149 | .asObject(runtime) 150 | .asFunction(runtime) 151 | .callWithThis(runtime, runtime.global(), {jsi::Value(runtime, jsObj)}); 152 | if (jsVal.isBool()) { 153 | return jsVal.getBool(); 154 | } else { 155 | throw std::runtime_error("value is not a boolean"); 156 | } 157 | } 158 | 159 | TypedArrayBase getTypedArray(jsi::Runtime& runtime, const jsi::Object& jsObj) { 160 | auto jsVal = runtime.global() 161 | .getProperty(runtime, propNameIDCache.get(runtime, Prop::ArrayBuffer)) 162 | .asObject(runtime) 163 | .getProperty(runtime, propNameIDCache.get(runtime, Prop::IsView)) 164 | .asObject(runtime) 165 | .asFunction(runtime) 166 | .callWithThis(runtime, runtime.global(), {jsi::Value(runtime, jsObj)}); 167 | if (jsVal.isBool()) { 168 | return TypedArrayBase(runtime, jsObj); 169 | } else { 170 | throw std::runtime_error("value is not a boolean"); 171 | } 172 | } 173 | 174 | std::vector arrayBufferToVector(jsi::Runtime& runtime, jsi::Object& jsObj) { 175 | if (!jsObj.isArrayBuffer(runtime)) { 176 | throw std::runtime_error("Object is not an ArrayBuffer"); 177 | } 178 | auto jsArrayBuffer = jsObj.getArrayBuffer(runtime); 179 | 180 | uint8_t* dataBlock = jsArrayBuffer.data(runtime); 181 | size_t blockSize = 182 | jsArrayBuffer.getProperty(runtime, propNameIDCache.get(runtime, Prop::ByteLength)).asNumber(); 183 | return std::vector(dataBlock, dataBlock + blockSize); 184 | } 185 | 186 | void arrayBufferUpdate(jsi::Runtime& runtime, jsi::ArrayBuffer& buffer, std::vector data, 187 | size_t offset) { 188 | uint8_t* dataBlock = buffer.data(runtime); 189 | size_t blockSize = buffer.size(runtime); 190 | if (data.size() > blockSize) { 191 | throw jsi::JSError(runtime, "ArrayBuffer is to small to fit data"); 192 | } 193 | std::copy(data.begin(), data.end(), dataBlock + offset); 194 | } 195 | 196 | template 197 | TypedArray::TypedArray(jsi::Runtime& runtime, size_t size) : TypedArrayBase(runtime, size, T) {} 198 | 199 | template 200 | TypedArray::TypedArray(jsi::Runtime& runtime, std::vector> data) 201 | : TypedArrayBase(runtime, data.size(), T) { 202 | update(runtime, data); 203 | } 204 | 205 | template 206 | TypedArray::TypedArray(jsi::Runtime& runtime, ContentType* dataToCopy, size_t size) 207 | : TypedArrayBase(runtime, size, T) { 208 | updateUnsafe(runtime, dataToCopy, size); 209 | } 210 | 211 | template 212 | TypedArray::TypedArray(TypedArrayBase&& base) : TypedArrayBase(std::move(base)) {} 213 | 214 | template 215 | std::vector> TypedArray::toVector(jsi::Runtime& runtime) { 216 | auto start = 217 | reinterpret_cast*>(getBuffer(runtime).data(runtime) + byteOffset(runtime)); 218 | auto end = start + size(runtime); 219 | return std::vector>(start, end); 220 | } 221 | 222 | template 223 | void TypedArray::update(jsi::Runtime& runtime, const std::vector>& data) { 224 | if (data.size() != size(runtime)) { 225 | throw jsi::JSError(runtime, "TypedArray can only be updated with a vector of the same size"); 226 | } 227 | uint8_t* rawData = getBuffer(runtime).data(runtime) + byteOffset(runtime); 228 | std::copy(data.begin(), data.end(), reinterpret_cast*>(rawData)); 229 | } 230 | 231 | template 232 | void TypedArray::updateUnsafe(jsi::Runtime& runtime, ContentType* data, size_t length) { 233 | if (length != byteLength(runtime)) { 234 | throw jsi::JSError(runtime, "TypedArray can only be updated with an array of the same size"); 235 | } 236 | uint8_t* rawData = getBuffer(runtime).data(runtime) + byteOffset(runtime); 237 | memcpy(rawData, data, length); 238 | } 239 | 240 | template uint8_t* TypedArray::data(jsi::Runtime& runtime) { 241 | return getBuffer(runtime).data(runtime) + byteOffset(runtime); 242 | } 243 | 244 | const jsi::PropNameID& PropNameIDCache::getConstructorNameProp(jsi::Runtime& runtime, 245 | TypedArrayKind kind) { 246 | switch (kind) { 247 | case TypedArrayKind::Int8Array: 248 | return get(runtime, Prop::Int8Array); 249 | case TypedArrayKind::Int16Array: 250 | return get(runtime, Prop::Int16Array); 251 | case TypedArrayKind::Int32Array: 252 | return get(runtime, Prop::Int32Array); 253 | case TypedArrayKind::Uint8Array: 254 | return get(runtime, Prop::Uint8Array); 255 | case TypedArrayKind::Uint8ClampedArray: 256 | return get(runtime, Prop::Uint8ClampedArray); 257 | case TypedArrayKind::Uint16Array: 258 | return get(runtime, Prop::Uint16Array); 259 | case TypedArrayKind::Uint32Array: 260 | return get(runtime, Prop::Uint32Array); 261 | case TypedArrayKind::Float32Array: 262 | return get(runtime, Prop::Float32Array); 263 | case TypedArrayKind::Float64Array: 264 | return get(runtime, Prop::Float64Array); 265 | case TypedArrayKind::BigInt64Array: 266 | return get(runtime, Prop::BigInt64Array); 267 | case TypedArrayKind::BigUint64Array: 268 | return get(runtime, Prop::BigUint64Array); 269 | } 270 | } 271 | 272 | jsi::PropNameID PropNameIDCache::createProp(jsi::Runtime& runtime, Prop prop) { 273 | auto create = [&](const std::string& propName) { 274 | return jsi::PropNameID::forUtf8(runtime, propName); 275 | }; 276 | switch (prop) { 277 | case Prop::Buffer: 278 | return create("buffer"); 279 | case Prop::Constructor: 280 | return create("constructor"); 281 | case Prop::Name: 282 | return create("name"); 283 | case Prop::Proto: 284 | return create("__proto__"); 285 | case Prop::Length: 286 | return create("length"); 287 | case Prop::ByteLength: 288 | return create("byteLength"); 289 | case Prop::ByteOffset: 290 | return create("byteOffset"); 291 | case Prop::IsView: 292 | return create("isView"); 293 | case Prop::ArrayBuffer: 294 | return create("ArrayBuffer"); 295 | case Prop::Int8Array: 296 | return create("Int8Array"); 297 | case Prop::Int16Array: 298 | return create("Int16Array"); 299 | case Prop::Int32Array: 300 | return create("Int32Array"); 301 | case Prop::Uint8Array: 302 | return create("Uint8Array"); 303 | case Prop::Uint8ClampedArray: 304 | return create("Uint8ClampedArray"); 305 | case Prop::Uint16Array: 306 | return create("Uint16Array"); 307 | case Prop::Uint32Array: 308 | return create("Uint32Array"); 309 | case Prop::Float32Array: 310 | return create("Float32Array"); 311 | case Prop::Float64Array: 312 | return create("Float64Array"); 313 | case Prop::BigInt64Array: 314 | return create("BigInt64Array"); 315 | case Prop::BigUint64Array: 316 | return create("BigUint64Array"); 317 | } 318 | } 319 | 320 | std::unordered_map nameToKindMap = { 321 | {"Int8Array", TypedArrayKind::Int8Array}, 322 | {"Int16Array", TypedArrayKind::Int16Array}, 323 | {"Int32Array", TypedArrayKind::Int32Array}, 324 | {"Uint8Array", TypedArrayKind::Uint8Array}, 325 | {"Uint8ClampedArray", TypedArrayKind::Uint8ClampedArray}, 326 | {"Uint16Array", TypedArrayKind::Uint16Array}, 327 | {"Uint32Array", TypedArrayKind::Uint32Array}, 328 | {"Float32Array", TypedArrayKind::Float32Array}, 329 | {"Float64Array", TypedArrayKind::Float64Array}, 330 | {"BigInt64Array", TypedArrayKind::BigInt64Array}, 331 | {"BigUint64Array", TypedArrayKind::BigUint64Array}, 332 | }; 333 | 334 | TypedArrayKind getTypedArrayKindForName(const std::string& name) { 335 | return nameToKindMap.at(name); 336 | } 337 | 338 | template class TypedArray; 339 | template class TypedArray; 340 | template class TypedArray; 341 | template class TypedArray; 342 | template class TypedArray; 343 | template class TypedArray; 344 | template class TypedArray; 345 | template class TypedArray; 346 | template class TypedArray; 347 | template class TypedArray; 348 | template class TypedArray; 349 | 350 | } // namespace mrousavy 351 | -------------------------------------------------------------------------------- /cpp/jsi/TypedArray.h: -------------------------------------------------------------------------------- 1 | // 2 | // TypedArray.h 3 | // VisionCamera 4 | // 5 | // Created by Marc Rousavy on 21.02.23. 6 | // Copyright © 2023 mrousavy. All rights reserved. 7 | // 8 | 9 | // Copied & Adapted from 10 | // https://github.com/expo/expo/blob/main/packages/expo-gl/common/EXTypedArrayApi.h Credits to Expo 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | namespace mrousavy { 19 | 20 | using namespace facebook; 21 | 22 | enum class TypedArrayKind { 23 | Int8Array, 24 | Int16Array, 25 | Int32Array, 26 | Uint8Array, 27 | Uint8ClampedArray, 28 | Uint16Array, 29 | Uint32Array, 30 | Float32Array, 31 | Float64Array, 32 | BigInt64Array, 33 | BigUint64Array, 34 | }; 35 | 36 | template class TypedArray; 37 | 38 | template struct typedArrayTypeMap; 39 | template <> struct typedArrayTypeMap { 40 | typedef int8_t type; 41 | }; 42 | template <> struct typedArrayTypeMap { 43 | typedef int16_t type; 44 | }; 45 | template <> struct typedArrayTypeMap { 46 | typedef int32_t type; 47 | }; 48 | template <> struct typedArrayTypeMap { 49 | typedef uint8_t type; 50 | }; 51 | template <> struct typedArrayTypeMap { 52 | typedef uint8_t type; 53 | }; 54 | template <> struct typedArrayTypeMap { 55 | typedef uint16_t type; 56 | }; 57 | template <> struct typedArrayTypeMap { 58 | typedef uint32_t type; 59 | }; 60 | template <> struct typedArrayTypeMap { 61 | typedef float type; 62 | }; 63 | template <> struct typedArrayTypeMap { 64 | typedef double type; 65 | }; 66 | template <> struct typedArrayTypeMap { 67 | typedef int64_t type; 68 | }; 69 | template <> struct typedArrayTypeMap { 70 | typedef uint64_t type; 71 | }; 72 | 73 | // Instance of this class will invalidate PropNameIDCache when destructor is called. 74 | // Attach this object to global in specific jsi::Runtime to make sure lifecycle of 75 | // the cache object is connected to the lifecycle of the js runtime 76 | class InvalidateCacheOnDestroy : public jsi::HostObject { 77 | public: 78 | explicit InvalidateCacheOnDestroy(jsi::Runtime& runtime); 79 | virtual ~InvalidateCacheOnDestroy(); 80 | virtual jsi::Value get(jsi::Runtime&, const jsi::PropNameID& name) { 81 | return jsi::Value::null(); 82 | } 83 | virtual void set(jsi::Runtime&, const jsi::PropNameID& name, const jsi::Value& value) {} 84 | virtual std::vector getPropertyNames(jsi::Runtime& rt) { 85 | return {}; 86 | } 87 | 88 | private: 89 | uintptr_t key; 90 | }; 91 | 92 | class TypedArrayBase : public jsi::Object { 93 | public: 94 | template using ContentType = typename typedArrayTypeMap::type; 95 | 96 | TypedArrayBase(jsi::Runtime&, size_t, TypedArrayKind); 97 | TypedArrayBase(jsi::Runtime&, const jsi::Object&); 98 | TypedArrayBase(TypedArrayBase&&) = default; 99 | TypedArrayBase& operator=(TypedArrayBase&&) = default; 100 | 101 | TypedArrayKind getKind(jsi::Runtime& runtime) const; 102 | 103 | template TypedArray get(jsi::Runtime& runtime) const&; 104 | template TypedArray get(jsi::Runtime& runtime) &&; 105 | template TypedArray as(jsi::Runtime& runtime) const&; 106 | template TypedArray as(jsi::Runtime& runtime) &&; 107 | 108 | size_t size(jsi::Runtime& runtime) const; 109 | size_t length(jsi::Runtime& runtime) const; 110 | size_t byteLength(jsi::Runtime& runtime) const; 111 | size_t byteOffset(jsi::Runtime& runtime) const; 112 | bool hasBuffer(jsi::Runtime& runtime) const; 113 | 114 | std::vector toVector(jsi::Runtime& runtime); 115 | jsi::ArrayBuffer getBuffer(jsi::Runtime& runtime) const; 116 | 117 | private: 118 | template friend class TypedArray; 119 | }; 120 | 121 | bool isTypedArray(jsi::Runtime& runtime, const jsi::Object& jsObj); 122 | TypedArrayBase getTypedArray(jsi::Runtime& runtime, const jsi::Object& jsObj); 123 | 124 | std::vector arrayBufferToVector(jsi::Runtime& runtime, jsi::Object& jsObj); 125 | void arrayBufferUpdate(jsi::Runtime& runtime, jsi::ArrayBuffer& buffer, std::vector data, 126 | size_t offset); 127 | 128 | template class TypedArray : public TypedArrayBase { 129 | public: 130 | explicit TypedArray(TypedArrayBase&& base); 131 | TypedArray(jsi::Runtime& runtime, size_t size); 132 | TypedArray(jsi::Runtime& runtime, ContentType* dataToCopy, size_t size); 133 | TypedArray(jsi::Runtime& runtime, std::vector> data); 134 | TypedArray(TypedArray&&) = default; 135 | TypedArray& operator=(TypedArray&&) = default; 136 | 137 | std::vector> toVector(jsi::Runtime& runtime); 138 | void update(jsi::Runtime& runtime, const std::vector>& data); 139 | void updateUnsafe(jsi::Runtime& runtime, ContentType* data, size_t length); 140 | uint8_t* data(jsi::Runtime& runtime); 141 | }; 142 | 143 | template TypedArray TypedArrayBase::get(jsi::Runtime& runtime) const& { 144 | assert(getKind(runtime) == T); 145 | (void)runtime; // when assert is disabled we need to mark this as used 146 | return TypedArray(jsi::Value(runtime, jsi::Value(runtime, *this).asObject(runtime))); 147 | } 148 | 149 | template TypedArray TypedArrayBase::get(jsi::Runtime& runtime) && { 150 | assert(getKind(runtime) == T); 151 | (void)runtime; // when assert is disabled we need to mark this as used 152 | return TypedArray(std::move(*this)); 153 | } 154 | 155 | template TypedArray TypedArrayBase::as(jsi::Runtime& runtime) const& { 156 | if (getKind(runtime) != T) { 157 | throw jsi::JSError(runtime, "Object is not a TypedArray"); 158 | } 159 | return get(runtime); 160 | } 161 | 162 | template TypedArray TypedArrayBase::as(jsi::Runtime& runtime) && { 163 | if (getKind(runtime) != T) { 164 | throw jsi::JSError(runtime, "Object is not a TypedArray"); 165 | } 166 | return std::move(*this).get(runtime); 167 | } 168 | } // namespace mrousavy 169 | -------------------------------------------------------------------------------- /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 | parser: '@typescript-eslint/parser', 4 | parserOptions: { 5 | tsconfigRootDir: __dirname, 6 | project: ['./tsconfig.json'], 7 | ecmaFeatures: { 8 | jsx: true, 9 | }, 10 | ecmaVersion: 2018, 11 | sourceType: 'module', 12 | }, 13 | ignorePatterns: ['babel.config.js', 'metro.config.js', '.eslintrc.js', 'react-native.config.js'], 14 | plugins: ['@typescript-eslint'], 15 | extends: ['plugin:@typescript-eslint/recommended', '@react-native', '../.eslintrc.js'], 16 | } 17 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | This is a new [**React Native**](https://reactnative.dev) project, bootstrapped using [`@react-native-community/cli`](https://github.com/react-native-community/cli). 2 | 3 | # Getting Started 4 | 5 | >**Note**: Make sure you have completed the [React Native - Environment Setup](https://reactnative.dev/docs/environment-setup) instructions till "Creating a new application" step, before proceeding. 6 | 7 | ## Step 1: Start the Metro Server 8 | 9 | First, you will need to start **Metro**, the JavaScript _bundler_ that ships _with_ React Native. 10 | 11 | To start Metro, run the following command from the _root_ of your React Native project: 12 | 13 | ```bash 14 | # using npm 15 | npm start 16 | 17 | # OR using Yarn 18 | yarn start 19 | ``` 20 | 21 | ## Step 2: Start your Application 22 | 23 | Let Metro Bundler run in its _own_ terminal. Open a _new_ terminal from the _root_ of your React Native project. Run the following command to start your _Android_ or _iOS_ app: 24 | 25 | ### For Android 26 | 27 | ```bash 28 | # using npm 29 | npm run android 30 | 31 | # OR using Yarn 32 | yarn android 33 | ``` 34 | 35 | ### For iOS 36 | 37 | ```bash 38 | # using npm 39 | npm run ios 40 | 41 | # OR using Yarn 42 | yarn ios 43 | ``` 44 | 45 | If everything is set up _correctly_, you should see your new app running in your _Android Emulator_ or _iOS Simulator_ shortly provided you have set up your emulator/simulator correctly. 46 | 47 | This is one way to run your app — you can also run it directly from within Android Studio and Xcode respectively. 48 | 49 | ## Step 3: Modifying your App 50 | 51 | Now that you have successfully run the app, let's modify it. 52 | 53 | 1. Open `App.tsx` in your text editor of choice and edit some lines. 54 | 2. For **Android**: Press the R key twice or select **"Reload"** from the **Developer Menu** (Ctrl + M (on Window and Linux) or Cmd ⌘ + M (on macOS)) to see your changes! 55 | 56 | For **iOS**: Hit Cmd ⌘ + R in your iOS Simulator to reload the app and see your changes! 57 | 58 | ## Congratulations! :tada: 59 | 60 | You've successfully run and modified your React Native App. :partying_face: 61 | 62 | ### Now what? 63 | 64 | - If you want to add this new React Native code to an existing application, check out the [Integration guide](https://reactnative.dev/docs/integration-with-existing-apps). 65 | - If you're curious to learn more about React Native, check out the [Introduction to React Native](https://reactnative.dev/docs/getting-started). 66 | 67 | # Troubleshooting 68 | 69 | If you can't get this to work, see the [Troubleshooting](https://reactnative.dev/docs/troubleshooting) page. 70 | 71 | # Learn More 72 | 73 | To learn more about React Native, take a look at the following resources: 74 | 75 | - [React Native Website](https://reactnative.dev) - learn more about React Native. 76 | - [Getting Started](https://reactnative.dev/docs/environment-setup) - an **overview** of React Native and how setup your environment. 77 | - [Learn the Basics](https://reactnative.dev/docs/getting-started) - a **guided tour** of the React Native **basics**. 78 | - [Blog](https://reactnative.dev/blog) - read the latest official React Native **Blog** posts. 79 | - [`@facebook/react-native`](https://github.com/facebook/react-native) - the Open Source; GitHub **repository** for React Native. 80 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | apply plugin: "com.facebook.react" 3 | 4 | /** 5 | * This is the configuration block to customize your React Native Android app. 6 | * By default you don't need to apply any configuration, just uncomment the lines you need. 7 | */ 8 | react { 9 | /* Folders */ 10 | // The root of your project, i.e. where "package.json" lives. Default is '..' 11 | // root = file("../") 12 | // The folder where the react-native NPM package is. Default is ../node_modules/react-native 13 | // reactNativeDir = file("../node_modules/react-native") 14 | // The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen 15 | // codegenDir = file("../node_modules/@react-native/codegen") 16 | // The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js 17 | // cliFile = file("../node_modules/react-native/cli.js") 18 | 19 | /* Variants */ 20 | // The list of variants to that are debuggable. For those we're going to 21 | // skip the bundling of the JS bundle and the assets. By default is just 'debug'. 22 | // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. 23 | // debuggableVariants = ["liteDebug", "prodDebug"] 24 | 25 | /* Bundling */ 26 | // A list containing the node command and its flags. Default is just 'node'. 27 | // nodeExecutableAndArgs = ["node"] 28 | // 29 | // The command to run when bundling. By default is 'bundle' 30 | // bundleCommand = "ram-bundle" 31 | // 32 | // The path to the CLI configuration file. Default is empty. 33 | // bundleConfig = file(../rn-cli.config.js) 34 | // 35 | // The name of the generated asset file containing your JS bundle 36 | // bundleAssetName = "MyApplication.android.bundle" 37 | // 38 | // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' 39 | // entryFile = file("../js/MyApplication.android.js") 40 | // 41 | // A list of extra flags to pass to the 'bundle' commands. 42 | // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle 43 | // extraPackagerArgs = [] 44 | 45 | /* Hermes Commands */ 46 | // The hermes compiler command to run. By default it is 'hermesc' 47 | // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" 48 | // 49 | // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" 50 | // hermesFlags = ["-O", "-output-source-map"] 51 | } 52 | 53 | /** 54 | * Set this to true to Run Proguard on Release builds to minify the Java bytecode. 55 | */ 56 | def enableProguardInReleaseBuilds = false 57 | 58 | /** 59 | * The preferred build flavor of JavaScriptCore (JSC) 60 | * 61 | * For example, to use the international variant, you can use: 62 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` 63 | * 64 | * The international variant includes ICU i18n library and necessary data 65 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that 66 | * give correct results when using with locales other than en-US. Note that 67 | * this variant is about 6MiB larger per architecture than default. 68 | */ 69 | def jscFlavor = 'org.webkit:android-jsc:+' 70 | 71 | android { 72 | ndkVersion rootProject.ext.ndkVersion 73 | 74 | buildToolsVersion rootProject.ext.buildToolsVersion 75 | compileSdkVersion rootProject.ext.compileSdkVersion 76 | 77 | namespace "com.tfliteexample" 78 | defaultConfig { 79 | applicationId "com.tfliteexample" 80 | minSdkVersion rootProject.ext.minSdkVersion 81 | targetSdkVersion rootProject.ext.targetSdkVersion 82 | versionCode 1 83 | versionName "1.0" 84 | } 85 | signingConfigs { 86 | debug { 87 | storeFile file('debug.keystore') 88 | storePassword 'android' 89 | keyAlias 'androiddebugkey' 90 | keyPassword 'android' 91 | } 92 | } 93 | buildTypes { 94 | debug { 95 | signingConfig signingConfigs.debug 96 | } 97 | release { 98 | // Caution! In production, you need to generate your own keystore file. 99 | // see https://reactnative.dev/docs/signed-apk-android. 100 | signingConfig signingConfigs.debug 101 | minifyEnabled enableProguardInReleaseBuilds 102 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 103 | } 104 | } 105 | } 106 | 107 | dependencies { 108 | // The version of react-native is set by the React Native Gradle Plugin 109 | implementation("com.facebook.react:react-android") 110 | 111 | if (hermesEnabled.toBoolean()) { 112 | implementation("com.facebook.react:hermes-android") 113 | } else { 114 | implementation jscFlavor 115 | } 116 | } 117 | 118 | apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) 119 | -------------------------------------------------------------------------------- /example/android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/react-native-fast-tflite/15663b60d620b1b833a841655f53e650789374b6/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/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/tfliteexample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.tfliteexample; 2 | 3 | import com.facebook.react.ReactActivity; 4 | import com.facebook.react.ReactActivityDelegate; 5 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; 6 | import com.facebook.react.defaults.DefaultReactActivityDelegate; 7 | 8 | public class MainActivity extends 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 15 | protected String getMainComponentName() { 16 | return "TfliteExample"; 17 | } 18 | 19 | /** 20 | * Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link 21 | * DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React 22 | * (aka React 18) with two boolean flags. 23 | */ 24 | @Override 25 | protected ReactActivityDelegate createReactActivityDelegate() { 26 | return new DefaultReactActivityDelegate( 27 | this, 28 | getMainComponentName(), 29 | // If you opted-in for the New Architecture, we enable the Fabric Renderer. 30 | DefaultNewArchitectureEntryPoint.getFabricEnabled()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/tfliteexample/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.tfliteexample; 2 | 3 | import android.app.Application; 4 | import com.facebook.react.PackageList; 5 | import com.facebook.react.ReactApplication; 6 | import com.facebook.react.ReactNativeHost; 7 | import com.facebook.react.ReactPackage; 8 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; 9 | import com.facebook.react.defaults.DefaultReactNativeHost; 10 | import com.facebook.soloader.SoLoader; 11 | import java.util.List; 12 | 13 | public class MainApplication extends Application implements ReactApplication { 14 | 15 | private final ReactNativeHost mReactNativeHost = 16 | new DefaultReactNativeHost(this) { 17 | @Override 18 | public boolean getUseDeveloperSupport() { 19 | return BuildConfig.DEBUG; 20 | } 21 | 22 | @Override 23 | protected List getPackages() { 24 | @SuppressWarnings("UnnecessaryLocalVariable") 25 | List packages = new PackageList(this).getPackages(); 26 | // Packages that cannot be autolinked yet can be added manually here, for example: 27 | // packages.add(new MyReactNativePackage()); 28 | return packages; 29 | } 30 | 31 | @Override 32 | protected String getJSMainModuleName() { 33 | return "index"; 34 | } 35 | 36 | @Override 37 | protected boolean isNewArchEnabled() { 38 | return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; 39 | } 40 | 41 | @Override 42 | protected Boolean isHermesEnabled() { 43 | return BuildConfig.IS_HERMES_ENABLED; 44 | } 45 | }; 46 | 47 | @Override 48 | public ReactNativeHost getReactNativeHost() { 49 | return mReactNativeHost; 50 | } 51 | 52 | @Override 53 | public void onCreate() { 54 | super.onCreate(); 55 | SoLoader.init(this, /* native exopackage */ false); 56 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { 57 | // If you opted-in for the New Architecture, we load the native entry point for this app. 58 | DefaultNewArchitectureEntryPoint.load(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /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/mrousavy/react-native-fast-tflite/15663b60d620b1b833a841655f53e650789374b6/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/mrousavy/react-native-fast-tflite/15663b60d620b1b833a841655f53e650789374b6/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/mrousavy/react-native-fast-tflite/15663b60d620b1b833a841655f53e650789374b6/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/mrousavy/react-native-fast-tflite/15663b60d620b1b833a841655f53e650789374b6/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/mrousavy/react-native-fast-tflite/15663b60d620b1b833a841655f53e650789374b6/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/mrousavy/react-native-fast-tflite/15663b60d620b1b833a841655f53e650789374b6/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/mrousavy/react-native-fast-tflite/15663b60d620b1b833a841655f53e650789374b6/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/mrousavy/react-native-fast-tflite/15663b60d620b1b833a841655f53e650789374b6/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/mrousavy/react-native-fast-tflite/15663b60d620b1b833a841655f53e650789374b6/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/mrousavy/react-native-fast-tflite/15663b60d620b1b833a841655f53e650789374b6/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | TfliteExample 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 | ext { 5 | buildToolsVersion = "34.0.0" 6 | minSdkVersion = 21 7 | compileSdkVersion = 34 8 | targetSdkVersion = 34 9 | ndkVersion = "25.1.8937393" 10 | kotlinVersion = "1.8.0" 11 | } 12 | repositories { 13 | google() 14 | mavenCentral() 15 | } 16 | dependencies { 17 | classpath("com.android.tools.build:gradle") 18 | classpath("com.facebook.react:react-native-gradle-plugin") 19 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") 20 | } 21 | } 22 | 23 | apply plugin: "com.facebook.react.rootproject" 24 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/react-native-fast-tflite/15663b60d620b1b833a841655f53e650789374b6/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: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | # Collect all arguments for the java command; 201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 202 | # shell script including quotes and variable substitutions, so put them in 203 | # double quotes to make sure that they get re-expanded; and 204 | # * put everything else in single quotes, so that it's not re-expanded. 205 | 206 | set -- \ 207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 208 | -classpath "$CLASSPATH" \ 209 | org.gradle.wrapper.GradleWrapperMain \ 210 | "$@" 211 | 212 | # Stop when "xargs" is not available. 213 | if ! command -v xargs >/dev/null 2>&1 214 | then 215 | die "xargs is not available" 216 | fi 217 | 218 | # Use "xargs" to parse quoted args. 219 | # 220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 221 | # 222 | # In Bash we could simply go: 223 | # 224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 225 | # set -- "${ARGS[@]}" "$@" 226 | # 227 | # but POSIX shell has neither arrays nor command substitution, so instead we 228 | # post-process each arg (as a line of input to sed) to backslash-escape any 229 | # character that might be a shell metacharacter, then use eval to reverse 230 | # that process (while maintaining the separation between arguments), and wrap 231 | # the whole thing up as a single "set" statement. 232 | # 233 | # This will of course break if any of these variables contains a newline or 234 | # an unmatched quote. 235 | # 236 | 237 | eval "set -- $( 238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 239 | xargs -n1 | 240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 241 | tr '\n' ' ' 242 | )" '"$@"' 243 | 244 | exec "$JAVACMD" "$@" 245 | -------------------------------------------------------------------------------- /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 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'TfliteExample' 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') 5 | -------------------------------------------------------------------------------- /example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TfliteExample", 3 | "displayName": "TfliteExample" 4 | } 5 | -------------------------------------------------------------------------------- /example/assets/efficientdet.tflite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/react-native-fast-tflite/15663b60d620b1b833a841655f53e650789374b6/example/assets/efficientdet.tflite -------------------------------------------------------------------------------- /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 | 'react-native-worklets-core/plugin', 8 | [ 9 | 'module-resolver', 10 | { 11 | extensions: ['.tsx', '.ts', '.js', '.json'], 12 | alias: { 13 | [pak.name]: path.join(__dirname, '..', pak.source), 14 | }, 15 | }, 16 | ], 17 | ], 18 | }; 19 | -------------------------------------------------------------------------------- /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/.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 | // TfliteExample 4 | // 5 | 6 | import Foundation 7 | -------------------------------------------------------------------------------- /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 | flipper_config = FlipperConfiguration.disabled 12 | 13 | linkage = ENV['USE_FRAMEWORKS'] 14 | if linkage != nil 15 | Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green 16 | use_frameworks! :linkage => linkage.to_sym 17 | end 18 | 19 | $EnableCoreMLDelegate=true 20 | 21 | target 'TfliteExample' do 22 | config = use_native_modules! 23 | 24 | use_react_native!( 25 | :path => config[:reactNativePath], 26 | :flipper_configuration => flipper_config, 27 | # An absolute path to your application root. 28 | :app_path => "#{Pod::Config.instance.installation_root}/.." 29 | ) 30 | 31 | target 'TfliteExampleTests' do 32 | inherit! :complete 33 | # Pods for testing 34 | end 35 | 36 | post_install do |installer| 37 | # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 38 | react_native_post_install( 39 | installer, 40 | config[:reactNativePath], 41 | :mac_catalyst_enabled => false 42 | ) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /example/ios/TfliteExample-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/TfliteExample.xcodeproj/xcshareddata/xcschemes/TfliteExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /example/ios/TfliteExample.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/TfliteExample.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/TfliteExample/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : RCTAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/ios/TfliteExample/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 = @"TfliteExample"; 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 | 22 | - (NSURL *)getBundleURL 23 | { 24 | #if DEBUG 25 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"]; 26 | #else 27 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 28 | #endif 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /example/ios/TfliteExample/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/TfliteExample/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /example/ios/TfliteExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | TfliteExample 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 | NSExceptionDomains 34 | 35 | localhost 36 | 37 | NSExceptionAllowsInsecureHTTPLoads 38 | 39 | 40 | 41 | 42 | NSCameraUsageDescription 43 | $(PRODUCT_NAME) needs access to your Camera. 44 | NSLocationWhenInUseUsageDescription 45 | 46 | UILaunchStoryboardName 47 | LaunchScreen 48 | UIRequiredDeviceCapabilities 49 | 50 | armv7 51 | 52 | UISupportedInterfaceOrientations 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationLandscapeLeft 56 | UIInterfaceOrientationLandscapeRight 57 | 58 | UIViewControllerBasedStatusBarAppearance 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /example/ios/TfliteExample/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/TfliteExample/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/TfliteExampleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /example/ios/TfliteExampleTests/TfliteExampleTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #import 5 | #import 6 | 7 | #define TIMEOUT_SECONDS 600 8 | #define TEXT_TO_LOOK_FOR @"Welcome to React" 9 | 10 | @interface TfliteExampleTests : XCTestCase 11 | 12 | @end 13 | 14 | @implementation TfliteExampleTests 15 | 16 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL (^)(UIView *view))test 17 | { 18 | if (test(view)) { 19 | return YES; 20 | } 21 | for (UIView *subview in [view subviews]) { 22 | if ([self findSubviewInView:subview matching:test]) { 23 | return YES; 24 | } 25 | } 26 | return NO; 27 | } 28 | 29 | - (void)testRendersWelcomeScreen 30 | { 31 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 32 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 33 | BOOL foundElement = NO; 34 | 35 | __block NSString *redboxError = nil; 36 | #ifdef DEBUG 37 | RCTSetLogFunction( 38 | ^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 39 | if (level >= RCTLogLevelError) { 40 | redboxError = message; 41 | } 42 | }); 43 | #endif 44 | 45 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 46 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 47 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 48 | 49 | foundElement = [self findSubviewInView:vc.view 50 | matching:^BOOL(UIView *view) { 51 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 52 | return YES; 53 | } 54 | return NO; 55 | }]; 56 | } 57 | 58 | #ifdef DEBUG 59 | RCTSetLogFunction(RCTDefaultLogFunction); 60 | #endif 61 | 62 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 63 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 64 | } 65 | 66 | @end 67 | -------------------------------------------------------------------------------- /example/metro.config.js: -------------------------------------------------------------------------------- 1 | const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); 2 | const path = require('path'); 3 | const escape = require('escape-string-regexp'); 4 | const exclusionList = require('metro-config/src/defaults/exclusionList'); 5 | const pak = require('../package.json'); 6 | 7 | const root = path.resolve(__dirname, '..'); 8 | const modules = Object.keys({ ...pak.peerDependencies }); 9 | 10 | /** 11 | * Metro configuration 12 | * https://facebook.github.io/metro/docs/configuration 13 | * 14 | * @type {import('metro-config').MetroConfig} 15 | */ 16 | const config = { 17 | watchFolders: [root], 18 | 19 | // We need to make sure that only one version is loaded for peerDependencies 20 | // So we block them at the root, and alias them to the versions in example's node_modules 21 | resolver: { 22 | assetExts: ['tflite', 'png', 'jpg'], 23 | blacklistRE: exclusionList( 24 | modules.map( 25 | (m) => 26 | new RegExp(`^${escape(path.join(root, 'node_modules', m))}\\/.*$`) 27 | ) 28 | ), 29 | 30 | extraNodeModules: modules.reduce((acc, name) => { 31 | acc[name] = path.join(__dirname, 'node_modules', name); 32 | return acc; 33 | }, {}), 34 | }, 35 | 36 | transformer: { 37 | getTransformOptions: async () => ({ 38 | transform: { 39 | experimentalImportSupport: false, 40 | inlineRequires: true, 41 | }, 42 | }), 43 | }, 44 | }; 45 | 46 | module.exports = mergeConfig(getDefaultConfig(__dirname), config); 47 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TfliteExample", 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": "cd ios && pod install", 10 | "lint": "eslint \"**/*.{js,ts,tsx}\" --fix" 11 | }, 12 | "dependencies": { 13 | "react": "18.2.0", 14 | "react-native": "0.73.3", 15 | "react-native-vision-camera": "4.0.3", 16 | "react-native-worklets-core": "1.2.0", 17 | "vision-camera-resize-plugin": "3.1.0" 18 | }, 19 | "devDependencies": { 20 | "@babel/core": "^7.23.9", 21 | "@babel/preset-env": "^7.23.9", 22 | "@babel/runtime": "^7.23.9", 23 | "@react-native-community/eslint-config": "^3.0.2", 24 | "@react-native/eslint-config": "^0.73.2", 25 | "@react-native/metro-config": "^0.73.3", 26 | "@types/metro-config": "^0.76.3", 27 | "babel-plugin-module-resolver": "^5.0.0", 28 | "eslint": "^8.56.0", 29 | "eslint-config-prettier": "^9.1.0", 30 | "eslint-plugin-prettier": "^5.1.3", 31 | "metro-react-native-babel-preset": "0.77.0" 32 | }, 33 | "engines": { 34 | "node": ">=18" 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 | }; 11 | -------------------------------------------------------------------------------- /example/src/App.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | import * as React from 'react' 3 | 4 | import { StyleSheet, View, Text, ActivityIndicator } from 'react-native' 5 | import { 6 | Tensor, 7 | TensorflowModel, 8 | useTensorflowModel, 9 | } from 'react-native-fast-tflite' 10 | import { 11 | Camera, 12 | useCameraDevice, 13 | useCameraPermission, 14 | useFrameProcessor, 15 | } from 'react-native-vision-camera' 16 | import { useResizePlugin } from 'vision-camera-resize-plugin' 17 | 18 | function tensorToString(tensor: Tensor): string { 19 | return `\n - ${tensor.dataType} ${tensor.name}[${tensor.shape}]` 20 | } 21 | function modelToString(model: TensorflowModel): string { 22 | return ( 23 | `TFLite Model (${model.delegate}):\n` + 24 | `- Inputs: ${model.inputs.map(tensorToString).join('')}\n` + 25 | `- Outputs: ${model.outputs.map(tensorToString).join('')}` 26 | ) 27 | } 28 | 29 | export default function App(): React.ReactNode { 30 | const { hasPermission, requestPermission } = useCameraPermission() 31 | const device = useCameraDevice('back') 32 | 33 | // from https://www.kaggle.com/models/tensorflow/efficientdet/frameworks/tfLite 34 | const model = useTensorflowModel(require('../assets/efficientdet.tflite')) 35 | const actualModel = model.state === 'loaded' ? model.model : undefined 36 | 37 | React.useEffect(() => { 38 | if (actualModel == null) return 39 | console.log(`Model loaded! Shape:\n${modelToString(actualModel)}]`) 40 | }, [actualModel]) 41 | 42 | const { resize } = useResizePlugin() 43 | 44 | const frameProcessor = useFrameProcessor( 45 | (frame) => { 46 | 'worklet' 47 | if (actualModel == null) { 48 | // model is still loading... 49 | return 50 | } 51 | 52 | console.log(`Running inference on ${frame}`) 53 | const resized = resize(frame, { 54 | scale: { 55 | width: 320, 56 | height: 320, 57 | }, 58 | pixelFormat: 'rgb', 59 | dataType: 'uint8', 60 | }) 61 | const result = actualModel.runSync([resized]) 62 | const num_detections = result[3]?.[0] ?? 0 63 | console.log('Result: ' + num_detections) 64 | }, 65 | [actualModel] 66 | ) 67 | 68 | React.useEffect(() => { 69 | requestPermission() 70 | }, [requestPermission]) 71 | 72 | console.log(`Model: ${model.state} (${model.model != null})`) 73 | 74 | return ( 75 | 76 | {hasPermission && device != null ? ( 77 | 84 | ) : ( 85 | No Camera available. 86 | )} 87 | 88 | {model.state === 'loading' && ( 89 | 90 | )} 91 | 92 | {model.state === 'error' && ( 93 | Failed to load model! {model.error.message} 94 | )} 95 | 96 | ) 97 | } 98 | 99 | const styles = StyleSheet.create({ 100 | container: { 101 | flex: 1, 102 | alignItems: 'center', 103 | justifyContent: 'center', 104 | }, 105 | }) 106 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig", 3 | "compilerOptions": { 4 | "paths": { 5 | "react-native-fast-tflite": ["../src/index"] 6 | }, 7 | }, 8 | "include": [ 9 | "src", 10 | "index.js" 11 | ], 12 | "exclude": [ 13 | "node_modules" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /img/banner-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/react-native-fast-tflite/15663b60d620b1b833a841655f53e650789374b6/img/banner-dark.png -------------------------------------------------------------------------------- /img/banner-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/react-native-fast-tflite/15663b60d620b1b833a841655f53e650789374b6/img/banner-light.png -------------------------------------------------------------------------------- /img/ios-coreml-guide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/react-native-fast-tflite/15663b60d620b1b833a841655f53e650789374b6/img/ios-coreml-guide.png -------------------------------------------------------------------------------- /img/netron-inspect-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/react-native-fast-tflite/15663b60d620b1b833a841655f53e650789374b6/img/netron-inspect-model.png -------------------------------------------------------------------------------- /img/tfhub-description.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrousavy/react-native-fast-tflite/15663b60d620b1b833a841655f53e650789374b6/img/tfhub-description.png -------------------------------------------------------------------------------- /ios/Tflite.h: -------------------------------------------------------------------------------- 1 | #ifdef RCT_NEW_ARCH_ENABLED 2 | 3 | #import "RNTfliteSpec.h" 4 | @interface Tflite : NSObject 5 | @end 6 | 7 | #else 8 | 9 | #import 10 | @interface Tflite : NSObject 11 | @end 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /ios/Tflite.mm: -------------------------------------------------------------------------------- 1 | #import "Tflite.h" 2 | #import "../cpp/TensorflowPlugin.h" 3 | #import 4 | #import 5 | #import 6 | #import 7 | 8 | // This is defined in RCTTurboModule.h. 9 | // for future versions of React we might need to figure out another approach to get the 10 | // JSCallInvoker! 11 | @interface RCTBridge (RCTTurboModule) 12 | - (std::shared_ptr)jsCallInvoker; 13 | @end 14 | 15 | using namespace facebook; 16 | 17 | @implementation Tflite 18 | RCT_EXPORT_MODULE(Tflite) 19 | 20 | - (NSNumber *)install { 21 | RCTBridge* bridge = [RCTBridge currentBridge]; 22 | RCTCxxBridge* cxxBridge = (RCTCxxBridge*)bridge; 23 | if (!cxxBridge.runtime) { 24 | return @(false); 25 | } 26 | jsi::Runtime& runtime = *(jsi::Runtime*)cxxBridge.runtime; 27 | 28 | auto fetchByteDataFromUrl = [](std::string url) { 29 | NSString* string = [NSString stringWithUTF8String:url.c_str()]; 30 | NSLog(@"Fetching %@...", string); 31 | NSURL* nsURL = [NSURL URLWithString:string]; 32 | NSData* contents = [NSData dataWithContentsOfURL:nsURL]; 33 | 34 | void* data = malloc(contents.length * sizeof(uint8_t)); 35 | memcpy(data, contents.bytes, contents.length); 36 | return Buffer{.data = data, .size = contents.length}; 37 | }; 38 | 39 | try { 40 | TensorflowPlugin::installToRuntime(runtime, [bridge jsCallInvoker], fetchByteDataFromUrl); 41 | } catch (std::exception& exc) { 42 | NSLog(@"Failed to install TensorFlow Lite plugin to Runtime! %s", exc.what()); 43 | return @(false); 44 | } 45 | 46 | return @(true); 47 | } 48 | 49 | // Don't compile this code when we build for the old architecture. 50 | #ifdef RCT_NEW_ARCH_ENABLED 51 | - (std::shared_ptr)getTurboModule: 52 | (const facebook::react::ObjCTurboModule::InitParams&)params { 53 | return std::make_shared(params); 54 | } 55 | #endif 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /ios/Tflite.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5E555C0D2413F4C50049A1A2 /* Tflite.mm in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* Tflite.mm */; }; 11 | B843C0D92A541AD0005F585B /* TensorFlowLiteC.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B843C0D82A541AD0005F585B /* TensorFlowLiteC.framework */; }; 12 | B84FBB7A2A93B57C008D281C /* TensorFlowLiteCCoreML.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B84FBB792A93B57C008D281C /* TensorFlowLiteCCoreML.framework */; }; 13 | /* End PBXBuildFile section */ 14 | 15 | /* Begin PBXCopyFilesBuildPhase section */ 16 | 58B511D91A9E6C8500147676 /* CopyFiles */ = { 17 | isa = PBXCopyFilesBuildPhase; 18 | buildActionMask = 2147483647; 19 | dstPath = "include/$(PRODUCT_NAME)"; 20 | dstSubfolderSpec = 16; 21 | files = ( 22 | ); 23 | runOnlyForDeploymentPostprocessing = 0; 24 | }; 25 | /* End PBXCopyFilesBuildPhase section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 134814201AA4EA6300B7C361 /* libTflite.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libTflite.a; sourceTree = BUILT_PRODUCTS_DIR; }; 29 | B3E7B5891CC2AC0600A0062D /* Tflite.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = Tflite.mm; sourceTree = ""; }; 30 | B843C0D82A541AD0005F585B /* TensorFlowLiteC.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = TensorFlowLiteC.framework; sourceTree = ""; }; 31 | B843C0DA2A541C75005F585B /* Tflite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Tflite.h; sourceTree = ""; }; 32 | B843C0DB2A541C7E005F585B /* cpp */ = {isa = PBXFileReference; lastKnownFileType = folder; name = cpp; path = ../cpp; sourceTree = ""; }; 33 | B84FBB792A93B57C008D281C /* TensorFlowLiteCCoreML.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = TensorFlowLiteCCoreML.framework; sourceTree = ""; }; 34 | /* End PBXFileReference section */ 35 | 36 | /* Begin PBXFrameworksBuildPhase section */ 37 | 58B511D81A9E6C8500147676 /* Frameworks */ = { 38 | isa = PBXFrameworksBuildPhase; 39 | buildActionMask = 2147483647; 40 | files = ( 41 | B843C0D92A541AD0005F585B /* TensorFlowLiteC.framework in Frameworks */, 42 | B84FBB7A2A93B57C008D281C /* TensorFlowLiteCCoreML.framework in Frameworks */, 43 | ); 44 | runOnlyForDeploymentPostprocessing = 0; 45 | }; 46 | /* End PBXFrameworksBuildPhase section */ 47 | 48 | /* Begin PBXGroup section */ 49 | 134814211AA4EA7D00B7C361 /* Products */ = { 50 | isa = PBXGroup; 51 | children = ( 52 | 134814201AA4EA6300B7C361 /* libTflite.a */, 53 | ); 54 | name = Products; 55 | sourceTree = ""; 56 | }; 57 | 58B511D21A9E6C8500147676 = { 58 | isa = PBXGroup; 59 | children = ( 60 | B843C0D82A541AD0005F585B /* TensorFlowLiteC.framework */, 61 | B84FBB792A93B57C008D281C /* TensorFlowLiteCCoreML.framework */, 62 | B843C0DA2A541C75005F585B /* Tflite.h */, 63 | B3E7B5891CC2AC0600A0062D /* Tflite.mm */, 64 | B843C0DB2A541C7E005F585B /* cpp */, 65 | 134814211AA4EA7D00B7C361 /* Products */, 66 | ); 67 | sourceTree = ""; 68 | }; 69 | /* End PBXGroup section */ 70 | 71 | /* Begin PBXNativeTarget section */ 72 | 58B511DA1A9E6C8500147676 /* Tflite */ = { 73 | isa = PBXNativeTarget; 74 | buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "Tflite" */; 75 | buildPhases = ( 76 | 58B511D71A9E6C8500147676 /* Sources */, 77 | 58B511D81A9E6C8500147676 /* Frameworks */, 78 | 58B511D91A9E6C8500147676 /* CopyFiles */, 79 | ); 80 | buildRules = ( 81 | ); 82 | dependencies = ( 83 | ); 84 | name = Tflite; 85 | productName = RCTDataManager; 86 | productReference = 134814201AA4EA6300B7C361 /* libTflite.a */; 87 | productType = "com.apple.product-type.library.static"; 88 | }; 89 | /* End PBXNativeTarget section */ 90 | 91 | /* Begin PBXProject section */ 92 | 58B511D31A9E6C8500147676 /* Project object */ = { 93 | isa = PBXProject; 94 | attributes = { 95 | LastUpgradeCheck = 0920; 96 | ORGANIZATIONNAME = Facebook; 97 | TargetAttributes = { 98 | 58B511DA1A9E6C8500147676 = { 99 | CreatedOnToolsVersion = 6.1.1; 100 | }; 101 | }; 102 | }; 103 | buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "Tflite" */; 104 | compatibilityVersion = "Xcode 3.2"; 105 | developmentRegion = English; 106 | hasScannedForEncodings = 0; 107 | knownRegions = ( 108 | English, 109 | en, 110 | ); 111 | mainGroup = 58B511D21A9E6C8500147676; 112 | productRefGroup = 58B511D21A9E6C8500147676; 113 | projectDirPath = ""; 114 | projectRoot = ""; 115 | targets = ( 116 | 58B511DA1A9E6C8500147676 /* Tflite */, 117 | ); 118 | }; 119 | /* End PBXProject section */ 120 | 121 | /* Begin PBXSourcesBuildPhase section */ 122 | 58B511D71A9E6C8500147676 /* Sources */ = { 123 | isa = PBXSourcesBuildPhase; 124 | buildActionMask = 2147483647; 125 | files = ( 126 | 5E555C0D2413F4C50049A1A2 /* Tflite.mm in Sources */, 127 | ); 128 | runOnlyForDeploymentPostprocessing = 0; 129 | }; 130 | /* End PBXSourcesBuildPhase section */ 131 | 132 | /* Begin XCBuildConfiguration section */ 133 | 58B511ED1A9E6C8500147676 /* Debug */ = { 134 | isa = XCBuildConfiguration; 135 | buildSettings = { 136 | ALWAYS_SEARCH_USER_PATHS = NO; 137 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 138 | CLANG_CXX_LIBRARY = "libc++"; 139 | CLANG_ENABLE_MODULES = YES; 140 | CLANG_ENABLE_OBJC_ARC = YES; 141 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 142 | CLANG_WARN_BOOL_CONVERSION = YES; 143 | CLANG_WARN_COMMA = YES; 144 | CLANG_WARN_CONSTANT_CONVERSION = YES; 145 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 146 | CLANG_WARN_EMPTY_BODY = YES; 147 | CLANG_WARN_ENUM_CONVERSION = YES; 148 | CLANG_WARN_INFINITE_RECURSION = YES; 149 | CLANG_WARN_INT_CONVERSION = YES; 150 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 151 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 152 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 153 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 154 | CLANG_WARN_STRICT_PROTOTYPES = YES; 155 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 156 | CLANG_WARN_UNREACHABLE_CODE = YES; 157 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 158 | COPY_PHASE_STRIP = NO; 159 | ENABLE_STRICT_OBJC_MSGSEND = YES; 160 | ENABLE_TESTABILITY = YES; 161 | "EXCLUDED_ARCHS[sdk=*]" = arm64; 162 | GCC_C_LANGUAGE_STANDARD = gnu99; 163 | GCC_DYNAMIC_NO_PIC = NO; 164 | GCC_NO_COMMON_BLOCKS = YES; 165 | GCC_OPTIMIZATION_LEVEL = 0; 166 | GCC_PREPROCESSOR_DEFINITIONS = ( 167 | "DEBUG=1", 168 | "$(inherited)", 169 | ); 170 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 171 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 172 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 173 | GCC_WARN_UNDECLARED_SELECTOR = YES; 174 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 175 | GCC_WARN_UNUSED_FUNCTION = YES; 176 | GCC_WARN_UNUSED_VARIABLE = YES; 177 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 178 | MTL_ENABLE_DEBUG_INFO = YES; 179 | ONLY_ACTIVE_ARCH = YES; 180 | SDKROOT = iphoneos; 181 | }; 182 | name = Debug; 183 | }; 184 | 58B511EE1A9E6C8500147676 /* Release */ = { 185 | isa = XCBuildConfiguration; 186 | buildSettings = { 187 | ALWAYS_SEARCH_USER_PATHS = NO; 188 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 189 | CLANG_CXX_LIBRARY = "libc++"; 190 | CLANG_ENABLE_MODULES = YES; 191 | CLANG_ENABLE_OBJC_ARC = YES; 192 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 193 | CLANG_WARN_BOOL_CONVERSION = YES; 194 | CLANG_WARN_COMMA = YES; 195 | CLANG_WARN_CONSTANT_CONVERSION = YES; 196 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 197 | CLANG_WARN_EMPTY_BODY = YES; 198 | CLANG_WARN_ENUM_CONVERSION = YES; 199 | CLANG_WARN_INFINITE_RECURSION = YES; 200 | CLANG_WARN_INT_CONVERSION = YES; 201 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 202 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 203 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 204 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 205 | CLANG_WARN_STRICT_PROTOTYPES = YES; 206 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 207 | CLANG_WARN_UNREACHABLE_CODE = YES; 208 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 209 | COPY_PHASE_STRIP = YES; 210 | ENABLE_NS_ASSERTIONS = NO; 211 | ENABLE_STRICT_OBJC_MSGSEND = YES; 212 | "EXCLUDED_ARCHS[sdk=*]" = arm64; 213 | GCC_C_LANGUAGE_STANDARD = gnu99; 214 | GCC_NO_COMMON_BLOCKS = YES; 215 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 216 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 217 | GCC_WARN_UNDECLARED_SELECTOR = YES; 218 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 219 | GCC_WARN_UNUSED_FUNCTION = YES; 220 | GCC_WARN_UNUSED_VARIABLE = YES; 221 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 222 | MTL_ENABLE_DEBUG_INFO = NO; 223 | SDKROOT = iphoneos; 224 | VALIDATE_PRODUCT = YES; 225 | }; 226 | name = Release; 227 | }; 228 | 58B511F01A9E6C8500147676 /* Debug */ = { 229 | isa = XCBuildConfiguration; 230 | buildSettings = { 231 | FRAMEWORK_SEARCH_PATHS = ( 232 | "$(inherited)", 233 | "$(PROJECT_DIR)", 234 | ); 235 | HEADER_SEARCH_PATHS = ( 236 | "$(inherited)", 237 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 238 | "$(SRCROOT)/../../../React/**", 239 | "$(SRCROOT)/../../react-native/React/**", 240 | ); 241 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 242 | OTHER_LDFLAGS = "-ObjC"; 243 | PRODUCT_NAME = Tflite; 244 | SKIP_INSTALL = YES; 245 | }; 246 | name = Debug; 247 | }; 248 | 58B511F11A9E6C8500147676 /* Release */ = { 249 | isa = XCBuildConfiguration; 250 | buildSettings = { 251 | FRAMEWORK_SEARCH_PATHS = ( 252 | "$(inherited)", 253 | "$(PROJECT_DIR)", 254 | ); 255 | HEADER_SEARCH_PATHS = ( 256 | "$(inherited)", 257 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 258 | "$(SRCROOT)/../../../React/**", 259 | "$(SRCROOT)/../../react-native/React/**", 260 | ); 261 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 262 | OTHER_LDFLAGS = "-ObjC"; 263 | PRODUCT_NAME = Tflite; 264 | SKIP_INSTALL = YES; 265 | }; 266 | name = Release; 267 | }; 268 | /* End XCBuildConfiguration section */ 269 | 270 | /* Begin XCConfigurationList section */ 271 | 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "Tflite" */ = { 272 | isa = XCConfigurationList; 273 | buildConfigurations = ( 274 | 58B511ED1A9E6C8500147676 /* Debug */, 275 | 58B511EE1A9E6C8500147676 /* Release */, 276 | ); 277 | defaultConfigurationIsVisible = 0; 278 | defaultConfigurationName = Release; 279 | }; 280 | 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "Tflite" */ = { 281 | isa = XCConfigurationList; 282 | buildConfigurations = ( 283 | 58B511F01A9E6C8500147676 /* Debug */, 284 | 58B511F11A9E6C8500147676 /* Release */, 285 | ); 286 | defaultConfigurationIsVisible = 0; 287 | defaultConfigurationName = Release; 288 | }; 289 | /* End XCConfigurationList section */ 290 | }; 291 | rootObject = 58B511D31A9E6C8500147676 /* Project object */; 292 | } 293 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-fast-tflite", 3 | "version": "1.6.1", 4 | "description": "High-performance TensorFlow Lite library for React Native", 5 | "main": "lib/commonjs/index", 6 | "module": "lib/module/index", 7 | "types": "lib/typescript/src/index.d.ts", 8 | "react-native": "src/index", 9 | "source": "src/index", 10 | "files": [ 11 | "src", 12 | "lib", 13 | "android/src", 14 | "!android/src/main/cpp/lib", 15 | "android/build.gradle", 16 | "android/CMakeLists.txt", 17 | "android/gradle.properties", 18 | "ios", 19 | "!ios/build", 20 | "cpp", 21 | "*.podspec", 22 | "app.plugin.js" 23 | ], 24 | "scripts": { 25 | "typescript": "tsc --noEmit", 26 | "test": "jest", 27 | "typecheck": "tsc --noEmit", 28 | "lint": "eslint \"**/*.{js,ts,tsx}\" --fix", 29 | "lint-cpp": "scripts/clang-format.sh", 30 | "check-all": "scripts/check-all.sh", 31 | "prepack": "bob build", 32 | "release": "release-it", 33 | "example": "yarn --cwd example", 34 | "bootstrap": "yarn && yarn example && yarn example pods" 35 | }, 36 | "keywords": [ 37 | "react-native", 38 | "tensorflow", 39 | "vision-camera", 40 | "tflite", 41 | "lite", 42 | "tensorflowlite", 43 | "tensorflow-lite", 44 | "visioncamera", 45 | "react-native-vision-camera", 46 | "vision", 47 | "camera", 48 | "ios", 49 | "android" 50 | ], 51 | "repository": "https://github.com/mrousavy/react-native-fast-tflite", 52 | "author": "Marc Rousavy (https://github.com/mrousavy)", 53 | "license": "MIT", 54 | "bugs": { 55 | "url": "https://github.com/mrousavy/react-native-fast-tflite/issues" 56 | }, 57 | "homepage": "https://github.com/mrousavy/react-native-fast-tflite#readme", 58 | "publishConfig": { 59 | "registry": "https://registry.npmjs.org/" 60 | }, 61 | "devDependencies": { 62 | "@expo/config-plugins": "^7.8.4", 63 | "@jamesacarr/eslint-formatter-github-actions": "^0.2.0", 64 | "@react-native-community/eslint-config": "^3.0.2", 65 | "@release-it/conventional-changelog": "^8.0.1", 66 | "@types/jest": "^29.5.11", 67 | "@types/react": "~18.2.48", 68 | "@types/react-native": "0.73.0", 69 | "eslint": "^8.56.0", 70 | "eslint-config-prettier": "^9.1.0", 71 | "eslint-plugin-prettier": "^5.1.3", 72 | "jest": "^29.7.0", 73 | "prettier": "^3.2.4", 74 | "react": "18.2.0", 75 | "react-native": "0.73.3", 76 | "react-native-builder-bob": "^0.23.2", 77 | "release-it": "^17.0.3", 78 | "typescript": "^5.3.3" 79 | }, 80 | "resolutions": { 81 | "@types/react": "17.0.21" 82 | }, 83 | "peerDependencies": { 84 | "react": "*", 85 | "react-native": "*" 86 | }, 87 | "engines": { 88 | "node": ">= 18" 89 | }, 90 | "jest": { 91 | "preset": "react-native", 92 | "modulePathIgnorePatterns": [ 93 | "/example/node_modules", 94 | "/lib/" 95 | ] 96 | }, 97 | "release-it": { 98 | "git": { 99 | "commitMessage": "chore: release ${version}", 100 | "tagName": "v${version}" 101 | }, 102 | "npm": { 103 | "publish": true 104 | }, 105 | "github": { 106 | "release": true 107 | }, 108 | "plugins": { 109 | "@release-it/conventional-changelog": { 110 | "preset": "angular" 111 | } 112 | } 113 | }, 114 | "prettier": { 115 | "quoteProps": "consistent", 116 | "semi": false, 117 | "singleQuote": true, 118 | "tabWidth": 2, 119 | "trailingComma": "es5", 120 | "useTabs": false 121 | }, 122 | "babel": { 123 | "presets": [ 124 | "module:metro-react-native-babel-preset" 125 | ] 126 | }, 127 | "react-native-builder-bob": { 128 | "source": "src", 129 | "output": "lib", 130 | "targets": [ 131 | "commonjs", 132 | "module", 133 | [ 134 | "typescript", 135 | { 136 | "project": "tsconfig.json" 137 | } 138 | ] 139 | ] 140 | }, 141 | "packageManager": "yarn@1.22.19+sha1.4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447", 142 | "codegenConfig": { 143 | "name": "RNTfliteSpec", 144 | "type": "modules", 145 | "jsSrcsDir": "src" 146 | }, 147 | "dependencies": {} 148 | } 149 | -------------------------------------------------------------------------------- /react-native-fast-tflite.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 | enableCoreMLDelegate = false 7 | if defined?($EnableCoreMLDelegate) 8 | enableCoreMLDelegate = $EnableCoreMLDelegate 9 | end 10 | Pod::UI.puts "[TFLite] CoreML Delegate is set to #{enableCoreMLDelegate}! ($EnableCoreMLDelegate setting in Podfile)" 11 | 12 | Pod::Spec.new do |s| 13 | s.name = "react-native-fast-tflite" 14 | s.version = package["version"] 15 | s.summary = package["description"] 16 | s.homepage = package["homepage"] 17 | s.license = package["license"] 18 | s.authors = package["author"] 19 | 20 | s.platforms = { :ios => "11.0" } 21 | s.source = { :git => "https://github.com/mrousavy/react-native-fast-tflite.git", :tag => "#{s.version}" } 22 | 23 | s.source_files = "ios/**/*.{h,m,mm}", "cpp/**/*.{hpp,cpp,c,h}" 24 | 25 | s.pod_target_xcconfig = { 26 | 'GCC_PREPROCESSOR_DEFINITIONS' => "$(inherited) FAST_TFLITE_ENABLE_CORE_ML=#{enableCoreMLDelegate}", 27 | 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++17', 28 | } 29 | 30 | s.dependency "TensorFlowLiteC", "2.17.0" 31 | if enableCoreMLDelegate then 32 | s.dependency "TensorFlowLiteC/CoreML", "2.17.0" 33 | end 34 | 35 | # Use install_modules_dependencies helper to install the dependencies if React Native version >=0.71.0. 36 | # See https://github.com/facebook/react-native/blob/febf6b7f33fdb4904669f99d795eba4c0f95d7bf/scripts/cocoapods/new_architecture.rb#L79. 37 | if respond_to?(:install_modules_dependencies, true) 38 | install_modules_dependencies(s) 39 | else 40 | s.dependency "React-Core" 41 | 42 | # Don't install the dependencies when we run `pod install` in the old architecture. 43 | if ENV['RCT_NEW_ARCH_ENABLED'] == '1' then 44 | s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" 45 | s.pod_target_xcconfig = { 46 | "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", 47 | 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++17', 48 | "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1", 49 | "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" 50 | } 51 | s.dependency "React-Codegen" 52 | s.dependency "RCT-Folly" 53 | s.dependency "RCTRequired" 54 | s.dependency "RCTTypeSafety" 55 | s.dependency "ReactCommon/turbomodule/core" 56 | end 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /scripts/check-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Formatting C++ code.." 4 | ./scripts/clang-format.sh 5 | 6 | echo "Linting JS/TS code.." 7 | yarn lint --fix 8 | yarn typescript 9 | 10 | echo "All done!" 11 | -------------------------------------------------------------------------------- /scripts/clang-format.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if which clang-format >/dev/null; then 4 | find cpp ios android/src/main/cpp -type f \( -name "*.h" -o -name "*.cpp" -o -name "*.m" -o -name "*.mm" \) -print0 | while read -d $'\0' file; do 5 | if [[ $file == *".framework"* ]]; then 6 | # Ignore iOS TensorFlow base library 7 | continue 8 | fi 9 | if [[ $file == *"/lib/"* ]]; then 10 | # Ignore Android TensorFlow base library 11 | continue 12 | fi 13 | echo "-> cpp-lint $file" 14 | clang-format -i "$file" 15 | done 16 | else 17 | echo "warning: clang-format not installed, download from https://clang.llvm.org/docs/ClangFormat.html (or run brew install clang-format)" 18 | fi 19 | -------------------------------------------------------------------------------- /src/NativeRNTflite.ts: -------------------------------------------------------------------------------- 1 | import type { TurboModule } from 'react-native' 2 | import { TurboModuleRegistry } from 'react-native' 3 | 4 | export interface Spec extends TurboModule { 5 | install(): boolean 6 | } 7 | 8 | export default TurboModuleRegistry.getEnforcing('Tflite') 9 | -------------------------------------------------------------------------------- /src/TensorflowLite.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import { Image } from 'react-native' 3 | import { TensorflowModule } from './TensorflowModule' 4 | 5 | type TypedArray = 6 | | Float32Array 7 | | Float64Array 8 | | Int8Array 9 | | Int16Array 10 | | Int32Array 11 | | Uint8Array 12 | | Uint16Array 13 | | Uint32Array 14 | | BigInt64Array 15 | | BigUint64Array 16 | 17 | declare global { 18 | /** 19 | * Loads the Model into memory. Path is fetchable resource, e.g.: 20 | * http://192.168.8.110:8081/assets/assets/model.tflite?platform=ios&hash=32e9958c83e5db7d0d693633a9f0b175 21 | */ 22 | // eslint-disable-next-line no-var 23 | var __loadTensorflowModel: ( 24 | path: string, 25 | delegate: TensorflowModelDelegate 26 | ) => Promise 27 | } 28 | // Installs the JSI bindings into the global namespace. 29 | console.log('Installing bindings...') 30 | const result = TensorflowModule.install() as boolean 31 | if (result !== true) 32 | console.error('Failed to install Tensorflow Lite bindings!') 33 | 34 | console.log('Successfully installed!') 35 | 36 | export type TensorflowModelDelegate = 37 | | 'default' 38 | | 'metal' 39 | | 'core-ml' 40 | | 'nnapi' 41 | | 'android-gpu' 42 | 43 | export interface Tensor { 44 | /** 45 | * The name of the Tensor. 46 | */ 47 | name: string 48 | /** 49 | * The data-type all values of this Tensor are represented in. 50 | */ 51 | dataType: 52 | | 'bool' 53 | | 'uint8' 54 | | 'int8' 55 | | 'int16' 56 | | 'int32' 57 | | 'int64' 58 | | 'float16' 59 | | 'float32' 60 | | 'float64' 61 | | 'invalid' 62 | /** 63 | * The shape of the data from this tensor. 64 | */ 65 | shape: number[] 66 | } 67 | 68 | export interface TensorflowModel { 69 | /** 70 | * The computation delegate used by this Model. 71 | * While CoreML and Metal delegates might be faster as they use the GPU, not all models support those delegates. 72 | */ 73 | delegate: TensorflowModelDelegate 74 | /** 75 | * Run the Tensorflow Model with the given input buffer. 76 | * The input buffer has to match the input tensor's shape. 77 | */ 78 | run(input: TypedArray[]): Promise 79 | /** 80 | * Synchronously run the Tensorflow Model with the given input buffer. 81 | * The input buffer has to match the input tensor's shape. 82 | */ 83 | runSync(input: TypedArray[]): TypedArray[] 84 | 85 | /** 86 | * All input tensors of this Tensorflow Model. 87 | */ 88 | inputs: Tensor[] 89 | /** 90 | * All output tensors of this Tensorflow Model. 91 | * The user is responsible for correctly interpreting this data. 92 | */ 93 | outputs: Tensor[] 94 | } 95 | 96 | // In React Native, `require(..)` returns a number. 97 | type Require = number // ReturnType 98 | type ModelSource = Require | { url: string } 99 | 100 | export type TensorflowPlugin = 101 | | { 102 | model: TensorflowModel 103 | state: 'loaded' 104 | } 105 | | { 106 | model: undefined 107 | state: 'loading' 108 | } 109 | | { 110 | model: undefined 111 | error: Error 112 | state: 'error' 113 | } 114 | 115 | /** 116 | * Load a Tensorflow Lite Model from the given `.tflite` asset. 117 | * 118 | * * If you are passing in a `.tflite` model from your app's bundle using `require(..)`, make sure to add `tflite` as an asset extension to `metro.config.js`! 119 | * * If you are passing in a `{ url: ... }`, make sure the URL points directly to a `.tflite` model. This can either be a web URL (`http://..`/`https://..`), or a local file (`file://..`). 120 | * 121 | * @param source The `.tflite` model in form of either a `require(..)` statement or a `{ url: string }`. 122 | * @param delegate The delegate to use for computations. Uses the standard CPU delegate per default. The `core-ml` or `metal` delegates are GPU-accelerated, but don't work on every model. 123 | * @returns The loaded Model. 124 | */ 125 | export function loadTensorflowModel( 126 | source: ModelSource, 127 | delegate: TensorflowModelDelegate = 'default' 128 | ): Promise { 129 | let uri: string 130 | if (typeof source === 'number') { 131 | console.log(`Loading Tensorflow Lite Model ${source}`) 132 | const asset = Image.resolveAssetSource(source) 133 | uri = asset.uri 134 | console.log(`Resolved Model path: ${asset.uri}`) 135 | } else if (typeof source === 'object' && 'url' in source) { 136 | uri = source.url 137 | } else { 138 | throw new Error( 139 | 'TFLite: Invalid source passed! Source should be either a React Native require(..) or a `{ url: string }` object!' 140 | ) 141 | } 142 | return global.__loadTensorflowModel(uri, delegate) 143 | } 144 | 145 | /** 146 | * Load a Tensorflow Lite Model from the given `.tflite` asset into a React State. 147 | * 148 | * * If you are passing in a `.tflite` model from your app's bundle using `require(..)`, make sure to add `tflite` as an asset extension to `metro.config.js`! 149 | * * If you are passing in a `{ url: ... }`, make sure the URL points directly to a `.tflite` model. This can either be a web URL (`http://..`/`https://..`), or a local file (`file://..`). 150 | * 151 | * @param source The `.tflite` model in form of either a `require(..)` statement or a `{ url: string }`. 152 | * @param delegate The delegate to use for computations. Uses the standard CPU delegate per default. The `core-ml` or `metal` delegates are GPU-accelerated, but don't work on every model. 153 | * @returns The state of the Model. 154 | */ 155 | export function useTensorflowModel( 156 | source: ModelSource, 157 | delegate: TensorflowModelDelegate = 'default' 158 | ): TensorflowPlugin { 159 | const [state, setState] = useState({ 160 | model: undefined, 161 | state: 'loading', 162 | }) 163 | 164 | useEffect(() => { 165 | const load = async (): Promise => { 166 | try { 167 | setState({ model: undefined, state: 'loading' }) 168 | const m = await loadTensorflowModel(source, delegate) 169 | setState({ model: m, state: 'loaded' }) 170 | console.log('Model loaded!') 171 | } catch (e) { 172 | console.error(`Failed to load Tensorflow Model ${source}!`, e) 173 | setState({ model: undefined, state: 'error', error: e as Error }) 174 | } 175 | } 176 | load() 177 | }, [delegate, source]) 178 | 179 | return state 180 | } 181 | -------------------------------------------------------------------------------- /src/TensorflowModule.ts: -------------------------------------------------------------------------------- 1 | export * from './NativeRNTflite' 2 | -------------------------------------------------------------------------------- /src/__tests__/index.test.tsx: -------------------------------------------------------------------------------- 1 | it.todo('write a test') 2 | -------------------------------------------------------------------------------- /src/expo-plugin/@types.ts: -------------------------------------------------------------------------------- 1 | export type ConfigProps = { 2 | /** 3 | * Whether to enable the CoreML GPU acceleration delegate for iOS, or not. 4 | * @default false 5 | */ 6 | enableCoreMLDelegate?: boolean 7 | /** 8 | * Whether to enable the GPU acceleration delegate for GPU, by including related native libraries. 9 | * You can leave it as boolean for an array of native libraries. 10 | * 11 | * If enabled, "libOpenCL.so" will always be included. 12 | * 13 | * When ran prebuild, it will yield following result. 14 | * 15 | * ```xml 16 | * 17 | * ``` 18 | * 19 | * @example 20 | * You can include more native libraries if needed. 21 | * ```json 22 | * [ 23 | * "react-native-fast-tflite", 24 | * { 25 | * "enableAndroidGpuLibraries": ["libOpenCL-pixel.so", "libGLES_mali.so"] 26 | * } 27 | * ] 28 | * ``` 29 | * 30 | * 31 | * @default false 32 | */ 33 | enableAndroidGpuLibraries?: boolean | string[] 34 | } | void 35 | -------------------------------------------------------------------------------- /src/expo-plugin/withAndroidGpuLibraries.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ConfigPlugin, 3 | AndroidConfig, 4 | withAndroidManifest, 5 | } from '@expo/config-plugins' 6 | import { 7 | ManifestApplication, 8 | prefixAndroidKeys, 9 | } from '@expo/config-plugins/build/android/Manifest' 10 | 11 | function addUsesNativeLibraryItemToMainApplication( 12 | mainApplication: AndroidConfig.Manifest.ManifestApplication & { 13 | 'uses-native-library'?: AndroidConfig.Manifest.ManifestUsesLibrary[] 14 | }, 15 | item: { name: string; required?: boolean } 16 | ): ManifestApplication { 17 | let existingMetaDataItem 18 | const newItem = { 19 | $: prefixAndroidKeys(item), 20 | } as AndroidConfig.Manifest.ManifestUsesLibrary 21 | 22 | if (mainApplication['uses-native-library'] !== undefined) { 23 | existingMetaDataItem = mainApplication['uses-native-library'].filter( 24 | (e) => e.$['android:name'] === item.name 25 | ) 26 | if ( 27 | existingMetaDataItem.length > 0 && 28 | existingMetaDataItem[0] !== undefined 29 | ) 30 | existingMetaDataItem[0].$ = newItem.$ 31 | else mainApplication['uses-native-library'].push(newItem) 32 | } else { 33 | mainApplication['uses-native-library'] = [newItem] 34 | } 35 | return mainApplication 36 | } 37 | 38 | export const withAndroidGpuLibraries: ConfigPlugin = ( 39 | cfg, 40 | enabledLibraries 41 | ) => 42 | withAndroidManifest(cfg, (config) => { 43 | const mainApplication = AndroidConfig.Manifest.getMainApplicationOrThrow( 44 | config.modResults 45 | ) 46 | const gpuLibraries = [{ name: 'libOpenCL.so', required: false }] 47 | 48 | if (Array.isArray(enabledLibraries)) { 49 | gpuLibraries.push( 50 | ...enabledLibraries.map((lib) => ({ name: lib, required: false })) 51 | ) 52 | } 53 | gpuLibraries.forEach((lib) => { 54 | addUsesNativeLibraryItemToMainApplication(mainApplication, lib) 55 | }) 56 | return config 57 | }) 58 | -------------------------------------------------------------------------------- /src/expo-plugin/withCoreMLDelegate.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | import { 4 | ConfigPlugin, 5 | withPlugins, 6 | withDangerousMod, 7 | withXcodeProject, 8 | ExportedConfigWithProps, 9 | XcodeProject, 10 | } from '@expo/config-plugins' 11 | import { insertContentsAtOffset } from '@expo/config-plugins/build/utils/commonCodeMod' 12 | 13 | export const withCoreMLDelegate: ConfigPlugin = (config) => 14 | withPlugins(config, [ 15 | // Add $EnableCoreMLDelegate = true to the top of the Podfile 16 | [ 17 | withDangerousMod, 18 | [ 19 | 'ios', 20 | (currentConfig: ExportedConfigWithProps) => { 21 | const podfilePath = path.join( 22 | currentConfig.modRequest.platformProjectRoot, 23 | 'Podfile' 24 | ) 25 | 26 | const prevPodfileContent = fs.readFileSync(podfilePath, 'utf-8') 27 | const newPodfileContent = insertContentsAtOffset( 28 | prevPodfileContent, 29 | '$EnableCoreMLDelegate=true\n', 30 | 0 31 | ) 32 | 33 | fs.writeFileSync(podfilePath, newPodfileContent) 34 | 35 | return currentConfig 36 | }, 37 | ], 38 | ], 39 | // Add the CoreML framework to the Xcode project 40 | [ 41 | withXcodeProject, 42 | (currentConfig: ExportedConfigWithProps) => { 43 | // `XcodeProject` isn't correctly exported from @expo/config-plugins, so TypeScript sees it as `any` 44 | // see https://github.com/expo/expo/issues/16470 45 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment 46 | const xcodeProject = currentConfig.modResults 47 | xcodeProject.addFramework('CoreML.framework') 48 | 49 | return currentConfig 50 | }, 51 | ], 52 | ]) 53 | -------------------------------------------------------------------------------- /src/expo-plugin/withFastTFLite.ts: -------------------------------------------------------------------------------- 1 | import { ConfigPlugin, createRunOncePlugin } from '@expo/config-plugins' 2 | import { ConfigProps } from './@types' 3 | import { withCoreMLDelegate } from './withCoreMLDelegate' 4 | import { withAndroidGpuLibraries } from './withAndroidGpuLibraries' 5 | // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-var-requires 6 | const pkg = require('../../../package.json') // from the lib directory, the package.json is three levels up 7 | 8 | const withFastTFLite: ConfigPlugin = (config, props) => { 9 | if (props?.enableCoreMLDelegate) config = withCoreMLDelegate(config) 10 | if ( 11 | props?.enableAndroidGpuLibraries === true || 12 | Array.isArray(props?.enableAndroidGpuLibraries) 13 | ) 14 | config = withAndroidGpuLibraries(config, props.enableAndroidGpuLibraries) 15 | 16 | return config 17 | } 18 | 19 | export default createRunOncePlugin(withFastTFLite, pkg.name, pkg.version) 20 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './TensorflowLite' 2 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": false, 4 | "allowUnreachableCode": false, 5 | "allowUnusedLabels": false, 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "jsx": "react-native", 9 | "lib": ["esnext"], 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "noFallthroughCasesInSwitch": true, 13 | "noImplicitReturns": true, 14 | "noStrictGenericChecks": false, 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": true, 17 | "noUncheckedIndexedAccess": true, 18 | "resolveJsonModule": true, 19 | "skipLibCheck": true, 20 | "strict": true, 21 | "target": "esnext", 22 | "outDir": "lib" 23 | }, 24 | "include": [ 25 | "src", 26 | ".eslintrc.js", 27 | "babel.config.js", 28 | ], 29 | "exclude": [ 30 | "node_modules", 31 | "lib", 32 | "docs", 33 | "example" 34 | ] 35 | } 36 | --------------------------------------------------------------------------------