├── .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 |
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 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/info.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------