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