├── tsconfig.build.json
├── babel.config.js
├── android
├── src
│ └── main
│ │ ├── AndroidManifestNew.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── visioncameratextrecognition
│ │ ├── VisionCameraTextRecognitionPackage.kt
│ │ ├── RemoveLanguageModel.kt
│ │ ├── TranslateLanguageEnum.kt
│ │ ├── PhotoRecognizerModule.kt
│ │ ├── VisionCameraTranslatorPlugin.kt
│ │ └── VisionCameraTextRecognitionPlugin.kt
├── gradle.properties
└── build.gradle
├── ios
├── VisionCameraTextRecognition-Bridging-Header.h
├── RemoveLanguageModel.swift
├── PhotoRecognizerModule.swift
├── VisionCameraTextRecognition.mm
├── TranslateLanguage.swift
├── VisionCameraTranslator.swift
└── VisionCameraTextRecognition.swift
├── src
├── index.ts
├── RemoveLanguageModel.ts
├── PhotoRecognizer.ts
├── translateText.ts
├── scanText.ts
├── Camera.tsx
└── types.ts
├── turbo.json
├── tsconfig.json
├── .github
└── FUNDING.yml
├── LICENSE
├── VisionCameraTextRecognition.podspec
├── .gitignore
├── package.json
├── CONTRIBUTING.md
├── CODE_OF_CONDUCT.md
└── README.md
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig",
3 | "exclude": ["example"]
4 | }
5 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['module:@react-native/babel-preset'],
3 | };
4 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifestNew.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/ios/VisionCameraTextRecognition-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #if VISION_CAMERA_ENABLE_FRAME_PROCESSORS
2 | #import
3 | #import
4 | #endif
5 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { Camera, useTranslate, useTextRecognition } from './Camera';
2 |
3 | export { RemoveLanguageModel } from './RemoveLanguageModel';
4 |
5 | export { PhotoRecognizer } from './PhotoRecognizer';
6 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | VisionCameraTextRecognition_kotlinVersion=1.7.0
2 | VisionCameraTextRecognition_minSdkVersion=21
3 | VisionCameraTextRecognition_targetSdkVersion=31
4 | VisionCameraTextRecognition_compileSdkVersion=31
5 | VisionCameraTextRecognition_ndkversion=21.4.7075529
6 |
--------------------------------------------------------------------------------
/src/RemoveLanguageModel.ts:
--------------------------------------------------------------------------------
1 | import { NativeModules } from 'react-native';
2 | import type { Languages } from './types';
3 |
4 | export async function RemoveLanguageModel(code: Languages): Promise {
5 | const { RemoveLanguageModel: Remove } = NativeModules;
6 | return await Remove.remove(code);
7 | }
8 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/PhotoRecognizer.ts:
--------------------------------------------------------------------------------
1 | import { NativeModules, Platform } from 'react-native';
2 | import type { PhotoOptions, Text } from './types';
3 |
4 | export async function PhotoRecognizer(options: PhotoOptions): Promise {
5 | const { PhotoRecognizerModule } = NativeModules;
6 | const { uri, orientation } = options;
7 | if (!uri) {
8 | throw Error("Can't resolve img uri");
9 | }
10 | if (Platform.OS === 'ios') {
11 | return await PhotoRecognizerModule.process(
12 | uri.replace('file://', ''),
13 | orientation || 'portrait'
14 | );
15 | } else {
16 | return await PhotoRecognizerModule.process(uri);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/translateText.ts:
--------------------------------------------------------------------------------
1 | import type { Frame, TranslatorPlugin, TranslatorOptions } from './types';
2 | import { VisionCameraProxy } from 'react-native-vision-camera';
3 |
4 | const LINKING_ERROR = `Can't load plugin translate.Try cleaning cache or reinstall plugin.`;
5 |
6 | export function createTranslatorPlugin(
7 | options?: TranslatorOptions
8 | ): TranslatorPlugin {
9 | const plugin = VisionCameraProxy.initFrameProcessorPlugin('translate', {
10 | ...options,
11 | });
12 | if (!plugin) {
13 | throw new Error(LINKING_ERROR);
14 | }
15 | return {
16 | translate: (frame: Frame): string => {
17 | 'worklet';
18 | return plugin.call(frame) as string;
19 | },
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/src/scanText.ts:
--------------------------------------------------------------------------------
1 | import { VisionCameraProxy } from 'react-native-vision-camera';
2 | import type {
3 | Frame,
4 | TextRecognitionPlugin,
5 | TextRecognitionOptions,
6 | Text,
7 | } from './types';
8 |
9 | const LINKING_ERROR = `Can't load plugin scanText.Try cleaning cache or reinstall plugin.`;
10 |
11 | export function createTextRecognitionPlugin(
12 | options?: TextRecognitionOptions
13 | ): TextRecognitionPlugin {
14 | const plugin = VisionCameraProxy.initFrameProcessorPlugin('scanText', {
15 | ...options,
16 | });
17 | if (!plugin) {
18 | throw new Error(LINKING_ERROR);
19 | }
20 | return {
21 | scanText: (frame: Frame): Text[] => {
22 | 'worklet';
23 | // @ts-ignore
24 | return plugin.call(frame) as Text[];
25 | },
26 | };
27 | }
28 |
--------------------------------------------------------------------------------
/ios/RemoveLanguageModel.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import React
3 | import MLKitTranslate
4 |
5 | @objc(RemoveLanguageModel)
6 | class RemoveLanguageModel: NSObject {
7 |
8 | @objc(remove:withResolver:withRejecter:)
9 | private func remove(code: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
10 | guard let modelName = TranslateLanguage(from: code) else {
11 | resolve(false)
12 | return
13 | }
14 | let model = TranslateRemoteModel.translateRemoteModel(language: modelName)
15 | ModelManager.modelManager().deleteDownloadedModel(model) { error in
16 | guard error == nil else {
17 | return
18 | }
19 | resolve(true)
20 | }
21 | }
22 | }
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "pipeline": {
4 | "build:android": {
5 | "inputs": [
6 | "package.json",
7 | "android",
8 | "!android/build",
9 | "src/*.ts",
10 | "src/*.tsx",
11 | "example/package.json",
12 | "example/android",
13 | "!example/android/.gradle",
14 | "!example/android/build",
15 | "!example/android/app/build"
16 | ],
17 | "outputs": []
18 | },
19 | "build:ios": {
20 | "inputs": [
21 | "package.json",
22 | "*.podspec",
23 | "ios",
24 | "src/*.ts",
25 | "src/*.tsx",
26 | "example/package.json",
27 | "example/ios",
28 | "!example/ios/build",
29 | "!example/ios/Pods"
30 | ],
31 | "outputs": []
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "rootDir": ".",
4 | "paths": {
5 | "react-native-vision-camera-text-recognition": ["./src/index"]
6 | },
7 | "allowUnreachableCode": false,
8 | "allowUnusedLabels": false,
9 | "esModuleInterop": true,
10 | "forceConsistentCasingInFileNames": true,
11 | "jsx": "react",
12 | "lib": ["esnext"],
13 | "module": "esnext",
14 | "moduleResolution": "node",
15 | "noFallthroughCasesInSwitch": true,
16 | "noImplicitReturns": true,
17 | "noImplicitUseStrict": false,
18 | "noStrictGenericChecks": false,
19 | "noUncheckedIndexedAccess": true,
20 | "noUnusedLocals": true,
21 | "noUnusedParameters": true,
22 | "resolveJsonModule": true,
23 | "skipLibCheck": true,
24 | "strict": true,
25 | "target": "esnext",
26 | "verbatimModuleSyntax": true
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: gev2002
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 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
12 | polar: # Replace with a single Polar username
13 | buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
14 | thanks_dev: # Replace with a single thanks.dev username
15 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 gev2002
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 |
--------------------------------------------------------------------------------
/VisionCameraTextRecognition.podspec:
--------------------------------------------------------------------------------
1 | require "json"
2 |
3 | package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4 | folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
5 |
6 | Pod::Spec.new do |s|
7 | s.name = "VisionCameraTextRecognition"
8 | s.version = package["version"]
9 | s.summary = package["description"]
10 | s.homepage = package["homepage"]
11 | s.license = package["license"]
12 | s.authors = package["author"]
13 |
14 | s.platforms = { :ios => min_ios_version_supported }
15 | s.source = { :git => "https://github.com/gev2002/react-native-vision-camera-text-recognition.git", :tag => "#{s.version}" }
16 |
17 | s.source_files = "ios/**/*.{h,m,mm,swift}"
18 |
19 | s.dependency "React-Core"
20 | s.dependency "VisionCamera"
21 | s.dependency "GoogleMLKit/TextRecognition"
22 | s.dependency "GoogleMLKit/TextRecognitionChinese"
23 | s.dependency "GoogleMLKit/TextRecognitionDevanagari"
24 | s.dependency "GoogleMLKit/TextRecognitionJapanese"
25 | s.dependency "GoogleMLKit/TextRecognitionKorean"
26 | s.dependency "GoogleMLKit/Translate"
27 |
28 | end
29 |
--------------------------------------------------------------------------------
/android/src/main/java/com/visioncameratextrecognition/VisionCameraTextRecognitionPackage.kt:
--------------------------------------------------------------------------------
1 | package com.visioncameratextrecognition
2 |
3 | import com.facebook.react.ReactPackage
4 | import com.facebook.react.bridge.ReactApplicationContext
5 | import com.facebook.react.uimanager.ViewManager
6 | import com.facebook.react.bridge.NativeModule
7 | import com.mrousavy.camera.frameprocessors.FrameProcessorPluginRegistry
8 |
9 | class VisionCameraTextRecognitionPackage : ReactPackage {
10 | companion object {
11 | init {
12 | FrameProcessorPluginRegistry.addFrameProcessorPlugin("scanText") { proxy, options ->
13 | VisionCameraTextRecognitionPlugin(proxy, options)
14 | }
15 | FrameProcessorPluginRegistry.addFrameProcessorPlugin("translate") { proxy, options ->
16 | VisionCameraTranslatorPlugin(proxy, options)
17 | }
18 | }
19 | }
20 |
21 | override fun createViewManagers(reactContext: ReactApplicationContext): List> {
22 | return emptyList()
23 | }
24 |
25 | override fun createNativeModules(reactContext: ReactApplicationContext): List {
26 | return listOf(
27 | RemoveLanguageModel(reactContext),
28 | PhotoRecognizerModule(reactContext)
29 | )
30 | }
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/android/src/main/java/com/visioncameratextrecognition/RemoveLanguageModel.kt:
--------------------------------------------------------------------------------
1 | package com.visioncameratextrecognition
2 |
3 | import com.facebook.react.bridge.Promise
4 | import com.facebook.react.bridge.ReactApplicationContext
5 | import com.facebook.react.bridge.ReactContextBaseJavaModule
6 | import com.facebook.react.bridge.ReactMethod
7 | import com.google.mlkit.common.model.RemoteModelManager
8 | import com.google.mlkit.nl.translate.TranslateRemoteModel
9 |
10 | class RemoveLanguageModel(reactContext: ReactApplicationContext) :
11 | ReactContextBaseJavaModule(reactContext) {
12 |
13 | @ReactMethod
14 | fun remove(code: String, promise: Promise) {
15 | val modelName = translateLanguage(code)?.let { TranslateRemoteModel.Builder(it).build() }
16 | if (modelName != null) {
17 | modelManager.deleteDownloadedModel(modelName)
18 | .addOnSuccessListener {
19 | promise.resolve(true)
20 | }
21 | .addOnFailureListener {
22 | promise.resolve(false)
23 | }
24 | } else {
25 | promise.resolve(false)
26 | }
27 | }
28 |
29 | private val modelManager = RemoteModelManager.getInstance()
30 | override fun getName(): String {
31 | return NAME
32 | }
33 |
34 | companion object {
35 | const val NAME = "RemoveLanguageModel"
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/.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 | .yarnrc.yml
56 | yarn-error.log
57 |
58 | # BUCK
59 | buck-out/
60 | \.buckd/
61 | android/app/libs
62 | android/keystores/debug.keystore
63 |
64 | # Yarn
65 | .yarn/*
66 | !.yarn/patches
67 | !.yarn/plugins
68 | !.yarn/releases
69 | !.yarn/sdks
70 | !.yarn/versions
71 | .yarnrc.yml
72 | .yarn
73 | yarn.lock
74 | # Expo
75 | .expo/
76 |
77 | # Turborepo
78 | .turbo/
79 |
80 | # generated by bob
81 | lib/
82 |
83 |
84 | # Watchman
85 | .watchmanconfig
86 |
87 | # NVM
88 | .nvmrc
89 | # Editor
90 | .editorconfig
91 |
92 | # Github
93 |
94 | .gitattributes
95 | .github
96 |
97 | # LeftHook
98 |
99 | lefthook.yml
100 | package-lock.json
101 | # Example
102 | example
103 | .pnp/
104 | .pnp.*
105 |
106 |
--------------------------------------------------------------------------------
/android/src/main/java/com/visioncameratextrecognition/TranslateLanguageEnum.kt:
--------------------------------------------------------------------------------
1 | package com.visioncameratextrecognition
2 |
3 | import com.google.mlkit.nl.translate.TranslateLanguage.*
4 |
5 | fun translateLanguage(language: String): String? {
6 | return when (language.lowercase()) {
7 | "af" -> AFRIKAANS
8 | "ar" -> ARABIC
9 | "be" -> BELARUSIAN
10 | "bg" -> BULGARIAN
11 | "bn" -> BENGALI
12 | "ca" -> CATALAN
13 | "cs" -> CZECH
14 | "cy" -> WELSH
15 | "da" -> DANISH
16 | "de" -> GERMAN
17 | "el" -> GREEK
18 | "en" -> ENGLISH
19 | "eo" -> ESPERANTO
20 | "es" -> SPANISH
21 | "et" -> ESTONIAN
22 | "fa" -> PERSIAN
23 | "fi" -> FINNISH
24 | "fr" -> FRENCH
25 | "ga" -> IRISH
26 | "gl" -> GALICIAN
27 | "gu" -> GUJARATI
28 | "he" -> HEBREW
29 | "hi" -> HINDI
30 | "hr" -> CROATIAN
31 | "ht" -> HAITIAN_CREOLE
32 | "hu" -> HUNGARIAN
33 | "id" -> INDONESIAN
34 | "is" -> ICELANDIC
35 | "it" -> ITALIAN
36 | "ja" -> JAPANESE
37 | "ka" -> GEORGIAN
38 | "kn" -> KANNADA
39 | "ko" -> KOREAN
40 | "lt" -> LITHUANIAN
41 | "lv" -> LATVIAN
42 | "mk" -> MACEDONIAN
43 | "mr" -> MARATHI
44 | "ms" -> MALAY
45 | "mt" -> MALTESE
46 | "nl" -> DUTCH
47 | "no" -> NORWEGIAN
48 | "pl" -> POLISH
49 | "pt" -> PORTUGUESE
50 | "ro" -> ROMANIAN
51 | "ru" -> RUSSIAN
52 | "sk" -> SLOVAK
53 | "sl" -> SLOVENIAN
54 | "sq" -> ALBANIAN
55 | "sv" -> SWEDISH
56 | "sw" -> SWAHILI
57 | "ta" -> TAMIL
58 | "te" -> TELUGU
59 | "th" -> THAI
60 | "tl" -> TAGALOG
61 | "tr" -> TURKISH
62 | "uk" -> UKRAINIAN
63 | "ur" -> URDU
64 | "vi" -> VIETNAMESE
65 | "zh" -> CHINESE
66 | else -> null
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/android/src/main/java/com/visioncameratextrecognition/PhotoRecognizerModule.kt:
--------------------------------------------------------------------------------
1 | package com.visioncameratextrecognition
2 |
3 | import android.net.Uri
4 | import com.facebook.react.bridge.Promise
5 | import com.facebook.react.bridge.ReactApplicationContext
6 | import com.facebook.react.bridge.ReactContextBaseJavaModule
7 | import com.facebook.react.bridge.ReactMethod
8 | import com.facebook.react.bridge.WritableNativeMap
9 | import com.google.android.gms.tasks.Task
10 | import com.google.android.gms.tasks.Tasks
11 | import com.google.mlkit.vision.common.InputImage
12 | import com.google.mlkit.vision.text.Text
13 | import com.google.mlkit.vision.text.TextRecognition
14 | import com.google.mlkit.vision.text.latin.TextRecognizerOptions
15 |
16 | class PhotoRecognizerModule(reactContext: ReactApplicationContext) :
17 | ReactContextBaseJavaModule(reactContext) {
18 |
19 | private val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
20 |
21 | @ReactMethod
22 | fun process(uri:String,promise:Promise){
23 | val parsedUri = Uri.parse(uri)
24 | val data = WritableNativeMap()
25 | val image = InputImage.fromFilePath(this.reactApplicationContext,parsedUri)
26 | val task: Task = recognizer.process(image)
27 | try {
28 | val text: Text = Tasks.await(task)
29 | if (text.text.isEmpty()) {
30 | promise.resolve(WritableNativeMap())
31 | }
32 | data.putString("resultText", text.text)
33 | data.putArray("blocks", VisionCameraTextRecognitionPlugin.getBlocks(text.textBlocks))
34 | promise.resolve(data)
35 | } catch (e: Exception) {
36 | e.printStackTrace()
37 | promise.reject("Error", "Error processing image")
38 | }
39 |
40 | promise.resolve(true)
41 |
42 | }
43 | override fun getName(): String {
44 | return NAME
45 | }
46 | companion object {
47 | const val NAME = "PhotoRecognizerModule"
48 | }
49 | }
50 |
51 |
--------------------------------------------------------------------------------
/ios/PhotoRecognizerModule.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 | import React
4 | import MLKitVision
5 | import MLKitTextRecognition
6 |
7 |
8 |
9 | @objc(PhotoRecognizerModule)
10 | class PhotoRecognizerModule: NSObject {
11 |
12 | private static let options = TextRecognizerOptions()
13 | private let textRecognizer = TextRecognizer.textRecognizer(options:options)
14 | private var data: [String: Any] = [:]
15 |
16 | @objc(process:orientation:withResolver:withRejecter:)
17 | private func process(uri: String,orientation:String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
18 | let image = UIImage(contentsOfFile: uri)
19 | if image != nil {
20 | do {
21 | let visionImage = VisionImage(image: image!)
22 | visionImage.orientation = getOrientation(orientation: orientation)
23 | let result = try textRecognizer.results(in: visionImage)
24 | let blocks = VisionCameraTextRecognition.processBlocks(blocks: result.blocks)
25 | data["resultText"] = result.text
26 | data["blocks"] = blocks
27 | if result.text.isEmpty {
28 | resolve([:])
29 | }else{
30 | resolve(data)
31 | }
32 | }catch{
33 | reject("Error","Processing Image",nil)
34 | }
35 | }else{
36 | reject("Error","Can't Find Photo",nil)
37 | }
38 | }
39 | private func getOrientation(
40 | orientation: String
41 | ) -> UIImage.Orientation {
42 | switch orientation {
43 | case "portrait":
44 | return .right
45 | case "landscapeLeft":
46 | return .up
47 | case "portraitUpsideDown":
48 | return .left
49 | case "landscapeRight":
50 | return .down
51 | default:
52 | return .up
53 | }
54 |
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/ios/VisionCameraTextRecognition.mm:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 | #import
4 | #import
5 |
6 |
7 | #if __has_include("VisionCameraTextRecognition/VisionCameraTextRecognition-Swift.h")
8 | #import "VisionCameraTextRecognition/VisionCameraTextRecognition-Swift.h"
9 | #else
10 | #import "VisionCameraTextRecognition-Swift.h"
11 | #endif
12 |
13 | @interface VisionCameraTextRecognition (FrameProcessorPluginLoader)
14 | @end
15 |
16 | @implementation VisionCameraTextRecognition (FrameProcessorPluginLoader)
17 | + (void) load {
18 | [FrameProcessorPluginRegistry addFrameProcessorPlugin:@"scanText"
19 | withInitializer:^FrameProcessorPlugin*(VisionCameraProxyHolder* proxy, NSDictionary* options) {
20 | return [[VisionCameraTextRecognition alloc] initWithProxy:proxy withOptions:options];
21 | }];
22 | }
23 | @end
24 |
25 |
26 |
27 | @interface VisionCameraTranslator (FrameProcessorPluginLoader)
28 | @end
29 |
30 | @implementation VisionCameraTranslator (FrameProcessorPluginLoader)
31 | + (void) load {
32 | [FrameProcessorPluginRegistry addFrameProcessorPlugin:@"translate"
33 | withInitializer:^FrameProcessorPlugin*(VisionCameraProxyHolder* proxy, NSDictionary* options) {
34 | return [[VisionCameraTranslator alloc] initWithProxy:proxy withOptions:options];
35 | }];
36 | }
37 | @end
38 |
39 |
40 |
41 |
42 |
43 | #import
44 | #import
45 |
46 | @interface RCT_EXTERN_MODULE(RemoveLanguageModel, NSObject)
47 |
48 | RCT_EXTERN_METHOD(remove:(NSString *)code
49 | withResolver:(RCTPromiseResolveBlock)resolve
50 | withRejecter:(RCTPromiseRejectBlock)reject)
51 |
52 | + (BOOL)requiresMainQueueSetup
53 | {
54 | return NO;
55 | }
56 |
57 | @end
58 |
59 | @interface RCT_EXTERN_MODULE(PhotoRecognizerModule, NSObject)
60 |
61 | RCT_EXTERN_METHOD(process:(NSString *)uri
62 | orientation:(NSString *)orientation
63 | withResolver:(RCTPromiseResolveBlock)resolve
64 | withRejecter:(RCTPromiseRejectBlock)reject)
65 |
66 |
67 | + (BOOL)requiresMainQueueSetup
68 | {
69 | return NO;
70 | }
71 |
72 | @end
73 |
--------------------------------------------------------------------------------
/src/Camera.tsx:
--------------------------------------------------------------------------------
1 | import React, { forwardRef, type ForwardedRef, useMemo } from 'react';
2 | import {
3 | Camera as VisionCamera,
4 | useFrameProcessor,
5 | } from 'react-native-vision-camera';
6 | import { createTextRecognitionPlugin } from './scanText';
7 | import { useRunOnJS } from 'react-native-worklets-core';
8 | import type {
9 | CameraTypes,
10 | Text,
11 | Frame,
12 | ReadonlyFrameProcessor,
13 | TextRecognitionPlugin,
14 | TranslatorPlugin,
15 | TextRecognitionOptions,
16 | TranslatorOptions,
17 | } from './types';
18 | import { createTranslatorPlugin } from './translateText';
19 |
20 | export const Camera = forwardRef(function Camera(
21 | props: CameraTypes,
22 | ref: ForwardedRef
23 | ) {
24 | const { device, callback, options, mode, ...p } = props;
25 |
26 | let plugin: TranslatorPlugin['translate'] | TextRecognitionPlugin['scanText'];
27 | if (mode === 'translate') {
28 | // eslint-disable-next-line react-hooks/rules-of-hooks
29 | const { translate } = useTranslate(options);
30 |
31 | plugin = translate;
32 | } else {
33 | // eslint-disable-next-line react-hooks/rules-of-hooks
34 | const { scanText } = useTextRecognition(options);
35 | plugin = scanText;
36 | }
37 |
38 | const useWorklets = useRunOnJS(
39 | (data): void => {
40 | callback(data);
41 | },
42 | [options]
43 | );
44 | const frameProcessor: ReadonlyFrameProcessor = useFrameProcessor(
45 | (frame: Frame) => {
46 | 'worklet';
47 | const data: Text[] | string = plugin(frame);
48 | // @ts-ignore
49 | useWorklets(data);
50 | },
51 | []
52 | );
53 | return (
54 | <>
55 | {!!device && (
56 |
63 | )}
64 | >
65 | );
66 | });
67 |
68 | export function useTextRecognition(
69 | options?: TextRecognitionOptions
70 | ): TextRecognitionPlugin {
71 | return useMemo(() => createTextRecognitionPlugin(options), [options]);
72 | }
73 | export function useTranslate(options?: TranslatorOptions): TranslatorPlugin {
74 | return useMemo(() => createTranslatorPlugin(options), [options]);
75 | }
76 |
--------------------------------------------------------------------------------
/src/types.ts:
--------------------------------------------------------------------------------
1 | export type {
2 | Frame,
3 | ReadonlyFrameProcessor,
4 | FrameProcessorPlugin,
5 | FrameInternal,
6 | CameraProps,
7 | CameraDevice,
8 | } from 'react-native-vision-camera';
9 | export type { ForwardedRef } from 'react';
10 | import type { CameraProps, Frame } from 'react-native-vision-camera';
11 |
12 | export type Languages =
13 | | 'af'
14 | | 'sq'
15 | | 'ar'
16 | | 'be'
17 | | 'bn'
18 | | 'bg'
19 | | 'ca'
20 | | 'zh'
21 | | 'cs'
22 | | 'da'
23 | | 'nl'
24 | | 'en'
25 | | 'eo'
26 | | 'et'
27 | | 'fi'
28 | | 'fr'
29 | | 'gl'
30 | | 'ka'
31 | | 'de'
32 | | 'el'
33 | | 'gu'
34 | | 'ht'
35 | | 'he'
36 | | 'hi'
37 | | 'hu'
38 | | 'is'
39 | | 'id'
40 | | 'ga'
41 | | 'it'
42 | | 'ja'
43 | | 'kn'
44 | | 'ko'
45 | | 'lv'
46 | | 'lt'
47 | | 'mk'
48 | | 'ms'
49 | | 'mt'
50 | | 'mr'
51 | | 'no'
52 | | 'fa'
53 | | 'pl'
54 | | 'pt'
55 | | 'ro'
56 | | 'ru'
57 | | 'sk'
58 | | 'sl'
59 | | 'es'
60 | | 'sw'
61 | | 'tl'
62 | | 'ta'
63 | | 'te'
64 | | 'th'
65 | | 'tr'
66 | | 'uk'
67 | | 'ur'
68 | | 'vi'
69 | | 'cy';
70 |
71 | export type TextRecognitionOptions = {
72 | language: 'latin' | 'chinese' | 'devanagari' | 'japanese' | 'korean';
73 | };
74 |
75 | export type TranslatorOptions = {
76 | from: Languages;
77 | to: Languages;
78 | };
79 |
80 | export type CameraTypes = {
81 | callback: (data: string | Text[]) => void;
82 | mode: 'translate' | 'recognize';
83 | } & CameraProps & (
84 | | { mode: 'recognize'; options: TextRecognitionOptions }
85 | | { mode: 'translate'; options: TranslatorOptions }
86 | );;
87 |
88 |
89 |
90 | export type TextRecognitionPlugin = {
91 | scanText: (frame: Frame) => Text[];
92 | };
93 | export type TranslatorPlugin = {
94 | translate: (frame: Frame) => string;
95 | };
96 |
97 | export type Text = {
98 | blocks: BlocksData;
99 | resultText: string;
100 | };
101 |
102 | type BlocksData = [
103 | blockFrame: FrameType,
104 | blockCornerPoints: CornerPointsType,
105 | lines: LinesData,
106 | blockLanguages: string[] | [],
107 | blockText: string,
108 | ];
109 |
110 | type CornerPointsType = [{ x: number; y: number }];
111 |
112 | type FrameType = {
113 | boundingCenterX: number;
114 | boundingCenterY: number;
115 | height: number;
116 | width: number;
117 | x: number;
118 | y: number;
119 | };
120 |
121 | type LinesData = [
122 | lineCornerPoints: CornerPointsType,
123 | elements: ElementsData,
124 | lineFrame: FrameType,
125 | lineLanguages: string[] | [],
126 | lineText: string,
127 | ];
128 |
129 | type ElementsData = [
130 | elementCornerPoints: CornerPointsType,
131 | elementFrame: FrameType,
132 | elementText: string,
133 | ];
134 |
135 | export type PhotoOptions = {
136 | uri: string;
137 | orientation?:
138 | | 'landscapeRight'
139 | | 'portrait'
140 | | 'portraitUpsideDown'
141 | | 'landscapeLeft';
142 | };
143 |
--------------------------------------------------------------------------------
/ios/TranslateLanguage.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import MLKitTranslate
3 |
4 | func TranslateLanguage(from language: String) -> TranslateLanguage? {
5 | switch language.lowercased() {
6 | case "af":
7 | return .afrikaans
8 | case "sq":
9 | return .albanian
10 | case "ar":
11 | return .arabic
12 | case "be":
13 | return .belarusian
14 | case "bg":
15 | return .bengali
16 | case "bn":
17 | return .bulgarian
18 | case "ca":
19 | return .catalan
20 | case "zh":
21 | return .chinese
22 | case "cs":
23 | return .czech
24 | case "da":
25 | return .danish
26 | case "nl":
27 | return .dutch
28 | case "en":
29 | return .english
30 | case "eo":
31 | return .eperanto
32 | case "et":
33 | return .estonian
34 | case "fi":
35 | return .finnish
36 | case "fr":
37 | return .french
38 | case "gl":
39 | return .galician
40 | case "ka":
41 | return .georgian
42 | case "de":
43 | return .german
44 | case "el":
45 | return .greek
46 | case "gu":
47 | return .gujarati
48 | case "ht":
49 | return .haitianCreole
50 | case "he":
51 | return .hebrew
52 | case "hi":
53 | return .hindi
54 | case "hu":
55 | return .hungarian
56 | case "is":
57 | return .icelandic
58 | case "id":
59 | return .indonesian
60 | case "ga":
61 | return .irish
62 | case "it":
63 | return .italian
64 | case "ja":
65 | return .japanese
66 | case "kn":
67 | return .kannada
68 | case "ko":
69 | return .korean
70 | case "lv":
71 | return .latvian
72 | case "lt":
73 | return .lithuanian
74 | case "mk":
75 | return .macedonian
76 | case "ms":
77 | return .malay
78 | case "mt":
79 | return .maltese
80 | case "mr":
81 | return .marathi
82 | case "no":
83 | return .norwegian
84 | case "fa":
85 | return .persian
86 | case "pl":
87 | return .polish
88 | case "pt":
89 | return .portuguese
90 | case "ro":
91 | return .romanian
92 | case "ru":
93 | return .russian
94 | case "sk":
95 | return .slovak
96 | case "sl":
97 | return .slovenian
98 | case "es":
99 | return .spanish
100 | case "sw":
101 | return .swahili
102 | case "tl":
103 | return .tagalog
104 | case "ta":
105 | return .tamil
106 | case "te":
107 | return .telugu
108 | case "th":
109 | return .thai
110 | case "tr":
111 | return .turkish
112 | case "uk":
113 | return .ukrainian
114 | case "ur":
115 | return .urdu
116 | case "vi":
117 | return .vietnamese
118 | case "cy":
119 | return .welsh
120 | default:
121 | return nil
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | // Buildscript is evaluated before everything else so we can't use getExtOrDefault
3 | def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["VisionCameraTextRecognition_kotlinVersion"]
4 |
5 | repositories {
6 | google()
7 | mavenCentral()
8 | }
9 |
10 | dependencies {
11 | classpath "com.android.tools.build:gradle:7.2.1"
12 | // noinspection DifferentKotlinGradleVersion
13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14 | }
15 | }
16 |
17 | def isNewArchitectureEnabled() {
18 | return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
19 | }
20 |
21 | apply plugin: "com.android.library"
22 | apply plugin: "kotlin-android"
23 |
24 | if (isNewArchitectureEnabled()) {
25 | apply plugin: "com.facebook.react"
26 | }
27 |
28 | def getExtOrDefault(name) {
29 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["VisionCameraTextRecognition_" + name]
30 | }
31 |
32 | def getExtOrIntegerDefault(name) {
33 | return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["VisionCameraTextRecognition_" + name]).toInteger()
34 | }
35 |
36 | def supportsNamespace() {
37 | def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')
38 | def major = parsed[0].toInteger()
39 | def minor = parsed[1].toInteger()
40 |
41 | // Namespace support was added in 7.3.0
42 | return (major == 7 && minor >= 3) || major >= 8
43 | }
44 |
45 | android {
46 | if (supportsNamespace()) {
47 | namespace "com.visioncameratextrecognition"
48 |
49 | sourceSets {
50 | main {
51 | manifest.srcFile "src/main/AndroidManifestNew.xml"
52 | }
53 | }
54 | }
55 |
56 | compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
57 |
58 | defaultConfig {
59 | minSdkVersion getExtOrIntegerDefault("minSdkVersion")
60 | targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
61 |
62 | }
63 |
64 | buildTypes {
65 | release {
66 | minifyEnabled false
67 | }
68 | }
69 |
70 | lintOptions {
71 | disable "GradleCompatible"
72 | }
73 |
74 | compileOptions {
75 | sourceCompatibility JavaVersion.VERSION_1_8
76 | targetCompatibility JavaVersion.VERSION_1_8
77 | }
78 | }
79 |
80 | repositories {
81 | mavenCentral()
82 | google()
83 | }
84 |
85 | def kotlin_version = getExtOrDefault("kotlinVersion")
86 |
87 | dependencies {
88 | // For < 0.71, this will be from the local maven repo
89 | // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
90 | //noinspection GradleDynamicVersion
91 | implementation "com.facebook.react:react-native:+"
92 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
93 | implementation 'com.google.android.gms:play-services-mlkit-text-recognition:19.0.1'
94 | implementation 'com.google.android.gms:play-services-mlkit-text-recognition-chinese:16.0.1'
95 | implementation 'com.google.android.gms:play-services-mlkit-text-recognition-devanagari:16.0.1'
96 | implementation 'com.google.android.gms:play-services-mlkit-text-recognition-japanese:16.0.1'
97 | implementation 'com.google.android.gms:play-services-mlkit-text-recognition-korean:16.0.1'
98 | implementation 'com.google.mlkit:translate:17.0.3'
99 | implementation project(':react-native-vision-camera')
100 | implementation "androidx.camera:camera-core:1.3.4"
101 |
102 | // For using the models in Google Play Services
103 | implementation 'com.google.android.gms:play-services-mlkit-text-recognition:19.0.1'
104 | implementation 'com.google.android.gms:play-services-mlkit-text-recognition-chinese:16.0.1'
105 | implementation 'com.google.android.gms:play-services-mlkit-text-recognition-devanagari:16.0.1'
106 | implementation 'com.google.android.gms:play-services-mlkit-text-recognition-japanese:16.0.1'
107 | implementation 'com.google.android.gms:play-services-mlkit-text-recognition-korean:16.0.1'
108 | }
109 |
110 |
--------------------------------------------------------------------------------
/ios/VisionCameraTranslator.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import VisionCamera
3 | import MLKitVision
4 | import MLKitTextRecognition
5 | import MLKitTranslate
6 |
7 | public class VisionCameraTranslator: FrameProcessorPlugin {
8 | private var models: [String] = []
9 | private var isDownloaded: Bool = false
10 | private let localModels = ModelManager.modelManager().downloadedTranslateModels
11 | private var translator: Translator
12 | private var translatorOptions: TranslatorOptions
13 | private var text = ""
14 | private let conditions = ModelDownloadConditions(
15 | allowsCellularAccess: false,
16 | allowsBackgroundDownloading: true
17 | )
18 | private var from = ""
19 | private var to = ""
20 | private var textRecognizer = TextRecognizer.textRecognizer(options: TextRecognizerOptions())
21 |
22 | public override init(proxy: VisionCameraProxyHolder, options: [AnyHashable: Any]! = [:]) {
23 | let from = options["from"] as? String ?? "en"
24 | let to = options["to"] as? String ?? "de"
25 |
26 | self.from = from
27 | self.to = to
28 |
29 | let sourceLanguage = TranslateLanguage(from: self.from) ?? .english
30 | let targetLanguage = TranslateLanguage(from: self.to) ?? .german
31 | self.translatorOptions = TranslatorOptions(sourceLanguage: sourceLanguage, targetLanguage: targetLanguage)
32 | self.translator = Translator.translator(options: translatorOptions)
33 | super.init(proxy: proxy, options: options)
34 | downloadModel()
35 | }
36 |
37 | public override func callback(_ frame: Frame, withArguments arguments: [AnyHashable: Any]?) -> Any {
38 | let buffer = frame.buffer
39 | let image = VisionImage(buffer: buffer)
40 |
41 | let orientation = getOrientation(
42 | orientation: frame.orientation
43 | )
44 | image.orientation = orientation
45 | let resultText = recognizeText(image: image)!.text
46 | if isDownloaded && !resultText.isEmpty {
47 | self.translateText(text:resultText){translatedText in
48 | self.text = translatedText!
49 | }
50 | }
51 | if resultText.isEmpty {
52 | self.text = ""
53 | }
54 | return self.text
55 | }
56 | private func downloadModel(){
57 | localModels.forEach { language in
58 | models.append(language.language.rawValue)
59 | if models.contains(from) && models.contains(to) {
60 | self.isDownloaded = true
61 | }
62 | }
63 | if !isDownloaded {
64 | translator.downloadModelIfNeeded(with: conditions) { error in
65 | guard error == nil else {
66 | self.isDownloaded = false
67 | return
68 | }
69 | self.isDownloaded = true
70 | }
71 | }
72 | }
73 |
74 | private func recognizeText(image:VisionImage)-> Text?{
75 | do {
76 | let result = try self.textRecognizer.results(in: image)
77 | return result
78 | } catch {
79 | print("Failed to recognize text: \(error.localizedDescription).")
80 | return nil
81 | }
82 |
83 | }
84 | private func getOrientation(orientation: UIImage.Orientation) -> UIImage.Orientation {
85 | switch orientation {
86 | case .up:
87 | return .up
88 | case .left:
89 | return .right
90 | case .down:
91 | return .down
92 | case .right:
93 | return .left
94 | default:
95 | return .up
96 | }
97 | }
98 |
99 | private func translateText(text: String, completion: @escaping (String?) -> Void) {
100 | if !isDownloaded {
101 | print("Translation model is not downloaded.")
102 | completion(nil)
103 | return
104 | }
105 | translator.translate(text) { translatedText, error in
106 | guard error == nil, let translatedText = translatedText else {
107 | print("Translation failed: \(error?.localizedDescription ?? "Unknown error").")
108 | completion(nil)
109 | return
110 | }
111 | completion(translatedText)
112 | }
113 | }
114 | }
115 |
116 |
117 |
--------------------------------------------------------------------------------
/android/src/main/java/com/visioncameratextrecognition/VisionCameraTranslatorPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.visioncameratextrecognition
2 |
3 | import android.media.Image
4 | import android.util.Log
5 | import com.google.android.gms.tasks.Task
6 | import com.google.android.gms.tasks.Tasks
7 | import com.google.mlkit.common.model.DownloadConditions
8 | import com.google.mlkit.common.model.RemoteModelManager
9 | import com.google.mlkit.nl.translate.TranslateLanguage
10 | import com.google.mlkit.nl.translate.TranslateRemoteModel
11 | import com.google.mlkit.nl.translate.Translation
12 | import com.google.mlkit.nl.translate.TranslatorOptions
13 | import com.google.mlkit.vision.common.InputImage
14 | import com.google.mlkit.vision.text.Text
15 | import com.google.mlkit.vision.text.TextRecognition
16 | import com.google.mlkit.vision.text.latin.TextRecognizerOptions
17 | import com.mrousavy.camera.frameprocessors.Frame
18 | import com.mrousavy.camera.frameprocessors.FrameProcessorPlugin
19 | import com.mrousavy.camera.frameprocessors.VisionCameraProxy
20 |
21 | private const val TAG = "VisionCameraTranslator"
22 |
23 | class VisionCameraTranslatorPlugin(proxy: VisionCameraProxy, options: Map?) :
24 | FrameProcessorPlugin() {
25 |
26 | private val conditions = DownloadConditions.Builder().requireWifi().build()
27 | private var translatorOptions = TranslatorOptions.Builder()
28 | .setSourceLanguage(TranslateLanguage.ENGLISH)
29 | .setTargetLanguage(TranslateLanguage.GERMAN)
30 | .build()
31 | private val translator = Translation.getClient(translatorOptions)
32 | private var recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
33 | private var from = ""
34 | private var to = ""
35 | private var models = emptyArray()
36 | private var isDownloaded: Boolean = false
37 | private val modelManager = RemoteModelManager.getInstance()
38 | private var translatedText = ""
39 | init {
40 | this.from = options?.get("from") as? String ?: ""
41 | this.to = options?.get("to") as? String ?: ""
42 | val sourceLanguage = translateLanguage(from)
43 | val targetLanguage = translateLanguage(to)
44 | if (sourceLanguage != null && targetLanguage != null) {
45 | translatorOptions = TranslatorOptions.Builder()
46 | .setSourceLanguage(sourceLanguage)
47 | .setTargetLanguage(targetLanguage)
48 | .build()
49 | } else {
50 | println("Invalid language strings provided for translation.")
51 | }
52 | downloadModel()
53 | }
54 |
55 | override fun callback(frame: Frame, params: MutableMap?) : Any? {
56 | val mediaImage: Image = frame.image
57 | println(" OKKK ${ frame.imageProxy.imageInfo.rotationDegrees }")
58 | val image = InputImage.fromMediaImage(mediaImage, frame.imageProxy.imageInfo.rotationDegrees)
59 | val task: Task = recognizer.process(image)
60 | try {
61 | val resultText: String = Tasks.await(task).text
62 | if (this.isDownloaded && resultText.isNotEmpty()) {
63 | this.translateText(resultText)
64 | }
65 | if (resultText.isEmpty()){
66 | this.translatedText = ""
67 | }
68 | return this.translatedText
69 | }catch (e:Exception){
70 | Log.e(TAG, "$e")
71 | return null
72 | }
73 | }
74 |
75 | private fun translateText(text: String) {
76 | translator.translate(text)
77 | .addOnSuccessListener {
78 | this.translatedText = it
79 | }
80 | .addOnFailureListener {
81 | Log.e("ERROR", "$it")
82 | this.translatedText = ""
83 | }
84 | }
85 |
86 |
87 | private fun downloadModel(){
88 | modelManager.getDownloadedModels(TranslateRemoteModel::class.java)
89 | .addOnSuccessListener {
90 | it.forEach() { language ->
91 | models.plus(language.language)
92 | }
93 | if (models.contains(from) && models.contains(to)) {
94 | isDownloaded = true
95 | }
96 | }
97 | .addOnFailureListener {
98 | this.isDownloaded = false
99 | }
100 |
101 | if (!isDownloaded){
102 | translator.downloadModelIfNeeded(conditions)
103 | .addOnSuccessListener {
104 | this.isDownloaded = true
105 | }
106 | .addOnFailureListener {
107 | this.isDownloaded = false
108 | }
109 | }
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-vision-camera-text-recognition",
3 | "version": "3.1.1",
4 | "description": "A plugin to Scanning Text,Translate using ML Kit Text Recognition and ML Kit Translation. With High Performance and many features.",
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",
14 | "ios",
15 | "cpp",
16 | "*.podspec",
17 | "!ios/build",
18 | "!android/build",
19 | "!android/gradle",
20 | "!android/gradlew",
21 | "!android/gradlew.bat",
22 | "!android/local.properties",
23 | "!**/__tests__",
24 | "!**/__fixtures__",
25 | "!**/__mocks__",
26 | "!**/.*"
27 | ],
28 | "scripts": {
29 | "example": "yarn workspace react-native-vision-camera-text-recognition-example",
30 | "test": "jest",
31 | "typecheck": "tsc --noEmit",
32 | "lint": "eslint \"**/*.{js,ts,tsx}\"",
33 | "clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
34 | "prepare": "bob build",
35 | "release": "release-it",
36 | "pack-zip": "npm-pack-zip"
37 |
38 | },
39 | "keywords": [
40 | "react-native",
41 | "ios",
42 | "android",
43 | "camera",
44 | "mlkit",
45 | "ML Kit",
46 | "react-native-vision-camera",
47 | "vision-camera",
48 | "text-recognition",
49 | "text"
50 | ],
51 | "repository": {
52 | "type": "git",
53 | "url": "git+https://github.com/gev2002/react-native-vision-camera-text-recognition.git"
54 | },
55 | "author": "gev2002 (https://github.com/gev2002)",
56 | "license": "MIT",
57 | "bugs": {
58 | "url": "https://github.com/gev2002/react-native-vision-camera-text-recognition/issues"
59 | },
60 | "homepage": "https://github.com/gev2002/react-native-vision-camera-text-recognition#readme",
61 | "publishConfig": {
62 | "registry": "https://registry.npmjs.org/"
63 | },
64 | "devDependencies": {
65 | "@commitlint/config-conventional": "^17.0.2",
66 | "@evilmartians/lefthook": "^1.5.0",
67 | "@react-native/eslint-config": "^0.73.1",
68 | "@release-it/conventional-changelog": "^5.0.0",
69 | "@types/jest": "^29.5.5",
70 | "@types/react": "^18.2.44",
71 | "commitlint": "^17.0.2",
72 | "del-cli": "^5.1.0",
73 | "eslint": "^8.51.0",
74 | "eslint-config-prettier": "^9.0.0",
75 | "eslint-plugin-prettier": "^5.0.1",
76 | "jest": "^29.7.0",
77 | "prettier": "^3.0.3",
78 | "react": "18.2.0",
79 | "react-native": "0.74.1",
80 | "react-native-builder-bob": "^0.23.2",
81 | "react-native-vision-camera": "^4.5.1",
82 | "react-native-worklets-core": "^1.3.3",
83 | "release-it": "^15.0.0",
84 | "turbo": "^1.10.7",
85 | "typescript": "^5.2.2"
86 | },
87 | "resolutions": {
88 | "@types/react": "^18.2.44"
89 | },
90 | "peerDependencies": {
91 | "react": "*",
92 | "react-native": "*"
93 | },
94 | "workspaces": [
95 | "example"
96 | ],
97 | "jest": {
98 | "preset": "react-native",
99 | "modulePathIgnorePatterns": [
100 | "/example/node_modules",
101 | "/lib/"
102 | ]
103 | },
104 | "commitlint": {
105 | "extends": [
106 | "@commitlint/config-conventional"
107 | ]
108 | },
109 | "release-it": {
110 | "git": {
111 | "commitMessage": "chore: release ${version}",
112 | "tagName": "v${version}"
113 | },
114 | "npm": {
115 | "publish": true
116 | },
117 | "github": {
118 | "release": true
119 | },
120 | "plugins": {
121 | "@release-it/conventional-changelog": {
122 | "preset": "angular"
123 | }
124 | }
125 | },
126 | "eslintConfig": {
127 | "root": true,
128 | "extends": [
129 | "@react-native",
130 | "prettier"
131 | ],
132 | "rules": {
133 | "prettier/prettier": [
134 | "error",
135 | {
136 | "quoteProps": "consistent",
137 | "singleQuote": true,
138 | "tabWidth": 2,
139 | "trailingComma": "es5",
140 | "useTabs": false
141 | }
142 | ]
143 | }
144 | },
145 | "eslintIgnore": [
146 | "node_modules/",
147 | "lib/"
148 | ],
149 | "prettier": {
150 | "quoteProps": "consistent",
151 | "singleQuote": true,
152 | "tabWidth": 2,
153 | "trailingComma": "es5",
154 | "useTabs": false
155 | },
156 | "react-native-builder-bob": {
157 | "source": "src",
158 | "output": "lib",
159 | "targets": [
160 | "commonjs",
161 | "module",
162 | [
163 | "typescript",
164 | {
165 | "project": "tsconfig.build.json"
166 | }
167 | ]
168 | ]
169 | },
170 | "packageManager": "yarn@1.22.21+sha1.1959a18351b811cdeedbd484a8f86c3cc3bbaf72",
171 | "dependencies": {
172 | "npm-pack-zip": "^1.3.0"
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are always welcome, no matter how large or small!
4 |
5 | We want this community to be friendly and respectful to each other. Please follow it in all your interactions with the project. Before contributing, please read the [code of conduct](./CODE_OF_CONDUCT.md).
6 |
7 | ## Development workflow
8 |
9 | This project is a monorepo managed using [Yarn workspaces](https://yarnpkg.com/features/workspaces). It contains the following packages:
10 |
11 | - The library package in the root directory.
12 | - An example app in the `example/` directory.
13 |
14 | To get started with the project, run `yarn` in the root directory to install the required dependencies for each package:
15 |
16 | ```sh
17 | yarn
18 | ```
19 |
20 | > Since the project relies on Yarn workspaces, you cannot use [`npm`](https://github.com/npm/cli) for development.
21 |
22 | The [example app](/example/) demonstrates usage of the library. You need to run it to test any changes you make.
23 |
24 | It is configured to use the local version of the library, so any changes you make to the library's source code will be reflected in the example app. Changes to the library's JavaScript code will be reflected in the example app without a rebuild, but native code changes will require a rebuild of the example app.
25 |
26 | If you want to use Android Studio or XCode to edit the native code, you can open the `example/android` or `example/ios` directories respectively in those editors. To edit the Objective-C or Swift files, open `example/ios/VisionCameraTextRecognitionExample.xcworkspace` in XCode and find the source files at `Pods > Development Pods > react-native-vision-camera-text-recognition`.
27 |
28 | To edit the Java or Kotlin files, open `example/android` in Android studio and find the source files at `react-native-vision-camera-text-recognition` under `Android`.
29 |
30 | You can use various commands from the root directory to work with the project.
31 |
32 | To start the packager:
33 |
34 | ```sh
35 | yarn example start
36 | ```
37 |
38 | To run the example app on Android:
39 |
40 | ```sh
41 | yarn example android
42 | ```
43 |
44 | To run the example app on iOS:
45 |
46 | ```sh
47 | yarn example ios
48 | ```
49 |
50 | Make sure your code passes TypeScript and ESLint. Run the following to verify:
51 |
52 | ```sh
53 | yarn typecheck
54 | yarn lint
55 | ```
56 |
57 | To fix formatting errors, run the following:
58 |
59 | ```sh
60 | yarn lint --fix
61 | ```
62 |
63 | Remember to add tests for your change if possible. Run the unit tests by:
64 |
65 | ```sh
66 | yarn test
67 | ```
68 |
69 | ### Commit message convention
70 |
71 | We follow the [conventional commits specification](https://www.conventionalcommits.org/en) for our commit messages:
72 |
73 | - `fix`: bug fixes, e.g. fix crash due to deprecated method.
74 | - `feat`: new features, e.g. add new method to the module.
75 | - `refactor`: code refactor, e.g. migrate from class components to hooks.
76 | - `docs`: changes into documentation, e.g. add usage example for the module..
77 | - `test`: adding or updating tests, e.g. add integration tests using detox.
78 | - `chore`: tooling changes, e.g. change CI config.
79 |
80 | Our pre-commit hooks verify that your commit message matches this format when committing.
81 |
82 | ### Linting and tests
83 |
84 | [ESLint](https://eslint.org/), [Prettier](https://prettier.io/), [TypeScript](https://www.typescriptlang.org/)
85 |
86 | 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.
87 |
88 | Our pre-commit hooks verify that the linter and tests pass when committing.
89 |
90 | ### Publishing to npm
91 |
92 | 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.
93 |
94 | To publish new versions, run the following:
95 |
96 | ```sh
97 | yarn release
98 | ```
99 |
100 | ### Scripts
101 |
102 | The `package.json` file contains various scripts for common tasks:
103 |
104 | - `yarn`: setup project by installing dependencies.
105 | - `yarn typecheck`: type-check files with TypeScript.
106 | - `yarn lint`: lint files with ESLint.
107 | - `yarn test`: run unit tests with Jest.
108 | - `yarn example start`: start the Metro server for the example app.
109 | - `yarn example android`: run the example app on Android.
110 | - `yarn example ios`: run the example app on iOS.
111 |
112 | ### Sending a pull request
113 |
114 | > **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).
115 |
116 | When you're sending a pull request:
117 |
118 | - Prefer small pull requests focused on one change.
119 | - Verify that linters and tests are passing.
120 | - Review the documentation to make sure it looks good.
121 | - Follow the pull request template when opening a pull request.
122 | - For pull requests that change the API or implementation, discuss with maintainers first by opening an issue.
123 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 |
2 | # Contributor Covenant Code of Conduct
3 |
4 | ## Our Pledge
5 |
6 | We as members, contributors, and leaders pledge to make participation in our
7 | community a harassment-free experience for everyone, regardless of age, body
8 | size, visible or invisible disability, ethnicity, sex characteristics, gender
9 | identity and expression, level of experience, education, socio-economic status,
10 | nationality, personal appearance, race, caste, color, religion, or sexual
11 | identity and orientation.
12 |
13 | We pledge to act and interact in ways that contribute to an open, welcoming,
14 | diverse, inclusive, and healthy community.
15 |
16 | ## Our Standards
17 |
18 | Examples of behavior that contributes to a positive environment for our
19 | community include:
20 |
21 | * Demonstrating empathy and kindness toward other people
22 | * Being respectful of differing opinions, viewpoints, and experiences
23 | * Giving and gracefully accepting constructive feedback
24 | * Accepting responsibility and apologizing to those affected by our mistakes,
25 | and learning from the experience
26 | * Focusing on what is best not just for us as individuals, but for the overall
27 | community
28 |
29 | Examples of unacceptable behavior include:
30 |
31 | * The use of sexualized language or imagery, and sexual attention or advances of
32 | any kind
33 | * Trolling, insulting or derogatory comments, and personal or political attacks
34 | * Public or private harassment
35 | * Publishing others' private information, such as a physical or email address,
36 | without their explicit permission
37 | * Other conduct which could reasonably be considered inappropriate in a
38 | professional setting
39 |
40 | ## Enforcement Responsibilities
41 |
42 | Community leaders are responsible for clarifying and enforcing our standards of
43 | acceptable behavior and will take appropriate and fair corrective action in
44 | response to any behavior that they deem inappropriate, threatening, offensive,
45 | or harmful.
46 |
47 | Community leaders have the right and responsibility to remove, edit, or reject
48 | comments, commits, code, wiki edits, issues, and other contributions that are
49 | not aligned to this Code of Conduct, and will communicate reasons for moderation
50 | decisions when appropriate.
51 |
52 | ## Scope
53 |
54 | This Code of Conduct applies within all community spaces, and also applies when
55 | an individual is officially representing the community in public spaces.
56 | Examples of representing our community include using an official e-mail address,
57 | posting via an official social media account, or acting as an appointed
58 | representative at an online or offline event.
59 |
60 | ## Enforcement
61 |
62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
63 | reported to the community leaders responsible for enforcement at
64 | [INSERT CONTACT METHOD].
65 | All complaints will be reviewed and investigated promptly and fairly.
66 |
67 | All community leaders are obligated to respect the privacy and security of the
68 | reporter of any incident.
69 |
70 | ## Enforcement Guidelines
71 |
72 | Community leaders will follow these Community Impact Guidelines in determining
73 | the consequences for any action they deem in violation of this Code of Conduct:
74 |
75 | ### 1. Correction
76 |
77 | **Community Impact**: Use of inappropriate language or other behavior deemed
78 | unprofessional or unwelcome in the community.
79 |
80 | **Consequence**: A private, written warning from community leaders, providing
81 | clarity around the nature of the violation and an explanation of why the
82 | behavior was inappropriate. A public apology may be requested.
83 |
84 | ### 2. Warning
85 |
86 | **Community Impact**: A violation through a single incident or series of
87 | actions.
88 |
89 | **Consequence**: A warning with consequences for continued behavior. No
90 | interaction with the people involved, including unsolicited interaction with
91 | those enforcing the Code of Conduct, for a specified period of time. This
92 | includes avoiding interactions in community spaces as well as external channels
93 | like social media. Violating these terms may lead to a temporary or permanent
94 | ban.
95 |
96 | ### 3. Temporary Ban
97 |
98 | **Community Impact**: A serious violation of community standards, including
99 | sustained inappropriate behavior.
100 |
101 | **Consequence**: A temporary ban from any sort of interaction or public
102 | communication with the community for a specified period of time. No public or
103 | private interaction with the people involved, including unsolicited interaction
104 | with those enforcing the Code of Conduct, is allowed during this period.
105 | Violating these terms may lead to a permanent ban.
106 |
107 | ### 4. Permanent Ban
108 |
109 | **Community Impact**: Demonstrating a pattern of violation of community
110 | standards, including sustained inappropriate behavior, harassment of an
111 | individual, or aggression toward or disparagement of classes of individuals.
112 |
113 | **Consequence**: A permanent ban from any sort of public interaction within the
114 | community.
115 |
116 | ## Attribution
117 |
118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
119 | version 2.1, available at
120 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
121 |
122 | Community Impact Guidelines were inspired by
123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
124 |
125 | For answers to common questions about this code of conduct, see the FAQ at
126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
127 | [https://www.contributor-covenant.org/translations][translations].
128 |
129 | [homepage]: https://www.contributor-covenant.org
130 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
131 | [Mozilla CoC]: https://github.com/mozilla/diversity
132 | [FAQ]: https://www.contributor-covenant.org/faq
133 | [translations]: https://www.contributor-covenant.org/translations
134 |
--------------------------------------------------------------------------------
/android/src/main/java/com/visioncameratextrecognition/VisionCameraTextRecognitionPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.visioncameratextrecognition
2 |
3 | import android.graphics.Point
4 | import android.graphics.Rect
5 | import android.media.Image
6 | import com.facebook.react.bridge.WritableNativeArray
7 | import com.facebook.react.bridge.WritableNativeMap
8 | import com.google.android.gms.tasks.Task
9 | import com.google.android.gms.tasks.Tasks
10 | import com.google.mlkit.vision.common.InputImage
11 | import com.google.mlkit.vision.text.Text
12 | import com.google.mlkit.vision.text.TextRecognition
13 | import com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions
14 | import com.google.mlkit.vision.text.devanagari.DevanagariTextRecognizerOptions
15 | import com.google.mlkit.vision.text.japanese.JapaneseTextRecognizerOptions
16 | import com.google.mlkit.vision.text.korean.KoreanTextRecognizerOptions
17 | import com.google.mlkit.vision.text.latin.TextRecognizerOptions
18 | import com.mrousavy.camera.frameprocessors.Frame
19 | import com.mrousavy.camera.frameprocessors.FrameProcessorPlugin
20 | import com.mrousavy.camera.frameprocessors.VisionCameraProxy
21 | import java.util.HashMap
22 |
23 | class VisionCameraTextRecognitionPlugin(proxy: VisionCameraProxy, options: Map?) :
24 | FrameProcessorPlugin() {
25 |
26 | private var recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
27 | private val latinOptions = TextRecognizerOptions.DEFAULT_OPTIONS
28 | private val chineseOptions = ChineseTextRecognizerOptions.Builder().build()
29 | private val devanagariOptions = DevanagariTextRecognizerOptions.Builder().build()
30 | private val japaneseOptions = JapaneseTextRecognizerOptions.Builder().build()
31 | private val koreanOptions = KoreanTextRecognizerOptions.Builder().build()
32 |
33 | init {
34 | val language = options?.get("language").toString()
35 | recognizer = when (language) {
36 | "latin" -> TextRecognition.getClient(latinOptions)
37 | "chinese" -> TextRecognition.getClient(chineseOptions)
38 | "devanagari" -> TextRecognition.getClient(devanagariOptions)
39 | "japanese" -> TextRecognition.getClient(japaneseOptions)
40 | "korean" -> TextRecognition.getClient(koreanOptions)
41 | else -> TextRecognition.getClient(latinOptions)
42 | }
43 | }
44 |
45 | override fun callback(frame: Frame, arguments: Map?): HashMap? {
46 | val data = WritableNativeMap()
47 | val mediaImage: Image = frame.image
48 | val image =
49 | InputImage.fromMediaImage(mediaImage, frame.imageProxy.imageInfo.rotationDegrees)
50 | val task: Task = recognizer.process(image)
51 | try {
52 | val text: Text = Tasks.await(task)
53 | if (text.text.isEmpty()) {
54 | return WritableNativeMap().toHashMap()
55 | }
56 | data.putString("resultText", text.text)
57 | data.putArray("blocks", getBlocks(text.textBlocks))
58 | return data.toHashMap()
59 | } catch (e: Exception) {
60 | e.printStackTrace()
61 | return null
62 | }
63 | }
64 |
65 | companion object {
66 | fun getBlocks(blocks: MutableList): WritableNativeArray {
67 | val blockArray = WritableNativeArray()
68 | blocks.forEach { block ->
69 | val blockMap = WritableNativeMap().apply {
70 | putString("blockText", block.text)
71 | putArray("blockCornerPoints", block.cornerPoints?.let { getCornerPoints(it) })
72 | putMap("blockFrame", getFrame(block.boundingBox))
73 | putArray("lines", getLines(block.lines))
74 | }
75 | blockArray.pushMap(blockMap)
76 | }
77 | return blockArray
78 | }
79 |
80 | private fun getLines(lines: MutableList): WritableNativeArray {
81 | val lineArray = WritableNativeArray()
82 | lines.forEach { line ->
83 | val lineMap = WritableNativeMap().apply {
84 | putString("lineText", line.text)
85 | putArray("lineCornerPoints", line.cornerPoints?.let { getCornerPoints(it) })
86 | putMap("lineFrame", getFrame(line.boundingBox))
87 | putArray(
88 | "lineLanguages",
89 | WritableNativeArray().apply { pushString(line.recognizedLanguage) })
90 | putArray("elements", getElements(line.elements))
91 | }
92 | lineArray.pushMap(lineMap)
93 | }
94 | return lineArray
95 | }
96 |
97 | private fun getElements(elements: MutableList): WritableNativeArray {
98 | val elementArray = WritableNativeArray()
99 | elements.forEach { element ->
100 | val elementMap = WritableNativeMap().apply {
101 | putString("elementText", element.text)
102 | putArray(
103 | "elementCornerPoints",
104 | element.cornerPoints?.let { getCornerPoints(it) })
105 | putMap("elementFrame", getFrame(element.boundingBox))
106 | }
107 | elementArray.pushMap(elementMap)
108 | }
109 | return elementArray
110 | }
111 |
112 | private fun getCornerPoints(points: Array): WritableNativeArray {
113 | val cornerPoints = WritableNativeArray()
114 | points.forEach { point ->
115 | cornerPoints.pushMap(WritableNativeMap().apply {
116 | putInt("x", point.x)
117 | putInt("y", point.y)
118 | })
119 | }
120 | return cornerPoints
121 | }
122 |
123 | private fun getFrame(boundingBox: Rect?): WritableNativeMap {
124 | return WritableNativeMap().apply {
125 | boundingBox?.let {
126 | putDouble("x", it.exactCenterX().toDouble())
127 | putDouble("y", it.exactCenterY().toDouble())
128 | putInt("width", it.width())
129 | putInt("height", it.height())
130 | putInt("boundingCenterX", it.centerX())
131 | putInt("boundingCenterY", it.centerY())
132 | }
133 | }
134 | }
135 | }
136 | }
137 |
138 |
139 |
--------------------------------------------------------------------------------
/ios/VisionCameraTextRecognition.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import VisionCamera
3 | import MLKitVision
4 | import MLKitTextRecognition
5 | import MLKitTextRecognitionChinese
6 | import MLKitTextRecognitionDevanagari
7 | import MLKitTextRecognitionJapanese
8 | import MLKitTextRecognitionKorean
9 | import MLKitCommon
10 |
11 | @objc(VisionCameraTextRecognition)
12 | public class VisionCameraTextRecognition: FrameProcessorPlugin {
13 |
14 | private var textRecognizer = TextRecognizer()
15 | private static let latinOptions = TextRecognizerOptions()
16 | private static let chineseOptions = ChineseTextRecognizerOptions()
17 | private static let devanagariOptions = DevanagariTextRecognizerOptions()
18 | private static let japaneseOptions = JapaneseTextRecognizerOptions()
19 | private static let koreanOptions = KoreanTextRecognizerOptions()
20 | private var data: [String: Any] = [:]
21 |
22 |
23 | public override init(proxy: VisionCameraProxyHolder, options: [AnyHashable: Any]! = [:]) {
24 | super.init(proxy: proxy, options: options)
25 | let language = options["language"] as? String ?? "latin"
26 | switch language {
27 | case "chinese":
28 | self.textRecognizer = TextRecognizer.textRecognizer(options: VisionCameraTextRecognition.chineseOptions)
29 | case "devanagari":
30 | self.textRecognizer = TextRecognizer.textRecognizer(options: VisionCameraTextRecognition.devanagariOptions)
31 | case "japanese":
32 | self.textRecognizer = TextRecognizer.textRecognizer(options: VisionCameraTextRecognition.japaneseOptions)
33 | case "korean":
34 | self.textRecognizer = TextRecognizer.textRecognizer(options: VisionCameraTextRecognition.koreanOptions)
35 | default:
36 | self.textRecognizer = TextRecognizer.textRecognizer(options: VisionCameraTextRecognition.latinOptions)
37 | }
38 | }
39 |
40 |
41 | public override func callback(_ frame: Frame, withArguments arguments: [AnyHashable: Any]?) -> Any {
42 | let buffer = frame.buffer
43 | let image = VisionImage(buffer: buffer)
44 | image.orientation = getOrientation(orientation: frame.orientation)
45 |
46 | do {
47 | let result = try self.textRecognizer.results(in: image)
48 | let blocks = VisionCameraTextRecognition.processBlocks(blocks: result.blocks)
49 | data["resultText"] = result.text
50 | data["blocks"] = blocks
51 | if result.text.isEmpty {
52 | return [:]
53 | }else{
54 | return data
55 | }
56 | } catch {
57 | print("Failed to recognize text: \(error.localizedDescription).")
58 | return [:]
59 | }
60 | }
61 |
62 | static func processBlocks(blocks:[TextBlock]) -> Array {
63 | var blocksArray : [Any] = []
64 | for block in blocks {
65 | var blockData : [String:Any] = [:]
66 | blockData["blockText"] = block.text
67 | blockData["blockCornerPoints"] = processCornerPoints(block.cornerPoints)
68 | blockData["blockFrame"] = processFrame(block.frame)
69 | blockData["lines"] = processLines(lines: block.lines)
70 | blocksArray.append(blockData)
71 | }
72 | return blocksArray
73 | }
74 |
75 | private static func processLines(lines:[TextLine]) -> Array {
76 | var linesArray : [Any] = []
77 | for line in lines {
78 | var lineData : [String:Any] = [:]
79 | lineData["lineText"] = line.text
80 | lineData["lineLanguages"] = processRecognizedLanguages(line.recognizedLanguages)
81 | lineData["lineCornerPoints"] = processCornerPoints(line.cornerPoints)
82 | lineData["lineFrame"] = processFrame(line.frame)
83 | lineData["elements"] = processElements(elements: line.elements)
84 | linesArray.append(lineData)
85 | }
86 | return linesArray
87 | }
88 |
89 | private static func processElements(elements:[TextElement]) -> Array {
90 | var elementsArray : [Any] = []
91 |
92 | for element in elements {
93 | var elementData : [String:Any] = [:]
94 | elementData["elementText"] = element.text
95 | elementData["elementCornerPoints"] = processCornerPoints(element.cornerPoints)
96 | elementData["elementFrame"] = processFrame(element.frame)
97 |
98 | elementsArray.append(elementData)
99 | }
100 |
101 | return elementsArray
102 | }
103 |
104 | private static func processRecognizedLanguages(_ languages: [TextRecognizedLanguage]) -> [String] {
105 |
106 | var languageArray: [String] = []
107 |
108 | for language in languages {
109 | guard let code = language.languageCode else {
110 | print("No language code exists")
111 | break;
112 | }
113 | if code.isEmpty{
114 | languageArray.append("und")
115 | }else {
116 | languageArray.append(code)
117 |
118 | }
119 | }
120 |
121 | return languageArray
122 | }
123 |
124 | private static func processCornerPoints(_ cornerPoints: [NSValue]) -> [[String: CGFloat]] {
125 | return cornerPoints.compactMap { $0.cgPointValue }.map { ["x": $0.x, "y": $0.y] }
126 | }
127 |
128 | private static func processFrame(_ frameRect: CGRect) -> [String: CGFloat] {
129 | let offsetX = (frameRect.midX - ceil(frameRect.width)) / 2.0
130 | let offsetY = (frameRect.midY - ceil(frameRect.height)) / 2.0
131 |
132 | let x = frameRect.maxX + offsetX
133 | let y = frameRect.minY + offsetY
134 |
135 | return [
136 | "x": frameRect.midX + (frameRect.midX - x),
137 | "y": frameRect.midY + (y - frameRect.midY),
138 | "width": frameRect.width,
139 | "height": frameRect.height,
140 | "boundingCenterX": frameRect.midX,
141 | "boundingCenterY": frameRect.midY
142 | ]
143 | }
144 |
145 | private func getOrientation(orientation: UIImage.Orientation) -> UIImage.Orientation {
146 | switch orientation {
147 | case .up:
148 | return .up
149 | case .left:
150 | return .right
151 | case .down:
152 | return .down
153 | case .right:
154 | return .left
155 | default:
156 | return .up
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-native-vision-camera-text-recognition
2 |
3 | A plugin to Scanning Text,Translate using ML Kit Text Recognition and ML Kit Translation. With High Performance and many features.
4 | # 🚨 Required Modules
5 | react-native-vision-camera = 4.5.1
6 | react-native-worklets-core = 1.3.3
7 |
8 | ## 💻 Installation
9 |
10 | ```sh
11 | npm install react-native-vision-camera-text-recognition
12 | yarn add react-native-vision-camera-text-recognition
13 | ```
14 | ## 👷Features
15 | Easy To Use.
16 | Works Just Writing few lines of Code.
17 | Works With React Native Vision Camera.
18 | Works for Both Cameras.
19 | Works Fast.
20 | Works With Android 🤖 and IOS.📱
21 | Writen With Kotlin and Swift.
22 | Can Recognize Text From Photo. 📸
23 | Can translate text. 🌍
24 |
25 | ## 💡 Usage
26 | ### 📚 For Live Recognition Text
27 | ```js
28 | import React, { useState } from 'react'
29 | import { useCameraDevice } from 'react-native-vision-camera'
30 | import { Camera } from 'react-native-vision-camera-text-recognition';
31 |
32 | function App (){
33 | const [data,setData] = useState(null)
34 | const device = useCameraDevice('back');
35 | console.log(data)
36 | return(
37 | <>
38 | {!!device && (
39 | setData(d)}
48 | />
49 | )}
50 | >
51 | )
52 | }
53 |
54 | export default App;
55 |
56 |
57 |
58 | ```
59 |
60 | ### 🌍 For Translate Text
61 | ```js
62 | import React, { useState } from 'react'
63 | import { useCameraDevice } from 'react-native-vision-camera'
64 | import { Camera } from 'react-native-vision-camera-text-recognition';
65 |
66 | function App (){
67 | const [data,setData] = useState(null)
68 | const device = useCameraDevice('back');
69 | console.log(data)
70 | return(
71 | <>
72 | {!!device && (
73 | setData(d)}
83 | />
84 | )}
85 | >
86 | )
87 | }
88 |
89 | export default App;
90 |
91 | ```
92 |
93 | ### Also You Can Use Like This
94 |
95 | ```js
96 | import React from 'react';
97 | import { StyleSheet } from "react-native";
98 | import {
99 | Camera,
100 | useCameraDevice,
101 | useFrameProcessor,
102 | } from "react-native-vision-camera";
103 | import { useTextRecognition } from "react-native-vision-camera-text-recognition";
104 |
105 | function App() {
106 | const device = useCameraDevice('back');
107 | const options = { language : 'latin' }
108 | const {scanText} = useTextRecognition(options)
109 | const frameProcessor = useFrameProcessor((frame) => {
110 | 'worklet'
111 | const data = scanText(frame)
112 | console.log(data, 'data')
113 | }, [])
114 | return (
115 | <>
116 | {!!device && (
117 |
124 | )}
125 | >
126 | );
127 | }
128 | export default App;
129 |
130 |
131 | ```
132 | ---
133 | ## ⚙️ Options
134 |
135 | | Name | Type | Values | Default |
136 | |:--------:| :---: |:--------------------------------------------:|:---------:|
137 | | language | string | latin, chinese, devanagari, japanese, korean | latin |
138 | | mode | string | recognize, translate | recognize |
139 | | from,to | string | See Below | en,de |
140 |
141 |
142 | ## Recognize By Photo 📸
143 |
144 | ```js
145 | import { PhotoRecognizer } from "react-native-vision-camera-text-recognition";
146 |
147 | const result = await PhotoRecognizer({
148 | uri:assets.uri,
149 | orientation: "portrait"
150 | })
151 | console.log(result);
152 |
153 | ```
154 | 🚨 Orientation available only for iOS, recommendation give it when you are using Camera.
155 |
156 | | Name | Type | Values | Required | Default | Platform |
157 | |:-----------:|:------:|:-----------------------------------------------------------:|:--------:|:--------:|:------------:|
158 | | uri | string | | yes | | android, iOS |
159 | | orientation | string | portrait, portraitUpsideDown, landscapeLeft, landscapeRight | no | portrait | iOS |
160 |
161 |
162 |
163 |
164 | ### You can also remove unnecessary translation model
165 |
166 |
167 |
168 | ```js
169 | import { RemoveLanguageModel } from "react-native-vision-camera-text-recognition";
170 |
171 | const bool = await RemoveLanguageModel("en")
172 | ```
173 | Supported Languages.
174 |
175 | ```
176 | Afrikaans: 🇿🇦, 🇨🇫 <---> code : "af"
177 | Albanian: 🇦🇱 <---> code : "sq"
178 | Arabic: 🇦🇪, 🇸🇦 <---> code : "ar"
179 | Belarusian: 🇧🇾 <---> code : "be"
180 | Bulgarian: 🇧🇬 <---> code : "bn"
181 | Bengali: 🇧🇩 <---> code : "bg"
182 | Catalan: 🏴 <---> code : "ca"
183 | Czech: 🇨🇿 <---> code : "cs"
184 | Welsh: 🏴 <---> code : "cy"
185 | Danish: 🇩🇰 <---> code : "da"
186 | German: 🇩🇪 <---> code : "de"
187 | Greek: 🇬🇷 <---> code : "el"
188 | English: 🇬🇧, 🇺🇸 <---> code : "en"
189 | Esperanto: 🌍 <---> code : "eo"
190 | Spanish: 🇪🇸 <---> code : "es"
191 | Estonian: 🇪🇪 <---> code : "et"
192 | Persian: 🇮🇷 <---> code : "fa"
193 | Finnish: 🇫🇮 <---> code : "fi"
194 | French: 🇫🇷 <---> code : "fr"
195 | Irish: 🇮🇪 <---> code : "ga"
196 | Galician: 🏴 <---> code : "gl"
197 | Gujarati: 🏴 <---> code : "gu"
198 | Hebrew: 🇮🇱 <---> code : "he"
199 | Hindi: 🇮🇳 <---> code : "hi"
200 | Croatian: 🇭🇷 <---> code : "hr"
201 | Haitian: 🇭🇹 <---> code : "ht"
202 | Hungarian: 🇭🇺 <---> code : "hu"
203 | Indonesian: 🇮🇩 <---> code : "id"
204 | Icelandic: 🇮🇸 <---> code : "is"
205 | Italian: 🇮🇹 <---> code : "it"
206 | Japanese: 🇯🇵 <---> code : "ja"
207 | Georgian: 🇬🇪 <---> code : "ka"
208 | Kannada: 🇨🇦 <---> code : "kn"
209 | Korean: 🇰🇷, 🇰🇵 <---> code : "ko"
210 | Lithuanian: 🇱🇹 <---> code : "lt"
211 | Latvian: 🇱🇻 <---> code : "lv"
212 | Macedonian: 🇲🇰 <---> code : "mk"
213 | Marathi: 🇮🇳 <---> code : "mr"
214 | Malay: 🇲🇾 <---> code : "ms"
215 | Maltese: 🇲🇹 <---> code : "mt"
216 | Dutch: 🇳🇱 <---> code : "nl"
217 | Norwegian: 🇳🇴 <---> code : "no"
218 | Polish: 🇵🇱 <---> code : "pl"
219 | Portuguese: 🇵🇹 <---> code : "pt"
220 | Romanian: 🇷🇴 <---> code : "ro"
221 | Russian: 🇷🇺 <---> code : "ru"
222 | Slovak: 🇸🇰 <---> code : "sk"
223 | Slovenian: 🇸🇮 <---> code : "sl"
224 | Swedish: 🇸🇪 <---> code : "sv"
225 | Swahili: 🇰🇪 <---> code : "sw"
226 | Tamil: 🇱🇰 <---> code : "ta"
227 | Telugu: 🇮🇳 <---> code : "te"
228 | Thai: 🇹🇭 <---> code : "th"
229 | Tagalog: 🇵🇭 <---> code : "tl"
230 | Turkish: 🇹🇷 <---> code : "tr"
231 | Ukrainian: 🇺🇦 <---> code : "uk"
232 | Urdu: 🇵🇰 <---> code : "ur"
233 | Vietnamese: 🇻🇳 <---> code : "vi"
234 | Chinese: 🇨🇳 <---> code : "zh"
235 |
--------------------------------------------------------------------------------