diff --git a/README.md b/README.md index 5b8f5ec..4b27b02 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,23 @@ # optictext -A new Flutter project. +Ein Tool, das dem Benutzer die Option gibt, OCR auf Bildern anzuwenden oder Text in Bildern zu übersetzen und den übersetzten Text wieder auf das Bild zu setzen. + +Screen1: Wahl zwischen Image tools und Audio Tools(falls ich in der Zukunft weiter daran Arbeiten werde). + + Image tools screen: DoOCR und ImageTranslate. + DoOCR: User kann ein Bild hochladen und OCR darauf anwenden. + ImageTranslate: User kann ein Bild hochladen welches dann übersetzt wird. + + +Den user die Sprache wählen zu lassen und dann in eine gewählte sprache zu übersetzen ist leicht gemacht, +allerding ist es mir wichtig, das die spracherkennung auch automatisch funktioniert. +Im ersten schritt war es wichtig diese funktionalität zu haben. +Aktuell läuft das auf einem vps, dafür ist der http request da. +Wenn möglich implementiere ich die langauge classfication in dart. + +aktuell teste ich auf einem android phone, es soll aber am ende auch auf IOS laufen. + + ## Getting Started -This project is a starting point for a Flutter application. - -A few resources to get you started if this is your first Flutter project: - -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) - -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. diff --git a/android/build.gradle b/android/build.gradle index f7eb7f6..7761169 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.7.10' + ext.kotlin_version = '1.8.0' repositories { google() mavenCentral() diff --git a/lib/OCRPage.dart b/lib/OCRPage.dart deleted file mode 100644 index b9e79c1..0000000 --- a/lib/OCRPage.dart +++ /dev/null @@ -1,90 +0,0 @@ -import 'dart:convert'; -import 'dart:typed_data'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:http/http.dart' as http; -import 'dart:io'; -import 'package:flutter_tesseract_ocr/flutter_tesseract_ocr.dart'; -import 'package:http_parser/http_parser.dart'; -import 'package:path_provider/path_provider.dart'; - -class OCRPage extends StatefulWidget { - const OCRPage({super.key}); - - @override - State createState() => _OCRPageState(); -} - -class _OCRPageState extends State { - String _extractedText = ''; - - Future uploadImage() async { - var postUri = Uri.parse("http://130.61.88.150/upload"); - http.MultipartRequest request = http.MultipartRequest("POST", postUri); - String imageName = "lorem.png"; - ByteData bytes = await rootBundle.load('assets/$imageName'); - Uint8List imageBytes = bytes.buffer.asUint8List(); - - http.MultipartFile multipartFile = http.MultipartFile.fromBytes( - 'file', - imageBytes, - filename: 'lorem.png', - contentType: MediaType('image', 'png'), - ); - - request.files.add(multipartFile); - http.StreamedResponse response = await request.send(); - http.Response finalResponse = await http.Response.fromStream(response); - var lang = ""; - if (finalResponse.statusCode == 200) { - Map jsonData = jsonDecode(finalResponse.body); - lang = jsonData['language']; - } else { - throw ('Error: ${finalResponse.statusCode}'); - } - - Directory tempDir = await getTemporaryDirectory(); - String tempPath = tempDir.path; - File tempImageFile = File('$tempPath/$imageName'); - await tempImageFile.writeAsBytes(imageBytes); - - String text = await FlutterTesseractOcr.extractText(tempImageFile.path, - language: lang, - args: { - "psm": "4", - "preserve_interword_spaces": "1", - }); - setState(() { - _extractedText = text; - }); - print(text); - return text; - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('OCR'), - ), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: uploadImage, - child: const Text('Test Upload Image'), - ), - Text(_extractedText), - TextButton( - onPressed: () { - Clipboard.setData(ClipboardData(text: _extractedText)); - }, - child: const Text('Copy to Clipboard'), - ), - ], - ), - ), - ); - } -} diff --git a/lib/bottomBar.dart b/lib/bottom_bar.dart similarity index 92% rename from lib/bottomBar.dart rename to lib/bottom_bar.dart index 08068e0..1b07dc2 100644 --- a/lib/bottomBar.dart +++ b/lib/bottom_bar.dart @@ -1,3 +1,5 @@ +// ignore_for_file: library_private_types_in_public_api + import 'package:flutter/material.dart'; //might be useless. keep for now @@ -10,6 +12,8 @@ class BottomBar extends StatefulWidget { class _BottomBarState extends State { int _selectedIndex = 0; + + // ignore: unused_field static const List _widgetOptions = [ Text('Home Page'), Text('todo'), diff --git a/lib/http_utils.dart b/lib/http_utils.dart new file mode 100644 index 0000000..0fe1eab --- /dev/null +++ b/lib/http_utils.dart @@ -0,0 +1,32 @@ +import 'dart:convert'; +import 'package:flutter/services.dart'; +import 'package:http/http.dart' as http; +import 'package:http_parser/http_parser.dart'; + +class HttpUtils { + var client = http.Client(); + Future performHttpRequest( + Uint8List imageBytes, String imageName) async { + var postUri = Uri.parse("http://130.61.88.150/upload"); + http.MultipartRequest request = http.MultipartRequest("POST", postUri); + + http.MultipartFile multipartFile = http.MultipartFile.fromBytes( + 'file', + imageBytes, + filename: 'lorem.png', + contentType: MediaType('image', 'png'), + ); + + request.files.add(multipartFile); + http.StreamedResponse response = await client.send(request); + http.Response finalResponse = await http.Response.fromStream(response); + var lang = ""; + if (finalResponse.statusCode == 200) { + Map jsonData = jsonDecode(finalResponse.body); + lang = jsonData['language']; + } else { + throw ('Error: ${finalResponse.statusCode}'); + } + return lang; + } +} diff --git a/lib/image_uploader.dart b/lib/image_uploader.dart new file mode 100644 index 0000000..7b8e212 --- /dev/null +++ b/lib/image_uploader.dart @@ -0,0 +1,33 @@ +import 'package:flutter/services.dart'; +import 'dart:io'; +import 'package:flutter_tesseract_ocr/flutter_tesseract_ocr.dart'; +import 'package:path_provider/path_provider.dart'; + +class ImageUploader { + //final http.Client client; + + ImageUploader(); + + Future buildImageFile(String img) async { + String imageName = img; + ByteData bytes = await rootBundle.load('assets/$imageName'); + return bytes.buffer.asUint8List(); + } + + Future performOcr( + Uint8List imageBytes, String imageName, String lang) async { + Directory tempDir = await getTemporaryDirectory(); + String tempPath = tempDir.path; + File tempImageFile = File('$tempPath/$imageName'); + await tempImageFile.writeAsBytes(imageBytes); + + String text = await FlutterTesseractOcr.extractText(tempImageFile.path, + language: lang, + args: { + "psm": "4", + "preserve_interword_spaces": "1", + }); + + return text; + } +} diff --git a/lib/main.dart b/lib/main.dart index 5bca55a..e587008 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,5 @@ -import 'package:cpd_app/OCRPage.dart'; -import 'package:cpd_app/bottomBar.dart'; +import 'package:cpd_app/ocr_page.dart'; +import 'package:cpd_app/bottom_bar.dart'; import 'package:flutter/material.dart'; void main() { diff --git a/lib/ocr_page.dart b/lib/ocr_page.dart new file mode 100644 index 0000000..ac526b8 --- /dev/null +++ b/lib/ocr_page.dart @@ -0,0 +1,62 @@ +import 'package:cpd_app/http_utils.dart'; +import 'package:cpd_app/image_uploader.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class OCRPage extends StatefulWidget { + const OCRPage({super.key}); + + @override + State createState() => _OCRPageState(); +} + +class _OCRPageState extends State { + String _extractedText = ''; + final ImageUploader _imageUploader = ImageUploader(); + final HttpUtils _httpUtils = HttpUtils(); + @override + void initState() { + super.initState(); + uploadImage("lorem.png"); + } + + Future uploadImage(String img) async { + //hier wird der user sein bild auswählen können + //aktuell hardcoded bild zum testen + String imageName = img; + Uint8List imageBytes = await _imageUploader.buildImageFile(imageName); + String lang = await _httpUtils.performHttpRequest(imageBytes, imageName); + String text = await _imageUploader.performOcr(imageBytes, imageName, lang); + + setState(() { + _extractedText = text; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('OCR'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ElevatedButton( + onPressed: () => uploadImage("lorem.png"), + child: const Text('Test Upload Image'), + ), + Text(_extractedText), + TextButton( + onPressed: () { + Clipboard.setData(ClipboardData(text: _extractedText)); + }, + child: const Text('Copy to Clipboard'), + ), + ], + ), + ), + ); + } +} diff --git a/lorem.png b/lorem.png new file mode 100644 index 0000000..42712bb Binary files /dev/null and b/lorem.png differ diff --git a/pubspec.lock b/pubspec.lock index 894cdcd..52a6447 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,30 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 + url: "https://pub.dev" + source: hosted + version: "64.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" + url: "https://pub.dev" + source: hosted + version: "6.2.0" + args: + dependency: transitive + description: + name: args + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + url: "https://pub.dev" + source: hosted + version: "2.4.2" async: dependency: transitive description: @@ -17,6 +41,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "723b4021e903217dfc445ec4cf5b42e27975aece1fc4ebbc1ca6329c2d9fb54e" + url: "https://pub.dev" + source: hosted + version: "8.7.0" characters: dependency: transitive description: @@ -33,6 +81,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: b2151ce26a06171005b379ecff6e08d34c470180ffe16b8e14b6d52be292b55f + url: "https://pub.dev" + source: hosted + version: "4.8.0" collection: dependency: transitive description: @@ -41,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.17.2" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" cross_file: dependency: transitive description: @@ -49,6 +113,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.3+6" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" cupertino_icons: dependency: "direct main" description: @@ -57,6 +129,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.6" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: abd7625e16f51f554ea244d090292945ec4d4be7bfbaf2ec8cccea568919d334 + url: "https://pub.dev" + source: hosted + version: "2.3.3" dio: dependency: "direct main" description: @@ -81,6 +161,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.1" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" file_picker: dependency: "direct main" description: @@ -121,6 +209,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.9.3+1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -160,6 +256,14 @@ packages: description: flutter source: sdk version: "0.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" http: dependency: "direct main" description: @@ -169,7 +273,7 @@ packages: source: hosted version: "1.1.0" http_parser: - dependency: transitive + dependency: "direct main" description: name: http_parser sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" @@ -256,6 +360,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" matcher: dependency: transitive description: @@ -288,6 +400,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + mockito: + dependency: "direct dev" + description: + name: mockito + sha256: "4b693867cee1853c9d1d7ecc1871f27f39b2ef2c13c0d8d8507dfe5bebd8aaf1" + url: "https://pub.dev" + source: hosted + version: "5.4.3" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" path: dependency: transitive description: @@ -360,11 +488,27 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.6" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" sky_engine: dependency: transitive description: flutter source: sdk version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16 + url: "https://pub.dev" + source: hosted + version: "1.4.0" source_span: dependency: transitive description: @@ -429,6 +573,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" web: dependency: transitive description: @@ -453,6 +605,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.3" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" sdks: dart: ">=3.1.3 <4.0.0" flutter: ">=3.7.0" diff --git a/pubspec.yaml b/pubspec.yaml index e94d852..99a9f5c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,13 +16,13 @@ dependencies: path_provider: ^2.0.14 http: ^1.1.0 flutter_tesseract_ocr: + http_parser: ^4.0.2 dev_dependencies: flutter_test: sdk: flutter - - flutter_lints: ^2.0.0 + mockito: ^5.0.0 flutter: assets: @@ -36,5 +36,4 @@ flutter: - assets/tessdata/ara.traineddata - assets/tessdata/rus.traineddata - uses-material-design: true - + uses-material-design: true \ No newline at end of file diff --git a/tessdata/ara.traineddata b/tessdata/ara.traineddata new file mode 100644 index 0000000..c8d129c Binary files /dev/null and b/tessdata/ara.traineddata differ diff --git a/tessdata/chi_sim.traineddata b/tessdata/chi_sim.traineddata new file mode 100644 index 0000000..388bac2 Binary files /dev/null and b/tessdata/chi_sim.traineddata differ diff --git a/tessdata/deu.traineddata b/tessdata/deu.traineddata new file mode 100644 index 0000000..97ed7b2 Binary files /dev/null and b/tessdata/deu.traineddata differ diff --git a/tessdata/ell.traineddata b/tessdata/ell.traineddata new file mode 100644 index 0000000..ed98ae1 Binary files /dev/null and b/tessdata/ell.traineddata differ diff --git a/tessdata/eng.traineddata b/tessdata/eng.traineddata new file mode 100644 index 0000000..bbef467 Binary files /dev/null and b/tessdata/eng.traineddata differ diff --git a/tessdata/jpn.traineddata b/tessdata/jpn.traineddata new file mode 100644 index 0000000..c4178f8 Binary files /dev/null and b/tessdata/jpn.traineddata differ diff --git a/tessdata/rus.traineddata b/tessdata/rus.traineddata new file mode 100644 index 0000000..b146cb2 Binary files /dev/null and b/tessdata/rus.traineddata differ diff --git a/test/widget_test.dart b/test/widget_test.dart index f8cabf9..ffd0467 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -1,9 +1,21 @@ +import 'package:cpd_app/image_uploader.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:cpd_app/main.dart'; void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const OpticText()); + TestWidgetsFlutterBinding.ensureInitialized(); + + test('Test OCR functionality', () async { + final ImageUploader imageUploader = ImageUploader(); + String imageName = "lorem.png"; + + var img = await imageUploader.buildImageFile(imageName); + assert(img.lengthInBytes > 0); + + // momentan auskommentiert, weil das pathproviderplugin irgentwie gemockt werden muss und ich es noch nicht hinbekommen habe + //mock response, weil man keine http requests in tests machen kann + // String mockResponse = "eng"; + + // String text = await imageUploader.performOcr(img, imageName, mockResponse); + // assert(text.contains("Lorem ipsum")); }); }