├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── equ.traineddata │ ├── jpn.traineddata │ └── kor.traineddata │ ├── java │ └── com │ │ └── example │ │ └── simpleocr │ │ ├── Adapters │ │ ├── DiffCallback.java │ │ └── OcrListAdapter.java │ │ ├── CameraxActivity.java │ │ ├── DataBase │ │ ├── DAO.java │ │ └── Room.java │ │ ├── EditActivity.java │ │ ├── FileUtils.java │ │ ├── MainActivity.java │ │ ├── Model │ │ ├── ItemClick.java │ │ └── OcrItem.java │ │ ├── MyApp.java │ │ ├── MyBottomSheetDialog.java │ │ ├── OcrActivity.java │ │ ├── PhotoActivity.java │ │ └── Views │ │ ├── FocusView.java │ │ └── ScanView.java │ └── res │ ├── anim │ ├── anim_layout.xml │ └── item_anim.xml │ ├── drawable │ ├── baseline_add_24.xml │ ├── baseline_assignment_24.xml │ ├── baseline_clear_24.xml │ ├── baseline_copy_all_24.xml │ ├── baseline_delete_outline_24.xml │ ├── baseline_done_24.xml │ ├── baseline_done_outline_24.xml │ ├── baseline_edit_24.xml │ ├── baseline_flashlight_off_24.xml │ ├── baseline_flashlight_on_24.xml │ ├── baseline_info_24.xml │ ├── baseline_insert_photo_24.xml │ ├── baseline_menu_24.xml │ ├── baseline_photo_camera_24.xml │ ├── baseline_qr_code_scanner_24.xml │ ├── baseline_rate_review_24.xml │ ├── baseline_settings_24.xml │ ├── baseline_share_24.xml │ └── baseline_swap_horiz_24.xml │ ├── layout │ ├── about.xml │ ├── activity_camerax.xml │ ├── activity_edit.xml │ ├── activity_main.xml │ ├── activity_ocr.xml │ ├── activity_photo.xml │ └── item_card.xml │ ├── menu │ ├── done.xml │ └── info.xml │ ├── mipmap-anydpi-v26 │ └── ic_launcher.xml │ ├── mipmap-hdpi │ └── ic_launcher_foreground.png │ ├── mipmap-mdpi │ └── ic_launcher_foreground.png │ ├── mipmap-xhdpi │ └── ic_launcher_foreground.png │ ├── mipmap-xxhdpi │ └── ic_launcher_foreground.png │ ├── mipmap-xxxhdpi │ └── ic_launcher_foreground.png │ ├── values-de │ └── strings.xml │ ├── values-night │ └── themes.xml │ ├── values-zh │ └── strings.xml │ ├── values │ ├── colors.xml │ ├── ic_launcher_background.xml │ ├── strings.xml │ └── themes.xml │ └── xml │ ├── backup_rules.xml │ ├── data_extraction_rules.xml │ ├── filepaths.xml │ └── locales_config.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Project Deprecated 2 | 3 | This project is no longer maintained. 4 | 5 | Please note that there will be no further updates or bug fixes. 6 | 7 | The new project called [Open Note](https://github.com/YangDai2003/OpenNote-Compose) will serve as a replacement for this project. 8 | 9 | "Open Note" offers enhanced features and ongoing support. 10 | 11 | Thank you for your support and understanding. 12 | 13 | --- 14 | 15 |
16 | 17 | # OCR 助手 18 | # Copilot OCR 19 | 20 | ### 一款强大的文本和二维码扫描软件 21 | ### A powerful text and QR code scann App 22 | 23 |
24 | 25 | 26 | Get it on Google Play 27 | 28 | 29 |
30 | 31 |
32 | 33 | ## 📖 特点 Features 34 | 35 | * 无不必要的权限 No unnecessary permissions required, 36 | * 材料主题3 Material Design 3, 37 | * 历史记录 History, 38 | * 适配不同大小和方向的屏幕 Portrait and landscape orientation, 39 | * 多项实用功能 Multiple useful functions 40 | 41 | ## 📷 屏幕截图 Screenshots 42 | 43 | 44 | 45 | 46 | 47 | ## 🌎 语言翻译 Translations 48 | 49 | 目前支持中文,英语和德语。 50 | 51 | Currently supports Chinese, English and German. 52 | 53 | ## 应用介绍 Application introduction 54 | 55 | !!! Code may not be used for profitable commercial purposes without the author's permission. 56 | 57 | !!! 未经作者允许不得将代码用于盈利性商业用途。 58 | 59 | Features: 60 | 1) Multi-engine offline image OCR, fast and accurate. Neural Network Engine 1 supports automatic recognition of nearly 100 languages such as Chinese, English, German, Spanish, etc. If you need to recognize Japanese and Korean, please use Neural Network Engine 2 61 | 2) The history of scanned images, slide to delete 62 | 3) Edit, copy and share scanned text 63 | 4) Automatically switch light and dark modes according to device, support Chinese, English and German three application languages 64 | 5) Material Design 3 65 | 6) Has smooth and intuitive animation, adapted to new features such as predictive return gestures 66 | 7) Quickly scan all types of barcodes and QR codes 67 | 68 | 功能: 69 | 1) 多引擎离线图片OCR,快速准确。神经网络引擎1支持汉语,英语,德语,西班牙语等近100种语言自动识别,如需识别日语和韩语请使用神经网络引擎2 70 | 2) 扫描图像的历史记录,滑动即可删除 71 | 3) 编辑,复制和分享扫描的文本 72 | 4) 根据设备自动切换浅色和深色模式,支持中文,英语和德语三种应用语言 73 | 5) Material Design 3 74 | 6) 拥有流畅的且符合直觉的动画,适配预测性返回手势等新特性 75 | 7) 快速扫描所有类型的条形码和二维码 76 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | apply plugin: 'com.google.android.gms.oss-licenses-plugin' 5 | 6 | android { 7 | namespace 'com.example.simpleocr' 8 | compileSdk 34 9 | 10 | defaultConfig { 11 | applicationId "com.yangdai.simpleocr" 12 | minSdk 28 13 | targetSdk 34 14 | versionCode 16 15 | versionName '1.6' 16 | resourceConfigurations += ["en", "de", "zh"] 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled true 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_17 27 | targetCompatibility JavaVersion.VERSION_17 28 | } 29 | } 30 | 31 | dependencies { 32 | // CameraX core library using the camera2 implementation 33 | def camerax_version = '1.4.0-alpha04' 34 | // The following line is optional, as the core library is included indirectly by camera-camera2 35 | implementation "androidx.camera:camera-core:${camerax_version}" 36 | implementation "androidx.camera:camera-camera2:${camerax_version}" 37 | // If you want to additionally use the CameraX Lifecycle library 38 | implementation "androidx.camera:camera-lifecycle:${camerax_version}" 39 | // If you want to additionally use the CameraX View class 40 | implementation "androidx.camera:camera-view:${camerax_version}" 41 | 42 | implementation 'com.google.mlkit:barcode-scanning:17.2.0' 43 | implementation 'com.google.mlkit:text-recognition-chinese:16.0.0' 44 | 45 | implementation 'com.github.ybq:Android-SpinKit:1.4.0' 46 | implementation 'com.github.YangDai2003:ImageViewLib:1.0.8' 47 | implementation 'com.google.android.gms:play-services-oss-licenses:17.0.1' 48 | implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" 49 | implementation 'com.github.bumptech.glide:glide:4.16.0' 50 | annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0' 51 | 52 | def room_version = '2.6.1' 53 | implementation "androidx.room:room-runtime:$room_version" 54 | annotationProcessor "androidx.room:room-compiler:$room_version" 55 | 56 | implementation 'cz.adaptech.tesseract4android:tesseract4android-openmp:4.7.0' 57 | implementation(platform('org.jetbrains.kotlin:kotlin-bom:1.9.23')) 58 | 59 | implementation 'com.github.yalantis:ucrop:2.2.8-native' 60 | 61 | implementation 'androidx.appcompat:appcompat:1.6.1' 62 | implementation 'com.google.android.material:material:1.10.0' 63 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 64 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -dontwarn com.yalantis.ucrop** 23 | -keep class com.yalantis.ucrop** { *; } 24 | -keep interface com.yalantis.ucrop** { *; } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 36 | 43 | 49 | 56 | 57 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 74 | 77 | 78 | 79 | 84 | 85 | 88 | 91 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /app/src/main/assets/equ.traineddata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YangDai2003/CopilotOCR-Android/d048c0caf50c6ab1695c12fda13fb8e67c7b7dbf/app/src/main/assets/equ.traineddata -------------------------------------------------------------------------------- /app/src/main/assets/jpn.traineddata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YangDai2003/CopilotOCR-Android/d048c0caf50c6ab1695c12fda13fb8e67c7b7dbf/app/src/main/assets/jpn.traineddata -------------------------------------------------------------------------------- /app/src/main/assets/kor.traineddata: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YangDai2003/CopilotOCR-Android/d048c0caf50c6ab1695c12fda13fb8e67c7b7dbf/app/src/main/assets/kor.traineddata -------------------------------------------------------------------------------- /app/src/main/java/com/example/simpleocr/Adapters/DiffCallback.java: -------------------------------------------------------------------------------- 1 | package com.example.simpleocr.Adapters; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.recyclerview.widget.DiffUtil; 5 | 6 | import com.example.simpleocr.Model.OcrItem; 7 | 8 | public class DiffCallback extends DiffUtil.ItemCallback { 9 | @Override 10 | public boolean areItemsTheSame(@NonNull OcrItem oldItem, @NonNull OcrItem newItem) { 11 | return oldItem.getId() == newItem.getId(); 12 | } 13 | 14 | @Override 15 | public boolean areContentsTheSame(@NonNull OcrItem oldItem, @NonNull OcrItem newItem) { 16 | return oldItem.getText().equals(newItem.getText()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/simpleocr/Adapters/OcrListAdapter.java: -------------------------------------------------------------------------------- 1 | package com.example.simpleocr.Adapters; 2 | 3 | import android.view.LayoutInflater; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | import android.view.animation.AnimationUtils; 7 | import android.widget.TextView; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.recyclerview.widget.ListAdapter; 11 | import androidx.recyclerview.widget.RecyclerView; 12 | 13 | import com.bumptech.glide.Glide; 14 | import com.example.simpleocr.Model.ItemClick; 15 | import com.example.simpleocr.Model.OcrItem; 16 | import com.example.simpleocr.R; 17 | import com.google.android.material.imageview.ShapeableImageView; 18 | 19 | /** 20 | * @author 30415 21 | */ 22 | public class OcrListAdapter extends ListAdapter { 23 | final ItemClick itemClick; 24 | 25 | public OcrListAdapter(ItemClick itemClick) { 26 | super(new DiffCallback()); 27 | this.itemClick = itemClick; 28 | } 29 | 30 | @Override 31 | public void onViewAttachedToWindow(@NonNull ItemViewHolder holder) { 32 | super.onViewAttachedToWindow(holder); 33 | addAnimation(holder); 34 | } 35 | 36 | private void addAnimation(ItemViewHolder holder) { 37 | holder.itemView.setAnimation(AnimationUtils.loadAnimation(holder.itemView.getContext(), R.anim.item_anim)); 38 | } 39 | 40 | @NonNull 41 | @Override 42 | public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 43 | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_card, parent, false); 44 | return new ItemViewHolder(view); 45 | } 46 | 47 | @Override 48 | public void onBindViewHolder(@NonNull ItemViewHolder holder, int position) { 49 | OcrItem ocrItem = getItem(position); 50 | holder.textViewText.setText(ocrItem.getText()); 51 | holder.textViewDate.setText(ocrItem.getDate()); 52 | String image = ocrItem.getImage(); 53 | if (!image.isEmpty()) { 54 | Glide.with(holder.itemView.getContext()).load(image).sizeMultiplier(0.8f).into(holder.imageView); 55 | } 56 | holder.itemView.setOnClickListener(v -> 57 | itemClick.onClick(ocrItem, holder.getAdapterPosition(), holder.imageView)); 58 | } 59 | 60 | public static class ItemViewHolder extends RecyclerView.ViewHolder { 61 | final TextView textViewText; 62 | final TextView textViewDate; 63 | final ShapeableImageView imageView; 64 | 65 | public ItemViewHolder(@NonNull View itemView) { 66 | super(itemView); 67 | 68 | textViewText = itemView.findViewById(R.id.textview_text); 69 | textViewDate = itemView.findViewById(R.id.textview_date); 70 | imageView = itemView.findViewById(R.id.imageShow); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/simpleocr/CameraxActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.simpleocr; 2 | 3 | import static com.example.simpleocr.FileUtils.fileSaveToInside; 4 | import static com.example.simpleocr.FileUtils.toTurn; 5 | 6 | import android.annotation.SuppressLint; 7 | import android.app.Activity; 8 | import android.content.Intent; 9 | import android.graphics.Bitmap; 10 | import android.os.Build; 11 | import android.os.Bundle; 12 | import android.os.Handler; 13 | import android.os.Looper; 14 | import android.util.Log; 15 | import android.view.MotionEvent; 16 | import android.view.View; 17 | import android.view.ViewGroup; 18 | import android.widget.ImageView; 19 | 20 | import androidx.activity.EdgeToEdge; 21 | import androidx.annotation.NonNull; 22 | import androidx.annotation.RequiresApi; 23 | import androidx.appcompat.app.AppCompatActivity; 24 | import androidx.camera.core.Camera; 25 | import androidx.camera.core.CameraControl; 26 | import androidx.camera.core.CameraSelector; 27 | import androidx.camera.core.ExperimentalGetImage; 28 | import androidx.camera.core.FocusMeteringAction; 29 | import androidx.camera.core.ImageAnalysis; 30 | import androidx.camera.core.ImageProxy; 31 | import androidx.camera.core.Preview; 32 | import androidx.camera.core.TorchState; 33 | import androidx.camera.core.ZoomState; 34 | import androidx.camera.lifecycle.ProcessCameraProvider; 35 | import androidx.camera.view.PreviewView; 36 | import androidx.core.content.ContextCompat; 37 | 38 | 39 | import com.example.simpleocr.Views.FocusView; 40 | import com.google.common.util.concurrent.ListenableFuture; 41 | import com.google.mlkit.vision.barcode.BarcodeScanner; 42 | import com.google.mlkit.vision.barcode.BarcodeScannerOptions; 43 | import com.google.mlkit.vision.barcode.BarcodeScanning; 44 | import com.google.mlkit.vision.barcode.common.Barcode; 45 | import com.google.mlkit.vision.common.InputImage; 46 | 47 | import java.time.LocalDateTime; 48 | import java.time.format.DateTimeFormatter; 49 | import java.util.Objects; 50 | 51 | /** 52 | * @author 30415 53 | */ 54 | @ExperimentalGetImage 55 | public class CameraxActivity extends AppCompatActivity { 56 | private ImageView success; 57 | private ImageView imageView; 58 | private ImageAnalysis imageAnalysis; 59 | private BarcodeScanner scanner; 60 | private PreviewView viewFinder; 61 | private FocusView focusView; 62 | private Camera camera; 63 | private String savedUri; 64 | private float fingerSpacing = 0; 65 | 66 | private void setZoomRatio(float zoomRatio) { 67 | CameraControl cameraControl = camera.getCameraControl(); 68 | cameraControl.setZoomRatio(zoomRatio); 69 | } 70 | 71 | private ZoomState getZoomState() { 72 | return camera.getCameraInfo().getZoomState().getValue(); 73 | } 74 | 75 | private float getZoomRatio(float delta) { 76 | float zoomLevel = 1f; 77 | float newZoomLevel = zoomLevel + delta; 78 | if (newZoomLevel < 1f) { 79 | newZoomLevel = 1f; 80 | } else if (newZoomLevel > Objects.requireNonNull(getZoomState()).getMaxZoomRatio()) { 81 | newZoomLevel = getZoomState().getMaxZoomRatio(); 82 | } 83 | return newZoomLevel; 84 | } 85 | 86 | private float getFingerSpacing(MotionEvent event) { 87 | float x = event.getX(0) - event.getX(1); 88 | float y = event.getY(0) - event.getY(1); 89 | return (float) Math.sqrt(x * x + y * y); 90 | } 91 | 92 | 93 | @RequiresApi(api = Build.VERSION_CODES.S) 94 | @Override 95 | protected void onCreate(Bundle savedInstanceState) { 96 | EdgeToEdge.enable(this); 97 | super.onCreate(savedInstanceState); 98 | setContentView(R.layout.activity_camerax); 99 | focusView = new FocusView(this); 100 | addContentView(focusView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 101 | focusView.setVisibility(View.GONE); 102 | success = findViewById(R.id.success); 103 | imageView = findViewById(R.id.imageView); 104 | ImageView back = findViewById(R.id.imageView1); 105 | back.getBackground().setAlpha(50); 106 | imageView.setClickable(true); 107 | imageView.bringToFront(); 108 | 109 | BarcodeScannerOptions options = 110 | new BarcodeScannerOptions.Builder().build(); 111 | scanner = BarcodeScanning.getClient(options); 112 | 113 | startCamera(); 114 | } 115 | 116 | @SuppressLint({"UseCompatLoadingForDrawables", "ClickableViewAccessibility"}) 117 | private void startCamera() { 118 | // 将Camera的生命周期和Activity绑定在一起(设定生命周期所有者),这样就不用手动控制相机的启动和关闭。 119 | ListenableFuture cameraProviderFuture = ProcessCameraProvider.getInstance(this); 120 | 121 | cameraProviderFuture.addListener(() -> { 122 | try { 123 | // 将你的相机和当前生命周期的所有者绑定所需的对象 124 | ProcessCameraProvider processCameraProvider = cameraProviderFuture.get(); 125 | 126 | // 创建一个Preview 实例,并设置该实例的 surface 提供者(provider)。 127 | viewFinder = findViewById(R.id.preview); 128 | viewFinder.setScaleType(PreviewView.ScaleType.FILL_CENTER); 129 | Preview preview = new Preview.Builder().build(); 130 | preview.setSurfaceProvider(viewFinder.getSurfaceProvider()); 131 | 132 | 133 | // 选择后置摄像头作为默认摄像头 134 | CameraSelector cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA; 135 | 136 | // 设置预览帧分析 137 | imageAnalysis = new ImageAnalysis.Builder() 138 | .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) 139 | .build(); 140 | imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(this), new MyAnalyzer()); 141 | 142 | // 重新绑定用例前先解绑 143 | processCameraProvider.unbindAll(); 144 | 145 | // 绑定用例至相机 146 | camera = processCameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis); 147 | 148 | imageView.setOnClickListener(v -> { 149 | if (camera.getCameraInfo().hasFlashUnit()) { 150 | // Toggle the torch state 151 | if (null != camera.getCameraInfo().getTorchState().getValue()) { 152 | if (camera.getCameraInfo().getTorchState().getValue() == TorchState.OFF) { 153 | camera.getCameraControl().enableTorch(true); 154 | imageView.setImageDrawable(getDrawable(R.drawable.baseline_flashlight_on_24)); 155 | } else { 156 | camera.getCameraControl().enableTorch(false); 157 | imageView.setImageDrawable(getDrawable(R.drawable.baseline_flashlight_off_24)); 158 | } 159 | } 160 | } 161 | }); 162 | 163 | viewFinder.setOnTouchListener((view, event) -> { 164 | try { 165 | switch (event.getActionMasked()) { 166 | case MotionEvent.ACTION_DOWN -> { 167 | if (event.getPointerCount() == 1) { 168 | focusView.setCenter((int) event.getX(), (int) event.getY()); 169 | focusView.setVisibility(View.VISIBLE); 170 | FocusMeteringAction action = new FocusMeteringAction.Builder( 171 | viewFinder.getMeteringPointFactory() 172 | .createPoint(event.getX(), event.getY())).build(); 173 | camera.getCameraControl().startFocusAndMetering(action); 174 | new Handler(Looper.getMainLooper()).postDelayed(() -> focusView.setVisibility(View.GONE), 1000); 175 | } 176 | } 177 | // 单指点击对焦 178 | case MotionEvent.ACTION_POINTER_DOWN -> 179 | // 双指缩放 180 | fingerSpacing = getFingerSpacing(event); 181 | case MotionEvent.ACTION_MOVE -> { 182 | if (event.getPointerCount() == 2) { 183 | float newFingerSpacing = getFingerSpacing(event); 184 | if (newFingerSpacing > fingerSpacing) { 185 | setZoomRatio(getZoomRatio(2f)); 186 | } else if (newFingerSpacing < fingerSpacing) { 187 | setZoomRatio(getZoomRatio(-2f)); 188 | } 189 | fingerSpacing = newFingerSpacing; 190 | } 191 | } 192 | case MotionEvent.ACTION_POINTER_UP -> fingerSpacing = 0; 193 | default -> { 194 | } 195 | } 196 | } catch (Exception e) { 197 | Log.e("Error setting focus and exposure", ""); 198 | } 199 | return true; 200 | }); 201 | 202 | } catch (Exception ignored) { 203 | } 204 | }, ContextCompat.getMainExecutor(this)); 205 | 206 | } 207 | 208 | private class MyAnalyzer implements ImageAnalysis.Analyzer { 209 | @SuppressLint("RestrictedApi") 210 | @Override 211 | public void analyze(@NonNull ImageProxy imageProxy) { 212 | final Bitmap bitmap = imageProxy.toBitmap(); 213 | InputImage image = 214 | InputImage.fromBitmap(bitmap, imageProxy.getImageInfo().getRotationDegrees()); 215 | scanner.process(image) 216 | .addOnSuccessListener(barcodes -> { 217 | StringBuilder codeInfo = new StringBuilder(); 218 | for (Barcode barcode : barcodes) { 219 | int type = barcode.getValueType(); 220 | switch (type) { 221 | case Barcode.TYPE_WIFI -> { 222 | String ssid = Objects.requireNonNull(barcode.getWifi()).getSsid(); 223 | String password = barcode.getWifi().getPassword(); 224 | if (ssid != null && !ssid.isEmpty()) { 225 | codeInfo.append(getString(R.string.ssid)).append(" ").append(ssid).append("\n"); 226 | } 227 | if (password != null && !password.isEmpty()) { 228 | codeInfo.append(getString(R.string.password)).append(" ").append(password).append("\n"); 229 | } 230 | } 231 | case Barcode.TYPE_URL -> { 232 | String title = Objects.requireNonNull(barcode.getUrl()).getTitle(); 233 | String uri = barcode.getUrl().getUrl(); 234 | if (title != null && !title.isEmpty()) { 235 | codeInfo.append(getString(R.string.title)).append(" ").append(title).append("\n"); 236 | } 237 | if (uri != null && !uri.isEmpty()) { 238 | codeInfo.append(getString(R.string.uri)).append(" ").append(uri).append("\n"); 239 | } 240 | } 241 | case Barcode.TYPE_EMAIL -> { 242 | String address = Objects.requireNonNull(barcode.getEmail()).getAddress(); 243 | String body = barcode.getEmail().getBody(); 244 | if (address != null && !address.isEmpty()) { 245 | codeInfo.append(getString(R.string.address)).append(" ").append(address).append("\n"); 246 | } 247 | if (body != null && !body.isEmpty()) { 248 | codeInfo.append(getString(R.string.body)).append(" ").append(body).append("\n"); 249 | } 250 | } 251 | case Barcode.TYPE_PHONE -> { 252 | String number = Objects.requireNonNull(barcode.getPhone()).getNumber(); 253 | if (number != null && !number.isEmpty()) { 254 | codeInfo.append(getString(R.string.phone)).append(" ").append(number).append("\n"); 255 | } 256 | } 257 | default -> { 258 | String raw = barcode.getRawValue(); 259 | codeInfo.append(raw).append("\n"); 260 | } 261 | } 262 | 263 | } 264 | if (!codeInfo.toString().isEmpty()) { 265 | success.bringToFront(); 266 | Bitmap res = toTurn(bitmap, imageProxy.getImageInfo().getRotationDegrees()); 267 | DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"); 268 | savedUri = fileSaveToInside(CameraxActivity.this, formatter.format(LocalDateTime.now()), res); 269 | Intent intent = new Intent(); 270 | intent.putExtra("code", codeInfo.toString().trim()); 271 | intent.putExtra("uri", savedUri); 272 | setResult(Activity.RESULT_OK, intent); 273 | finish(); 274 | } 275 | }).addOnCompleteListener(c -> imageProxy.close()); 276 | } 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/simpleocr/DataBase/DAO.java: -------------------------------------------------------------------------------- 1 | package com.example.simpleocr.DataBase; 2 | 3 | 4 | import static androidx.room.OnConflictStrategy.REPLACE; 5 | 6 | import androidx.room.Dao; 7 | import androidx.room.Delete; 8 | import androidx.room.Insert; 9 | import androidx.room.Query; 10 | import androidx.room.Update; 11 | 12 | import com.example.simpleocr.Model.OcrItem; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * @author 30415 18 | */ 19 | @Dao 20 | public interface DAO { 21 | 22 | @Insert(onConflict = REPLACE) 23 | long insert(OcrItem ocrItem); 24 | 25 | @Query("SELECT * FROM items ORDER BY ID DESC") 26 | List getAll(); 27 | 28 | @Update 29 | void update(OcrItem ocrItem); 30 | 31 | @Delete 32 | void delete(OcrItem ocrItem); 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/simpleocr/DataBase/Room.java: -------------------------------------------------------------------------------- 1 | package com.example.simpleocr.DataBase; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.room.Database; 6 | import androidx.room.RoomDatabase; 7 | 8 | import com.example.simpleocr.Model.OcrItem; 9 | 10 | /** 11 | * @author 30415 12 | */ 13 | @Database(entities = OcrItem.class, version = 1, exportSchema = false) 14 | public abstract class Room extends RoomDatabase { 15 | private static Room database; 16 | private static final String DATABASE_NAME = "OcrApp"; 17 | 18 | public synchronized static Room getInstance(Context context) { 19 | if (database == null) { 20 | database = androidx.room.Room.databaseBuilder(context.getApplicationContext(), 21 | Room.class, DATABASE_NAME) 22 | .allowMainThreadQueries() 23 | .fallbackToDestructiveMigration() 24 | .build(); 25 | } 26 | return database; 27 | } 28 | 29 | public abstract DAO dao(); 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/simpleocr/EditActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.simpleocr; 2 | 3 | import androidx.activity.OnBackPressedCallback; 4 | import androidx.appcompat.app.AppCompatActivity; 5 | 6 | import android.app.Activity; 7 | import android.content.Intent; 8 | import android.os.Bundle; 9 | import android.view.Menu; 10 | import android.view.MenuItem; 11 | 12 | import com.google.android.material.elevation.SurfaceColors; 13 | import com.google.android.material.textfield.TextInputEditText; 14 | 15 | import java.util.Objects; 16 | 17 | /** 18 | * @author 30415 19 | */ 20 | public class EditActivity extends AppCompatActivity { 21 | 22 | TextInputEditText textInputEditText; 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | getWindow().setStatusBarColor(SurfaceColors.SURFACE_2.getColor(this)); 28 | Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(false); 29 | setContentView(R.layout.activity_edit); 30 | 31 | OnBackPressedCallback callback = new OnBackPressedCallback(true) { 32 | @Override 33 | public void handleOnBackPressed() { 34 | 35 | } 36 | }; 37 | getOnBackPressedDispatcher().addCallback(this, callback); 38 | 39 | textInputEditText = findViewById(R.id.editText); 40 | 41 | if (null != getIntent().getStringExtra("text")) { 42 | textInputEditText.setText(getIntent().getStringExtra("text")); 43 | } 44 | } 45 | 46 | @Override 47 | public boolean onCreateOptionsMenu(Menu menu) { 48 | getMenuInflater().inflate(R.menu.done, menu); 49 | return super.onCreateOptionsMenu(menu); 50 | } 51 | 52 | @Override 53 | public boolean onOptionsItemSelected(MenuItem item) { 54 | if (item.getItemId() == R.id.done) { 55 | CharSequence text = textInputEditText.getText(); 56 | Intent intent = new Intent(); 57 | intent.putExtra("text", text); 58 | setResult(Activity.RESULT_OK, intent); 59 | finish(); 60 | return true; 61 | } 62 | return super.onOptionsItemSelected(item); 63 | } 64 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/simpleocr/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.example.simpleocr; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.pm.PackageManager; 6 | import android.content.res.AssetManager; 7 | import android.graphics.Bitmap; 8 | import android.graphics.Matrix; 9 | import android.os.Build; 10 | 11 | import androidx.annotation.NonNull; 12 | import androidx.core.app.ActivityCompat; 13 | import androidx.core.content.ContextCompat; 14 | import androidx.exifinterface.media.ExifInterface; 15 | 16 | import java.io.File; 17 | import java.io.FileOutputStream; 18 | import java.io.IOException; 19 | import java.io.InputStream; 20 | import java.io.OutputStream; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import java.util.Objects; 24 | 25 | /** 26 | * @author 30415 27 | * @noinspection ResultOfMethodCallIgnored 28 | */ 29 | public class FileUtils { 30 | public static void deleteSingleFile(String filePath) { 31 | File file = new File(filePath); 32 | // 如果文件路径所对应的文件存在,并且是一个文件,则直接删除 33 | if (file.exists() && file.isFile()) { 34 | file.delete(); 35 | } 36 | } 37 | 38 | public static String fileSaveToInside(Context context, String fileName, Bitmap bitmap) { 39 | FileOutputStream fos = null; 40 | String path = null; 41 | try { 42 | File folder = context.getFilesDir(); 43 | //判断目录是否存在 44 | //目录不存在时自动创建 45 | if (folder.exists() || folder.mkdir()) { 46 | File file = new File(folder, fileName); 47 | fos = new FileOutputStream(file); 48 | //写入文件 49 | bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos); 50 | fos.flush(); 51 | path = file.getAbsolutePath(); 52 | } 53 | } catch (Exception e) { 54 | e.printStackTrace(); 55 | } finally { 56 | try { 57 | if (fos != null) { 58 | //关闭流 59 | fos.close(); 60 | } 61 | } catch (IOException e) { 62 | e.printStackTrace(); 63 | } 64 | } 65 | //返回路径 66 | return path; 67 | } 68 | 69 | public static int readPictureDegree(String path) { 70 | int degree = 0; 71 | try { 72 | ExifInterface exifInterface = new ExifInterface(path); 73 | int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); 74 | switch (orientation) { 75 | case ExifInterface.ORIENTATION_ROTATE_90 -> degree = 90; 76 | case ExifInterface.ORIENTATION_ROTATE_180 -> degree = 180; 77 | case ExifInterface.ORIENTATION_ROTATE_270 -> degree = 270; 78 | default -> { 79 | } 80 | } 81 | } catch (IOException e) { 82 | e.printStackTrace(); 83 | } 84 | return degree; 85 | } 86 | 87 | public static Bitmap toTurn(Bitmap img, int deg) { 88 | Matrix matrix = new Matrix(); 89 | matrix.postRotate(deg); 90 | int width = img.getWidth(); 91 | int height = img.getHeight(); 92 | return Bitmap.createBitmap(img, 0, 0, width, height, matrix, true); 93 | } 94 | 95 | public static void deleteRecursive(File fileOrDirectory) { 96 | if (fileOrDirectory.exists() && fileOrDirectory.isDirectory()) { 97 | for (File child : Objects.requireNonNull(fileOrDirectory.listFiles())) { 98 | deleteRecursive(child); 99 | } 100 | } 101 | fileOrDirectory.delete(); 102 | } 103 | 104 | public static void deleteLangFile(String filePath, File parentFile) { 105 | File file = new File(parentFile, filePath); 106 | // 如果文件路径所对应的文件存在,并且是一个文件,则直接删除 107 | if (file.exists() && file.isFile()) { 108 | file.delete(); 109 | } 110 | } 111 | 112 | public static void copyFile(@NonNull AssetManager am, @NonNull String assetName, @NonNull File outFile) { 113 | try ( 114 | InputStream in = am.open(assetName); 115 | OutputStream out = new FileOutputStream(outFile) 116 | ) { 117 | byte[] buffer = new byte[1024]; 118 | int read; 119 | while ((read = in.read(buffer)) != -1) { 120 | out.write(buffer, 0, read); 121 | } 122 | } catch (IOException e) { 123 | e.printStackTrace(); 124 | } 125 | } 126 | 127 | public static List checkPermissions(Activity activity) { 128 | String[] permissions; 129 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 130 | if (Build.VERSION.SDK_INT >= 34) { 131 | permissions = new String[]{ 132 | android.Manifest.permission.READ_MEDIA_IMAGES, 133 | android.Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED, 134 | android.Manifest.permission.CAMERA 135 | }; 136 | } else { 137 | permissions = new String[]{ 138 | android.Manifest.permission.READ_MEDIA_IMAGES, 139 | android.Manifest.permission.CAMERA 140 | }; 141 | } 142 | } else { 143 | permissions = new String[]{ 144 | android.Manifest.permission.WRITE_EXTERNAL_STORAGE, 145 | android.Manifest.permission.READ_EXTERNAL_STORAGE, 146 | android.Manifest.permission.CAMERA 147 | }; 148 | } 149 | 150 | List permissionsToRequest = new ArrayList<>(); 151 | for (String permission : permissions) { 152 | if (ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) { 153 | permissionsToRequest.add(permission); 154 | } 155 | } 156 | return permissionsToRequest; 157 | } 158 | 159 | public static void checkAndRequestPermissions(Activity activity) { 160 | List permissionsToRequest = checkPermissions(activity); 161 | if (!permissionsToRequest.isEmpty()) { 162 | ActivityCompat.requestPermissions(activity, permissionsToRequest.toArray(new String[0]), 1024); 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/simpleocr/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.simpleocr; 2 | 3 | import static com.example.simpleocr.FileUtils.copyFile; 4 | import static com.example.simpleocr.FileUtils.deleteLangFile; 5 | import static com.example.simpleocr.FileUtils.deleteRecursive; 6 | import static com.example.simpleocr.FileUtils.deleteSingleFile; 7 | 8 | import androidx.activity.EdgeToEdge; 9 | import androidx.activity.result.ActivityResultLauncher; 10 | import androidx.activity.result.contract.ActivityResultContracts; 11 | import androidx.annotation.NonNull; 12 | import androidx.appcompat.app.AppCompatActivity; 13 | import androidx.camera.core.ExperimentalGetImage; 14 | import androidx.core.app.ActivityCompat; 15 | import androidx.core.app.ActivityOptionsCompat; 16 | import androidx.core.content.ContextCompat; 17 | import androidx.recyclerview.widget.GridLayoutManager; 18 | import androidx.recyclerview.widget.ItemTouchHelper; 19 | import androidx.recyclerview.widget.LinearLayoutManager; 20 | import androidx.recyclerview.widget.RecyclerView; 21 | import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; 22 | 23 | import android.Manifest; 24 | import android.animation.ObjectAnimator; 25 | import android.app.Activity; 26 | 27 | import android.content.Intent; 28 | import android.content.pm.PackageManager; 29 | import android.content.res.AssetManager; 30 | import android.content.res.Configuration; 31 | import android.os.Build; 32 | import android.os.Bundle; 33 | import android.os.Handler; 34 | import android.os.Looper; 35 | import android.view.Menu; 36 | import android.view.MenuItem; 37 | import android.view.View; 38 | import android.widget.Toast; 39 | 40 | import com.example.simpleocr.Adapters.OcrListAdapter; 41 | import com.example.simpleocr.DataBase.Room; 42 | import com.example.simpleocr.Model.ItemClick; 43 | import com.example.simpleocr.Model.OcrItem; 44 | import com.github.ybq.android.spinkit.SpinKitView; 45 | import com.google.android.material.appbar.AppBarLayout; 46 | import com.google.android.material.appbar.MaterialToolbar; 47 | import com.google.android.material.dialog.MaterialAlertDialogBuilder; 48 | import com.google.android.material.floatingactionbutton.FloatingActionButton; 49 | import com.google.android.material.shape.MaterialShapeDrawable; 50 | 51 | import java.io.File; 52 | import java.util.ArrayList; 53 | import java.util.List; 54 | 55 | /** 56 | * @author 30415 57 | */ 58 | @ExperimentalGetImage 59 | public class MainActivity extends AppCompatActivity { 60 | AppBarLayout appBarLayout; 61 | MaterialToolbar materialToolbar; 62 | RecyclerView recyclerView; 63 | FloatingActionButton button, openCamera, openAlbum, openScan; 64 | SwipeRefreshLayout refresh; 65 | Room room; 66 | OcrListAdapter ocrListAdapter; 67 | private List itemList = new ArrayList<>(); 68 | private int mPosition; 69 | ActivityResultLauncher intentActivityResultLauncher1, intentActivityResultLauncher2; 70 | File parentFile; 71 | private static final String LANG = "jpn+kor+equ"; 72 | private int engineNum = 0; 73 | 74 | 75 | private void initUi() { 76 | appBarLayout = findViewById(R.id.appBar); 77 | appBarLayout.setStatusBarForeground(MaterialShapeDrawable.createWithElevationOverlay(this)); 78 | materialToolbar = findViewById(R.id.materialToolbar); 79 | setSupportActionBar(materialToolbar); 80 | materialToolbar.setTitleCentered(true); 81 | materialToolbar.setNavigationIcon(R.drawable.baseline_menu_24); 82 | 83 | recyclerView = findViewById(R.id.recycler_view); 84 | button = findViewById(R.id.fab_add_btn); 85 | openCamera = findViewById(R.id.camera_btn); 86 | openAlbum = findViewById(R.id.album_btn); 87 | openScan = findViewById(R.id.scan_btn); 88 | refresh = findViewById(R.id.refresh); 89 | 90 | openCamera.setOnClickListener(v -> { 91 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { 92 | requestPermissions(new String[]{Manifest.permission.CAMERA}, 1); 93 | } else { 94 | Intent intent = new Intent(this, OcrActivity.class); 95 | intent.putExtra("launch", "camera"); 96 | intent.putExtra("langs", LANG); 97 | intent.putExtra("engine", engineNum); 98 | intentActivityResultLauncher1.launch(intent); 99 | } 100 | }); 101 | openAlbum.setOnClickListener(v -> { 102 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { 103 | // 不需要解释为何需要该权限,直接请求授权 104 | ActivityCompat.requestPermissions(this, 105 | new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1); 106 | } else { 107 | Intent intent = new Intent(this, OcrActivity.class); 108 | intent.putExtra("launch", "album"); 109 | intent.putExtra("langs", LANG); 110 | intent.putExtra("engine", engineNum); 111 | intentActivityResultLauncher1.launch(intent); 112 | } 113 | }); 114 | openScan.setOnClickListener(v -> { 115 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { 116 | requestPermissions(new String[]{Manifest.permission.CAMERA}, 1); 117 | } else { 118 | Intent intent = new Intent(this, OcrActivity.class); 119 | intent.putExtra("launch", "scancode"); 120 | intentActivityResultLauncher1.launch(intent); 121 | } 122 | }); 123 | button.setOnClickListener(view -> { 124 | if (openCamera.getVisibility() == View.GONE) { 125 | showOptions(); 126 | } else { 127 | hideOptions(); 128 | } 129 | }); 130 | refresh.setOnRefreshListener(this::onRefresh); 131 | refresh.setProgressViewEndTarget(true, (int) (getResources().getDisplayMetrics().density * 100)); 132 | refresh.setDistanceToTriggerSync((int) (getResources().getDisplayMetrics().density * 200)); 133 | recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { 134 | @Override 135 | public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { 136 | super.onScrollStateChanged(recyclerView, newState); 137 | if (openCamera.getVisibility() != View.GONE) { 138 | hideOptions(); 139 | } 140 | } 141 | }); 142 | } 143 | 144 | private void showOptions() { 145 | openCamera.setVisibility(View.VISIBLE); 146 | openAlbum.setVisibility(View.VISIBLE); 147 | openScan.setVisibility(View.VISIBLE); 148 | animateViewTranslationY(openCamera, -getResources().getDisplayMetrics().density * 150); 149 | animateViewTranslationY(openAlbum, -getResources().getDisplayMetrics().density * 75); 150 | animateViewTranslationX(openScan, -getResources().getDisplayMetrics().density * 75); 151 | button.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.baseline_clear_24)); 152 | } 153 | 154 | private void hideOptions() { 155 | animateViewTranslationY(openCamera, 0f); 156 | animateViewTranslationY(openAlbum, 0f); 157 | animateViewTranslationX(openScan, 0f); 158 | new Handler(Looper.getMainLooper()).postDelayed(() -> { 159 | openCamera.setVisibility(View.GONE); 160 | openAlbum.setVisibility(View.GONE); 161 | openScan.setVisibility(View.GONE); 162 | }, 200); 163 | button.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.baseline_add_24)); 164 | } 165 | 166 | private void animateViewTranslationY(View view, float translationY) { 167 | ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationY", translationY); 168 | animator.setDuration(200); 169 | animator.start(); 170 | } 171 | 172 | private void animateViewTranslationX(View view, float translationX) { 173 | ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationX", translationX); 174 | animator.setDuration(200); 175 | animator.start(); 176 | } 177 | 178 | @Override 179 | protected void onCreate(Bundle savedInstanceState) { 180 | EdgeToEdge.enable(this); 181 | super.onCreate(savedInstanceState); 182 | setContentView(R.layout.activity_main); 183 | 184 | FileUtils.checkAndRequestPermissions(this); 185 | 186 | initUi(); 187 | initRecyclerView(); 188 | 189 | itemTouchHelper.attachToRecyclerView(recyclerView); 190 | room = Room.getInstance(this); 191 | itemList = room.dao().getAll(); 192 | updateData(itemList); 193 | 194 | intentActivityResultLauncher1 = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), res -> { 195 | if (res.getResultCode() == Activity.RESULT_OK) { 196 | OcrItem newItem = null; 197 | if (res.getData() != null) { 198 | newItem = (OcrItem) res.getData().getSerializableExtra("ocr_item"); 199 | } 200 | long id = room.dao().insert(newItem); 201 | if (newItem != null) { 202 | newItem.setId(id); 203 | } 204 | itemList.add(0, newItem); 205 | ocrListAdapter.notifyItemInserted(0); 206 | } 207 | }); 208 | 209 | intentActivityResultLauncher2 = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), res -> { 210 | if (res.getResultCode() == Activity.RESULT_OK) { 211 | OcrItem newItem = null; 212 | if (res.getData() != null) { 213 | newItem = (OcrItem) res.getData().getSerializableExtra("ocr_item"); 214 | } 215 | if (newItem != null) { 216 | room.dao().update(newItem); 217 | } 218 | itemList.clear(); 219 | itemList.addAll(room.dao().getAll()); 220 | ocrListAdapter.notifyItemChanged(mPosition); 221 | } 222 | }); 223 | 224 | getSupportFragmentManager().setFragmentResultListener("requestKey", this, (requestKey, bundle) -> { 225 | if ("requestKey".equals(requestKey)) { 226 | if (bundle.getBoolean("clear", false)) { 227 | SpinKitView process = findViewById(R.id.spin_kit); 228 | process.setVisibility(View.VISIBLE); 229 | room.clearAllTables(); 230 | File dir = getFilesDir(); 231 | deleteRecursive(dir); 232 | new Handler(Looper.getMainLooper()).postDelayed(() -> { 233 | itemList = room.dao().getAll(); 234 | updateData(itemList); 235 | process.setVisibility(View.GONE); 236 | }, 2000); 237 | } 238 | } 239 | }); 240 | 241 | String mDataPath = getFilesDir().getAbsolutePath(); 242 | 243 | parentFile = new File(mDataPath, "tessdata"); 244 | if (!parentFile.exists()) { // 确保路径存在 245 | parentFile.mkdir(); 246 | } 247 | copyFiles(); // 复制字库到手机 248 | 249 | String[] deleteFilePaths = new String[]{"chi_all.traineddata", "eng.traineddata", "deu.traineddata", "osd.traineddata"}; 250 | try { 251 | for (String path : deleteFilePaths) { 252 | deleteLangFile(path, parentFile); 253 | } 254 | } catch (Exception ignored) { 255 | } 256 | } 257 | 258 | private void copyFiles() { 259 | AssetManager am = getAssets(); 260 | String[] dataFilePaths = new String[]{"jpn.traineddata", "kor.traineddata", "equ.traineddata"}; // 拷字库过去 261 | for (String dataFilePath : dataFilePaths) { 262 | File engFile = new File(parentFile, dataFilePath); 263 | if (!engFile.exists()) { 264 | copyFile(am, dataFilePath, engFile); 265 | } 266 | } 267 | } 268 | 269 | public void onRefresh() {//刷新 270 | new Handler(Looper.getMainLooper()).postDelayed(() -> { 271 | itemList.clear(); 272 | itemList.addAll(room.dao().getAll()); 273 | updateData(itemList); 274 | refresh.setRefreshing(false);//刷新旋转动画停止 275 | }, 1000); 276 | } 277 | 278 | private void updateData(List ocrItemList) { 279 | ocrListAdapter.submitList(ocrItemList); 280 | } 281 | 282 | private void initRecyclerView(){ 283 | recyclerView.setHasFixedSize(true); 284 | if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { 285 | LinearLayoutManager layoutManager = new LinearLayoutManager(this); 286 | recyclerView.setLayoutManager(layoutManager); 287 | } else { 288 | GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 2); 289 | recyclerView.setLayoutManager(gridLayoutManager); 290 | } 291 | ocrListAdapter = new OcrListAdapter(itemClick); 292 | recyclerView.setAdapter(ocrListAdapter); 293 | } 294 | 295 | final ItemClick itemClick = new ItemClick() { 296 | @Override 297 | public void onClick(OcrItem ocrItem, int position, View imageview) { 298 | mPosition = position; 299 | Intent intent = new Intent(MainActivity.this, OcrActivity.class); 300 | intent.putExtra("old_ocr", ocrItem); 301 | ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(MainActivity.this, imageview, "testImg"); 302 | intentActivityResultLauncher2.launch(intent, optionsCompat); 303 | } 304 | }; 305 | 306 | final ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new ItemTouchHelper.Callback() { 307 | @Override 308 | public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { 309 | int swiped = ItemTouchHelper.RIGHT | ItemTouchHelper.LEFT; 310 | //第一个参数拖动,第二个删除侧滑 311 | return makeMovementFlags(0, swiped); 312 | } 313 | 314 | @Override 315 | public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) { 316 | return false; 317 | } 318 | 319 | @Override 320 | public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { 321 | int position = viewHolder.getAdapterPosition(); 322 | new MaterialAlertDialogBuilder(MainActivity.this) 323 | .setTitle(getString(R.string.delete))//标题 324 | .setMessage(getString(R.string.sure))//内容 325 | .setIcon(R.mipmap.ic_launcher)//图标 326 | .setCancelable(false) 327 | .setNegativeButton(getString(R.string.cancel), (dialogInterface, i) -> ocrListAdapter.notifyItemChanged(position)) 328 | .setPositiveButton(getString(R.string.delete), (dialog, which) -> { 329 | room.dao().delete(itemList.get(position)); 330 | String path = itemList.get(position).getImage(); 331 | deleteSingleFile(path); 332 | itemList.remove(position); 333 | ocrListAdapter.notifyItemRemoved(position); 334 | }).show(); 335 | } 336 | }); 337 | 338 | @Override 339 | public boolean onCreateOptionsMenu(Menu menu) { 340 | getMenuInflater().inflate(R.menu.info, menu); 341 | return super.onCreateOptionsMenu(menu); 342 | } 343 | 344 | @Override 345 | public boolean onOptionsItemSelected(MenuItem item) { 346 | if (item.getItemId() == android.R.id.home) { 347 | // 创建Material3 Bottom Sheet 348 | MyBottomSheetDialog bottomSheetDialog = new MyBottomSheetDialog(); 349 | // 显示Bottom Sheet 350 | bottomSheetDialog.show(getSupportFragmentManager(), "bottom_sheet_tag"); 351 | return true; 352 | } else if (item.getItemId() == R.id.choose) { 353 | final String[] items = {getString(R.string.engineOptions1), getString(R.string.engineOptions2)}; 354 | new MaterialAlertDialogBuilder(this) 355 | .setTitle(getString(R.string.engine)) 356 | .setIcon(R.drawable.baseline_settings_24) 357 | .setCancelable(false) 358 | //0 google 1 tess 359 | .setSingleChoiceItems(items, engineNum, (dialog1, which) -> { 360 | if (which == 0) { 361 | engineNum = 0; 362 | } else { 363 | engineNum = 1; 364 | } 365 | }) 366 | .setPositiveButton(getString(R.string.confirm), null) 367 | .show(); 368 | return true; 369 | } 370 | return super.onOptionsItemSelected(item); 371 | } 372 | 373 | @Override 374 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 375 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 376 | if (requestCode == 1024) { 377 | for (int i = 0; i < permissions.length; i++) { 378 | if (grantResults[i] != PackageManager.PERMISSION_GRANTED) { 379 | Toast.makeText(this, getString(R.string.ask), Toast.LENGTH_LONG).show(); 380 | break; 381 | } 382 | } 383 | } 384 | } 385 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/simpleocr/Model/ItemClick.java: -------------------------------------------------------------------------------- 1 | package com.example.simpleocr.Model; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * @author 30415 7 | */ 8 | public interface ItemClick { 9 | void onClick(OcrItem ocrItem, int position, View imageView); 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/simpleocr/Model/OcrItem.java: -------------------------------------------------------------------------------- 1 | package com.example.simpleocr.Model; 2 | 3 | import androidx.room.ColumnInfo; 4 | import androidx.room.Entity; 5 | import androidx.room.PrimaryKey; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * @author 30415 11 | */ 12 | @Entity(tableName = "items") 13 | public class OcrItem implements Serializable { 14 | @PrimaryKey(autoGenerate = true) 15 | long id = 0; 16 | 17 | @ColumnInfo(name = "text") 18 | String text = ""; 19 | 20 | @ColumnInfo(name = "date") 21 | String date = ""; 22 | 23 | @ColumnInfo(name = "image") 24 | String image = ""; 25 | 26 | public String getImage() { 27 | return image; 28 | } 29 | 30 | public void setImage(String image) { 31 | this.image = image; 32 | } 33 | 34 | public long getId() { 35 | return id; 36 | } 37 | 38 | public void setId(long id) { 39 | this.id = id; 40 | } 41 | 42 | 43 | public String getText() { 44 | return text; 45 | } 46 | 47 | public void setText(String text) { 48 | this.text = text; 49 | } 50 | 51 | public String getDate() { 52 | return date; 53 | } 54 | 55 | public void setDate(String date) { 56 | this.date = date; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/simpleocr/MyApp.java: -------------------------------------------------------------------------------- 1 | package com.example.simpleocr; 2 | 3 | import android.app.Application; 4 | 5 | import com.google.android.material.color.DynamicColors; 6 | 7 | public class MyApp extends Application { 8 | @Override 9 | public void onCreate() { 10 | super.onCreate(); 11 | DynamicColors.applyToActivitiesIfAvailable(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/simpleocr/MyBottomSheetDialog.java: -------------------------------------------------------------------------------- 1 | package com.example.simpleocr; 2 | 3 | import android.app.Dialog; 4 | import android.content.Intent; 5 | import android.net.Uri; 6 | import android.os.Bundle; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.FrameLayout; 11 | 12 | import androidx.annotation.NonNull; 13 | import androidx.annotation.Nullable; 14 | import androidx.fragment.app.FragmentManager; 15 | 16 | import com.google.android.gms.oss.licenses.OssLicensesMenuActivity; 17 | import com.google.android.material.bottomsheet.BottomSheetBehavior; 18 | import com.google.android.material.bottomsheet.BottomSheetDialog; 19 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment; 20 | import com.google.android.material.button.MaterialButton; 21 | import com.google.android.material.dialog.MaterialAlertDialogBuilder; 22 | 23 | /** 24 | * @author 30415 25 | */ 26 | public class MyBottomSheetDialog extends BottomSheetDialogFragment { 27 | @NonNull 28 | @Override 29 | public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { 30 | BottomSheetDialog dialog = (BottomSheetDialog) super.onCreateDialog(savedInstanceState); 31 | dialog.setContentView(R.layout.about); 32 | 33 | BottomSheetBehavior behavior = dialog.getBehavior(); 34 | behavior.setState(BottomSheetBehavior.STATE_EXPANDED); 35 | behavior.setSkipCollapsed(true); 36 | 37 | return dialog; 38 | } 39 | 40 | @Override 41 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { 42 | super.onViewCreated(view, savedInstanceState); 43 | //初始化控件 44 | MaterialButton info = view.findViewById(R.id.about); 45 | MaterialButton source = view.findViewById(R.id.source); 46 | MaterialButton deleteAll = view.findViewById(R.id.deleteAll); 47 | MaterialButton share = view.findViewById(R.id.share); 48 | MaterialButton rate = view.findViewById(R.id.rate); 49 | 50 | info.setOnClickListener(v -> { 51 | new MaterialAlertDialogBuilder(requireContext()) 52 | .setTitle(R.string.appInfo) 53 | .setMessage(R.string.content) 54 | .setPositiveButton(R.string.confirm, null) 55 | .show(); 56 | dismiss(); 57 | }); 58 | rate.setOnClickListener(v -> { 59 | Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=com.yangdai.simpleocr")); 60 | startActivity(intent); 61 | dismiss(); 62 | }); 63 | share.setOnClickListener(v -> { 64 | Intent sendIntent = new Intent(Intent.ACTION_SEND); 65 | sendIntent.setType("text/plain"); 66 | sendIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.shareContent)); 67 | startActivity(Intent.createChooser(sendIntent, "")); 68 | dismiss(); 69 | }); 70 | source.setOnClickListener(v -> { 71 | OssLicensesMenuActivity.setActivityTitle(getString(R.string.source)); 72 | startActivity(new Intent(getActivity(), OssLicensesMenuActivity.class)); 73 | dismiss(); 74 | }); 75 | deleteAll.setOnClickListener(v -> { 76 | FragmentManager fragmentManager = getParentFragmentManager(); 77 | new MaterialAlertDialogBuilder(requireContext()) 78 | .setTitle(getString(R.string.deleteAll)) 79 | .setPositiveButton(getString(R.string.confirm), 80 | (dialog, which) -> { 81 | Bundle result = new Bundle(); 82 | result.putBoolean("clear", true); 83 | fragmentManager.setFragmentResult("requestKey", result); 84 | }) 85 | .setNegativeButton(getString(R.string.cancel), null) 86 | .setCancelable(false) 87 | .show(); 88 | dismiss(); 89 | }); 90 | } 91 | 92 | @Nullable 93 | @Override 94 | public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 95 | return inflater.inflate(R.layout.about, container, false); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/simpleocr/OcrActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.simpleocr; 2 | 3 | import static com.example.simpleocr.FileUtils.fileSaveToInside; 4 | import static com.example.simpleocr.FileUtils.readPictureDegree; 5 | import static com.example.simpleocr.FileUtils.toTurn; 6 | 7 | import androidx.activity.OnBackPressedCallback; 8 | import androidx.activity.result.ActivityResultLauncher; 9 | import androidx.activity.result.contract.ActivityResultContracts; 10 | import androidx.appcompat.app.AppCompatActivity; 11 | import androidx.camera.core.ExperimentalGetImage; 12 | import androidx.core.app.ActivityOptionsCompat; 13 | 14 | import android.annotation.SuppressLint; 15 | import android.app.Activity; 16 | import android.app.ActivityOptions; 17 | import android.content.ClipData; 18 | import android.content.ClipboardManager; 19 | import android.content.ContentValues; 20 | import android.content.Context; 21 | import android.content.Intent; 22 | import android.graphics.Bitmap; 23 | import android.graphics.BitmapFactory; 24 | import android.net.Uri; 25 | import android.os.Build; 26 | import android.os.Bundle; 27 | import android.provider.MediaStore; 28 | import android.view.MenuItem; 29 | import android.view.View; 30 | import android.widget.ImageButton; 31 | import android.widget.Toast; 32 | 33 | import com.bumptech.glide.Glide; 34 | import com.example.simpleocr.Model.OcrItem; 35 | import com.github.ybq.android.spinkit.SpinKitView; 36 | import com.google.android.material.elevation.SurfaceColors; 37 | import com.google.android.material.imageview.ShapeableImageView; 38 | import com.google.android.material.textview.MaterialTextView; 39 | import com.google.mlkit.vision.common.InputImage; 40 | import com.google.mlkit.vision.text.Text; 41 | import com.google.mlkit.vision.text.TextRecognition; 42 | import com.google.mlkit.vision.text.TextRecognizer; 43 | import com.google.mlkit.vision.text.chinese.ChineseTextRecognizerOptions; 44 | import com.googlecode.tesseract.android.TessBaseAPI; 45 | import com.yalantis.ucrop.UCrop; 46 | 47 | import java.io.File; 48 | import java.time.LocalDateTime; 49 | import java.time.format.DateTimeFormatter; 50 | import java.util.Objects; 51 | import java.util.concurrent.ExecutorService; 52 | import java.util.concurrent.Executors; 53 | 54 | /** 55 | * @author 30415 56 | */ 57 | @ExperimentalGetImage 58 | public class OcrActivity extends AppCompatActivity { 59 | SpinKitView process; 60 | ImageButton copy, edit, share; 61 | ShapeableImageView imageButton; 62 | MaterialTextView textView; 63 | private Bitmap bitmap; 64 | ActivityResultLauncher galleryActivityResultLauncher, cameraActivityResultLauncher, 65 | editActivityResultLauncher, cameraxActivityResultLauncher; 66 | Uri sourceUri; 67 | TessBaseAPI mTess; 68 | OcrItem ocrItem; 69 | private boolean oldItem; 70 | String imageUri = "", dateStr = ""; 71 | private boolean changed = false; 72 | TextRecognizer textRecognizer; 73 | private int engineNum; 74 | 75 | @Override 76 | public void finish() { 77 | String text = Objects.requireNonNull(textView.getText()).toString(); 78 | if (!text.isEmpty() || !imageUri.isEmpty()) { 79 | if (changed) { 80 | ocrItem.setText(text); 81 | ocrItem.setImage(imageUri); 82 | Intent intent = new Intent(); 83 | intent.putExtra("ocr_item", ocrItem); 84 | setResult(Activity.RESULT_OK, intent); 85 | } 86 | } 87 | if (!oldItem) { 88 | if (engineNum == 1) { 89 | mTess.recycle(); 90 | } else if (engineNum == 0) { 91 | textRecognizer.close(); 92 | } 93 | } 94 | super.finish(); 95 | } 96 | 97 | private void initUi() { 98 | process = findViewById(R.id.spin_kit); 99 | share = findViewById(R.id.buttonShare); 100 | edit = findViewById(R.id.buttonEdit); 101 | imageButton = findViewById(R.id.imageButton); 102 | copy = findViewById(R.id.buttonCopy); 103 | textView = findViewById(R.id.textView); 104 | 105 | copy.setOnClickListener(view -> { 106 | if (!Objects.requireNonNull(textView.getText()).toString().isEmpty()) { 107 | ClipData clipData = ClipData.newPlainText("", textView.getText()); 108 | ((ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(clipData); 109 | Toast.makeText(this, getString(R.string.copied), Toast.LENGTH_SHORT).show(); 110 | } 111 | }); 112 | edit.setOnClickListener(v -> { 113 | if (!textView.getText().toString().isEmpty()) { 114 | Intent intent = new Intent(this, EditActivity.class); 115 | intent.putExtra("text", textView.getText().toString()); 116 | editActivityResultLauncher.launch(intent, ActivityOptionsCompat.makeSceneTransitionAnimation(this, textView, "text")); 117 | } 118 | }); 119 | share.setOnClickListener(v -> { 120 | if (!textView.getText().toString().isEmpty()) { 121 | try { 122 | CharSequence res = textView.getText(); 123 | Intent sendIntent = new Intent(Intent.ACTION_SEND); 124 | sendIntent.setType("text/plain"); 125 | sendIntent.putExtra(Intent.EXTRA_TEXT, res.toString()); 126 | startActivity(Intent.createChooser(sendIntent, getString(R.string.share))); 127 | } catch (Exception e) { 128 | Toast.makeText(this, "???", Toast.LENGTH_SHORT).show(); 129 | } 130 | } 131 | }); 132 | } 133 | 134 | @Override 135 | public boolean onOptionsItemSelected(MenuItem item) { 136 | if (item.getItemId() == android.R.id.home) { 137 | if (changed) { 138 | finish(); 139 | } else { 140 | finishAfterTransition(); 141 | } 142 | return true; 143 | } 144 | return super.onOptionsItemSelected(item); 145 | } 146 | 147 | @SuppressLint("QueryPermissionsNeeded") 148 | @Override 149 | protected void onCreate(Bundle savedInstanceState) { 150 | super.onCreate(savedInstanceState); 151 | getWindow().setStatusBarColor(SurfaceColors.SURFACE_2.getColor(this)); 152 | setContentView(R.layout.activity_ocr); 153 | Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true); 154 | initUi(); 155 | 156 | OnBackPressedCallback callback = new OnBackPressedCallback(true) { 157 | @Override 158 | public void handleOnBackPressed() { 159 | if (changed) { 160 | finish(); 161 | } else { 162 | finishAfterTransition(); 163 | } 164 | } 165 | }; 166 | getOnBackPressedDispatcher().addCallback(this, callback); 167 | 168 | initGalleryActivityResultLauncher(); 169 | initCameraActivityResultLauncher(); 170 | initEditActivityResultLauncher(); 171 | initCameraxActivityResultLauncher(); 172 | 173 | ocrItem = new OcrItem(); 174 | oldItem = true; 175 | if (null != getIntent().getExtras() && getIntent().getExtras().containsKey("old_ocr")) { 176 | ocrItem = (OcrItem) getIntent().getExtras().getSerializable("old_ocr"); 177 | if (ocrItem != null) { 178 | textView.setText(ocrItem.getText()); 179 | imageUri = ocrItem.getImage(); 180 | dateStr = ocrItem.getDate(); 181 | } 182 | bitmap = BitmapFactory.decodeFile(imageUri); 183 | imageButton.setVisibility(View.VISIBLE); 184 | imageButton.setImageBitmap(bitmap); 185 | imageButton.setOnClickListener(v -> { 186 | Intent intent = new Intent(this, PhotoActivity.class); 187 | intent.putExtra("uri", imageUri); 188 | startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this, imageButton, "testImg").toBundle()); 189 | }); 190 | } else { 191 | engineNum = getIntent().getIntExtra("engine", -1); 192 | if (engineNum == 1) { 193 | String lang = getIntent().getStringExtra("langs"); 194 | mTess = new TessBaseAPI(); 195 | try { 196 | mTess.init(getFilesDir().getAbsolutePath(), lang, TessBaseAPI.OEM_LSTM_ONLY); 197 | } catch (IllegalArgumentException ignored) { 198 | } 199 | } else if (engineNum == 0) { 200 | textRecognizer = TextRecognition.getClient(new ChineseTextRecognizerOptions.Builder().build()); 201 | } 202 | oldItem = false; 203 | DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); 204 | dateStr = formatter.format(LocalDateTime.now()); 205 | ocrItem.setDate(dateStr); 206 | changed = true; 207 | } 208 | Objects.requireNonNull(getSupportActionBar()).setTitle(dateStr); 209 | if (getIntent().getStringExtra("launch") != null && "camera".equals(getIntent().getStringExtra("launch"))) { 210 | Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); 211 | intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 212 | if (intent.resolveActivity(getPackageManager()) != null) { 213 | ContentValues values = new ContentValues(); 214 | values.put(MediaStore.Images.Media.TITLE, getString(R.string.photo)); 215 | values.put(MediaStore.Images.Media.DESCRIPTION, getString(R.string.camera)); 216 | sourceUri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); 217 | intent.putExtra(MediaStore.EXTRA_OUTPUT, sourceUri); 218 | cameraActivityResultLauncher.launch(intent); 219 | } 220 | } else if (getIntent().getStringExtra("launch") != null && "album".equals(getIntent().getStringExtra("launch"))) { 221 | Intent intent; 222 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 223 | intent = new Intent(MediaStore.ACTION_PICK_IMAGES); 224 | intent.setType("image/*"); 225 | galleryActivityResultLauncher.launch(intent); 226 | } else { 227 | intent = new Intent(Intent.ACTION_PICK, null); 228 | intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*"); 229 | galleryActivityResultLauncher.launch(intent); 230 | } 231 | } else if (getIntent().getStringExtra("launch") != null && "scancode".equals(getIntent().getStringExtra("launch"))) { 232 | Intent intent = new Intent(this, CameraxActivity.class); 233 | cameraxActivityResultLauncher.launch(intent); 234 | } 235 | } 236 | 237 | private void initGalleryActivityResultLauncher() { 238 | galleryActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { 239 | if (result.getData() != null && result.getResultCode() == Activity.RESULT_OK) { 240 | sourceUri = result.getData().getData(); 241 | startUcrop(sourceUri); 242 | } 243 | }); 244 | } 245 | 246 | private void initCameraActivityResultLauncher() { 247 | cameraActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { 248 | if (result.getResultCode() == Activity.RESULT_OK) { 249 | startUcrop(sourceUri); 250 | } 251 | }); 252 | } 253 | 254 | private void initCameraxActivityResultLauncher() { 255 | cameraxActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { 256 | if (result.getResultCode() == Activity.RESULT_OK) { 257 | if (result.getData() != null) { 258 | textView.setText(result.getData().getStringExtra("code")); 259 | imageButton.setVisibility(View.VISIBLE); 260 | imageUri = result.getData().getStringExtra("uri"); 261 | bitmap = BitmapFactory.decodeFile(imageUri); 262 | imageButton.setImageBitmap(bitmap); 263 | imageButton.setOnClickListener(v -> { 264 | Intent intent = new Intent(this, PhotoActivity.class); 265 | intent.putExtra("uri", imageUri); 266 | startActivity(intent, ActivityOptionsCompat.makeSceneTransitionAnimation(this, imageButton, "testImg").toBundle()); 267 | }); 268 | } 269 | } 270 | }); 271 | } 272 | 273 | private void initEditActivityResultLauncher() { 274 | editActivityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { 275 | if (result.getData() != null && result.getResultCode() == Activity.RESULT_OK) { 276 | textView.setText(result.getData().getCharSequenceExtra("text")); 277 | changed = true; 278 | } 279 | }); 280 | } 281 | 282 | public void startUcrop(Uri sourceUri) { 283 | UCrop.Options options = new UCrop.Options(); 284 | options.setToolbarWidgetColor(getColor(android.R.color.tab_indicator_text)); 285 | options.setStatusBarColor(SurfaceColors.SURFACE_2.getColor(this)); 286 | options.setToolbarColor(SurfaceColors.SURFACE_2.getColor(this)); 287 | options.setFreeStyleCropEnabled(true); 288 | File newFile = new File(getApplicationContext().getCacheDir(), "temp.jpeg"); 289 | UCrop.of(sourceUri, Uri.fromFile(newFile)) 290 | .withOptions(options) 291 | .start(this, UCrop.REQUEST_CROP); 292 | } 293 | 294 | @Override 295 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 296 | super.onActivityResult(requestCode, resultCode, data); 297 | if (resultCode == RESULT_OK && requestCode == UCrop.REQUEST_CROP) { 298 | final Uri resultUri = UCrop.getOutput(data); 299 | if (resultUri != null) { 300 | imageButton.setVisibility(View.VISIBLE); 301 | bitmap = toTurn(BitmapFactory.decodeFile(resultUri.getPath()), readPictureDegree(resultUri.getPath())); 302 | DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"); 303 | imageUri = fileSaveToInside(this, formatter.format(LocalDateTime.now()), bitmap); 304 | Glide.with(this).load(imageUri).into(imageButton); 305 | imageButton.setOnClickListener(v -> { 306 | Intent intent = new Intent(this, PhotoActivity.class); 307 | intent.putExtra("uri", imageUri); 308 | startActivity(intent, ActivityOptionsCompat.makeSceneTransitionAnimation(this, imageButton, "testImg").toBundle()); 309 | }); 310 | process.setVisibility(View.VISIBLE); 311 | getText(bitmap); 312 | } 313 | } 314 | } 315 | 316 | private void getText(Bitmap bitmap) { 317 | textView.setText(""); 318 | textView.setHint(R.string.processing___); 319 | 320 | if (engineNum == 1) { 321 | ExecutorService executor = Executors.newSingleThreadExecutor(); 322 | executor.execute(() -> { 323 | mTess.setImage(bitmap); 324 | //mTess.setPageSegMode(TessBaseAPI.PageSegMode.PSM_SPARSE_TEXT_OSD); 325 | String text = mTess.getUTF8Text(); 326 | runOnUiThread(() -> { 327 | process.setVisibility(View.GONE); 328 | if (text.isEmpty()) { 329 | textView.setText(getString(R.string.nothing)); 330 | } else { 331 | textView.setText(text); 332 | } 333 | }); 334 | }); 335 | executor.shutdown(); 336 | } else if (engineNum == 0) { 337 | InputImage image = InputImage.fromBitmap(bitmap, 0); 338 | textRecognizer.process(image) 339 | .addOnSuccessListener(visionText -> { 340 | process.setVisibility(View.GONE); 341 | StringBuilder stringBuilder = new StringBuilder(); 342 | for (Text.TextBlock textBlock : visionText.getTextBlocks()) { 343 | for (Text.Line textLines : textBlock.getLines()) { 344 | stringBuilder.append(textLines.getText()).append(" "); 345 | } 346 | stringBuilder.append("\n"); 347 | } 348 | if (stringBuilder.toString().isEmpty()) { 349 | textView.setText(getString(R.string.nothing)); 350 | } else { 351 | textView.setText(stringBuilder.toString()); 352 | } 353 | }).addOnFailureListener(f -> textView.setText(getString(R.string.nothing))); 354 | } 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/simpleocr/PhotoActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.simpleocr; 2 | 3 | import androidx.activity.EdgeToEdge; 4 | import androidx.appcompat.app.AppCompatActivity; 5 | 6 | import android.os.Bundle; 7 | 8 | import com.bumptech.glide.Glide; 9 | import com.yangdai.imageviewpro.ImageViewPro; 10 | 11 | /** 12 | * @author 30415 13 | */ 14 | public class PhotoActivity extends AppCompatActivity { 15 | ImageViewPro photoView; 16 | 17 | @Override 18 | protected void onCreate(Bundle savedInstanceState) { 19 | EdgeToEdge.enable(this); 20 | super.onCreate(savedInstanceState); 21 | setContentView(R.layout.activity_photo); 22 | 23 | photoView = findViewById(R.id.photoView); 24 | Glide.with(this).load(getIntent().getStringExtra("uri")).into(photoView); 25 | photoView.setOnClickListener(v -> this.finishAfterTransition()); 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/simpleocr/Views/FocusView.java: -------------------------------------------------------------------------------- 1 | package com.example.simpleocr.Views; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.util.AttributeSet; 8 | import android.view.View; 9 | 10 | import androidx.annotation.NonNull; 11 | 12 | /** 13 | * @author 30415 14 | */ 15 | public class FocusView extends View { 16 | private Paint paint; 17 | private int x, y; 18 | 19 | public FocusView(Context context) { 20 | super(context); 21 | init(); 22 | } 23 | 24 | public FocusView(Context context, AttributeSet attrs) { 25 | super(context, attrs); 26 | init(); 27 | } 28 | 29 | public FocusView(Context context, AttributeSet attrs, int defStyleAttr) { 30 | super(context, attrs, defStyleAttr); 31 | init(); 32 | } 33 | 34 | private void init() { 35 | paint = new Paint(); 36 | paint.setColor(Color.YELLOW); 37 | paint.setStyle(Paint.Style.STROKE); 38 | paint.setStrokeWidth(5); 39 | paint.setAntiAlias(true); 40 | } 41 | 42 | public void setCenter(int x, int y) { 43 | this.x = x; 44 | this.y = y; 45 | invalidate(); 46 | } 47 | 48 | @Override 49 | protected void onDraw(@NonNull Canvas canvas) { 50 | super.onDraw(canvas); 51 | int radius = 100; 52 | canvas.drawCircle(x, y, radius, paint); 53 | } 54 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/simpleocr/Views/ScanView.java: -------------------------------------------------------------------------------- 1 | package com.example.simpleocr.Views; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.graphics.Canvas; 6 | import android.graphics.Color; 7 | import android.graphics.Paint; 8 | import android.graphics.Path; 9 | import android.graphics.RectF; 10 | import android.util.AttributeSet; 11 | import android.view.View; 12 | 13 | import androidx.annotation.NonNull; 14 | 15 | /** 16 | * @author 30415 17 | */ 18 | public class ScanView extends View { 19 | 20 | private Paint mLinePaint; 21 | private RectF mFrameRect; 22 | 23 | public ScanView(Context context) { 24 | super(context); 25 | init(); 26 | } 27 | 28 | public ScanView(Context context, AttributeSet attrs) { 29 | super(context, attrs); 30 | init(); 31 | } 32 | 33 | public ScanView(Context context, AttributeSet attrs, int defStyleAttr) { 34 | super(context, attrs, defStyleAttr); 35 | init(); 36 | } 37 | 38 | private void init() { 39 | mLinePaint = new Paint(); 40 | mLinePaint.setColor(Color.WHITE); 41 | mLinePaint.setStrokeWidth(16); 42 | mLinePaint.setStyle(Paint.Style.STROKE); 43 | mLinePaint.setAntiAlias(true); 44 | 45 | float mFrameWidth = getResources().getDisplayMetrics().widthPixels / 1.5f; 46 | float mFrameHeight = getResources().getDisplayMetrics().widthPixels / 1.5f; 47 | float left = (getResources().getDisplayMetrics().widthPixels - mFrameWidth) / 2; 48 | float top = (getResources().getDisplayMetrics().heightPixels - mFrameHeight) / 2 - dpToPx(); 49 | mFrameRect = new RectF(left, top, left + mFrameWidth, top + mFrameHeight); 50 | } 51 | 52 | @Override 53 | protected void onDraw(@NonNull Canvas canvas) { 54 | super.onDraw(canvas); 55 | 56 | float frameLeft = mFrameRect.left; 57 | float frameTop = mFrameRect.top; 58 | float frameRight = mFrameRect.right; 59 | float frameBottom = mFrameRect.bottom; 60 | 61 | float cornerSize = 60f; 62 | 63 | // 左上角 64 | @SuppressLint("DrawAllocation") Path path = new Path(); 65 | path.moveTo(frameLeft, frameTop + cornerSize); 66 | path.lineTo(frameLeft, frameTop); 67 | path.lineTo(frameLeft + cornerSize, frameTop); 68 | canvas.drawPath(path, mLinePaint); 69 | 70 | // 右上角 71 | path.reset(); 72 | path.moveTo(frameRight - cornerSize, frameTop); 73 | path.lineTo(frameRight, frameTop); 74 | path.lineTo(frameRight, frameTop + cornerSize); 75 | canvas.drawPath(path, mLinePaint); 76 | 77 | // 右下角 78 | path.reset(); 79 | path.moveTo(frameRight, frameBottom - cornerSize); 80 | path.lineTo(frameRight, frameBottom); 81 | path.lineTo(frameRight - cornerSize, frameBottom); 82 | canvas.drawPath(path, mLinePaint); 83 | 84 | // 左下角 85 | path.reset(); 86 | path.moveTo(frameLeft + cornerSize, frameBottom); 87 | path.lineTo(frameLeft, frameBottom); 88 | path.lineTo(frameLeft, frameBottom - cornerSize); 89 | canvas.drawPath(path, mLinePaint); 90 | } 91 | 92 | private int dpToPx() { 93 | float density = getResources().getDisplayMetrics().density; 94 | return Math.round(100 * density); 95 | } 96 | } -------------------------------------------------------------------------------- /app/src/main/res/anim/anim_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/anim/item_anim.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_add_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_assignment_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_clear_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_copy_all_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_delete_outline_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_done_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_done_outline_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_edit_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_flashlight_off_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_flashlight_on_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_info_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_insert_photo_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_menu_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_photo_camera_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_qr_code_scanner_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_rate_review_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_settings_24.xml: -------------------------------------------------------------------------------- 1 | 5 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_share_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_swap_horiz_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/about.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 17 | 18 | 19 | 22 | 23 | 33 | 34 | 38 | 39 | 49 | 50 | 60 | 61 | 71 | 72 | 76 | 77 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_camerax.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 | 22 | 26 | 27 | 28 | 32 | 33 | 44 | 45 | 56 | 57 | 63 | 64 | 70 | 71 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_edit.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | 17 | 21 | 22 | 23 | 28 | 29 | 34 | 35 | 36 | 46 | 47 | 58 | 59 | 70 | 71 | 82 | 83 | 91 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_ocr.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 28 | 29 | 30 | 35 | 36 | 37 | 38 | 39 | 48 | 49 | 56 | 57 | 65 | 66 | 74 | 75 | 76 | 95 | 96 | 107 | 108 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_photo.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_card.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 15 | 16 | 27 | 28 | 42 | 43 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /app/src/main/res/menu/done.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/menu/info.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YangDai2003/CopilotOCR-Android/d048c0caf50c6ab1695c12fda13fb8e67c7b7dbf/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YangDai2003/CopilotOCR-Android/d048c0caf50c6ab1695c12fda13fb8e67c7b7dbf/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YangDai2003/CopilotOCR-Android/d048c0caf50c6ab1695c12fda13fb8e67c7b7dbf/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YangDai2003/CopilotOCR-Android/d048c0caf50c6ab1695c12fda13fb8e67c7b7dbf/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YangDai2003/CopilotOCR-Android/d048c0caf50c6ab1695c12fda13fb8e67c7b7dbf/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/values-de/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Foto 4 | Kopieren 5 | Text: 6 | Kamera 7 | Album 8 | Abbrechen 9 | Kopilot OCR 10 | Kopiert 11 | Behandeln… 12 | Festlegen 13 | Verwahren 14 | Entfernen 15 | Möchten Sie es wirklich löschen? 16 | Redaktion 17 | Open Source Lizenzen 18 | Funktionen:\n 1) Offline-Bild-OCR mit mehreren Engines, schnell und genau. Neural Network Engine 1 unterstützt die automatische Erkennung von fast 100 Sprachen wie Chinesisch, Englisch, Deutsch, Spanisch usw. Wenn Sie Japanisch und Koreanisch erkennen müssen, verwenden Sie bitte Neural Network Engine 2\n 2) Der Verlauf gescannter Bilder wird gespeichert und kann mit einem Wisch gelöscht werden\n 3 ) Gescannten Text bearbeiten, kopieren und teilen\n 4) Automatische Umschaltung zwischen Hell- und Dunkelmodus je nach Gerät, Unterstützung von Chinesisch, Englisch und Deutsch in drei Anwendungssprachen\n 5) Material Design 3, mit reibungslos und konform Intuitive Animationen, und angepasst an neue Funktionen wie vorausschauende Rückkehrgesten\n 6) Die App kann auch schnell alle Arten von Barcodes und QR-Codes scannen 19 | Teilen 20 | Diese App teilen 21 | Bitte erlauben Sie zuerst die Zugriffsberechtigung! 22 | Laden Sie jetzt den Kopilot OCR von Google Play herunter:\nhttps://play.google.com/store/apps/details?id=com.yangdai.simpleocr 23 | Diese App bewerten 24 | Wählen Sie eine Erkennungs-Engine 25 | Neural Netzwerk Engine 1 (Empfohlen) 26 | Neural Netzwerk Engine 2 27 | Scannen 28 | Kopilot erkennt keine Wörter oder Symbole. 29 | SSID: 30 | Passwort: 31 | Titel: 32 | Uri: 33 | E-Mail Adresse: 34 | E-Mail Text: 35 | Handy Nummer: 36 | Anwendungseinführung 37 | Alle Datensätze löschen 38 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 照片 4 | 复制 5 | 扫描的文本: 6 | 相机 7 | 相册 8 | 取消 9 | OCR 助手 10 | 已复制 11 | 处理中… 12 | 确定 13 | 保存 14 | 删除 15 | 确定要删除吗? 16 | 编辑 17 | 开源许可证 18 | 功能:\n 1) 多引擎离线图片OCR,快速准确。神经网络引擎1支持汉语,英语,德语,西班牙语等近100种语言自动识别,如需识别日语和韩语请使用神经网络引擎2\n 2) 扫描图像的历史记录,滑动可删除\n 3) 编辑,复制和分享扫描的文本\n 4) 根据设备自动切换浅色和深色模式,支持中文,英语和德语三种应用语言\n 5) Material Design3 视觉风格,拥有流畅的且符合直觉的动画,适配了预测性返回手势等新特性\n 6) 快速扫描所有类型的条形码和二维码 19 | 分享 20 | 分享此应用 21 | 请先允许访问许可! 22 | 快从谷歌商店下载OCR助手吧:\nhttps://play.google.com/store/apps/details?id=com.yangdai.simpleocr 23 | 给应用打分 24 | 选择识别引擎 25 | 神经网络引擎 1 (推荐) 26 | 神经网络引擎 2 27 | 扫描 28 | 助手什么也没有识别到。 29 | 服务集标识: 30 | 密钥: 31 | 标题: 32 | 网址链接: 33 | 邮箱地址: 34 | 电子邮件正文: 35 | 电话号码: 36 | 应用介绍 37 | 清除全部记录 38 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #6750A4 5 | #FFFFFF 6 | #E9DDFF 7 | #22005D 8 | #625B71 9 | #FFFFFF 10 | #E8DEF8 11 | #1E192B 12 | #7E5260 13 | #FFFFFF 14 | #FFD9E3 15 | #31101D 16 | #BA1A1A 17 | #FFDAD6 18 | #FFFFFF 19 | #410002 20 | #FFFBFF 21 | #1C1B1E 22 | #FFFBFF 23 | #1C1B1E 24 | #E7E0EB 25 | #49454E 26 | #7A757F 27 | #F4EFF4 28 | #313033 29 | #CFBCFF 30 | #CFBCFF 31 | #381E72 32 | #4F378A 33 | #E9DDFF 34 | #CBC2DB 35 | #332D41 36 | #4A4458 37 | #E8DEF8 38 | #EFB8C8 39 | #4A2532 40 | #633B48 41 | #FFD9E3 42 | #FFB4AB 43 | #93000A 44 | #690005 45 | #FFDAD6 46 | #1C1B1E 47 | #E6E1E6 48 | #1C1B1E 49 | #E6E1E6 50 | #49454E 51 | #CAC4CF 52 | #948F99 53 | #1C1B1E 54 | #E6E1E6 55 | #6750A4 56 | -------------------------------------------------------------------------------- /app/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Copilot OCR 3 | Photo 4 | Copy 5 | Text: 6 | Camera 7 | Album 8 | Cancel 9 | Copied 10 | Processing… 11 | Confirm 12 | Save 13 | Delete 14 | Are you sure you want to delete? 15 | Edit 16 | Open Source Licenses 17 | Features:\n 1) Multi-engine offline image OCR, fast and accurate. Neural Network Engine 1 supports automatic recognition of nearly 100 languages such as Chinese, English, German, Spanish, etc. If you need to recognize Japanese and Korean, please use Neural Network Engine 2\n 2) The history of scanned images, slide to delete\n 3) Edit, copy and share scanned text\n 4) Automatically switch light and dark modes according to device, support Chinese, English and German three application languages\n 5) Material Design 3, with smooth and intuitive animation, and adapted to new features such as predictive return gestures\n 6) Quickly scan all types of barcodes and QR codes 18 | Share 19 | Share this App 20 | Please allow access permission first! 21 | Get Copilot OCR from Google Play now:\nhttps://play.google.com/store/apps/details?id=com.yangdai.simpleocr 22 | Rate this App 23 | Choose a recognition engine 24 | Neural Network Engine 1 (Recommended) 25 | Neural Network Engine 2 26 | Scan 27 | No words or symbols are recognized by copilot. 28 | SSID: 29 | Password: 30 | Title: 31 | Uri: 32 | Email address: 33 | Email body: 34 | Phone number: 35 | Application introduction 36 | Clear all scan history 37 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 44 | 45 | 49 | 54 | 58 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/xml/filepaths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/xml/locales_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | dependencies { 4 | classpath 'com.google.android.gms:oss-licenses-plugin:0.10.6' 5 | } 6 | } 7 | plugins { 8 | id 'com.android.application' version '8.3.0' apply false 9 | id 'com.android.library' version '8.3.0' apply false 10 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Enables namespacing of each library's R class so that its R class includes only the 19 | # resources declared in the library itself and none from the library's dependencies, 20 | # thereby reducing the size of the R class for that library 21 | android.nonTransitiveRClass=true 22 | android.enableJetifier=true 23 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/YangDai2003/CopilotOCR-Android/d048c0caf50c6ab1695c12fda13fb8e67c7b7dbf/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Feb 15 20:05:45 CET 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | maven { url "https://jitpack.io" } 14 | } 15 | } 16 | rootProject.name = "Simple OCR" 17 | include ':app' 18 | --------------------------------------------------------------------------------