alatte 2024-01-02 13:53:21 +01:00
parent bfae90faca
commit 73a518733d
10 changed files with 510 additions and 50 deletions

View File

@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited.
version:
revision: "ead455963c12b453cdb2358cad34969c76daf180"
revision: "78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9"
channel: "stable"
project_type: app
@ -13,26 +13,26 @@ project_type: app
migration:
platforms:
- platform: root
create_revision: ead455963c12b453cdb2358cad34969c76daf180
base_revision: ead455963c12b453cdb2358cad34969c76daf180
create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
- platform: android
create_revision: ead455963c12b453cdb2358cad34969c76daf180
base_revision: ead455963c12b453cdb2358cad34969c76daf180
create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
- platform: ios
create_revision: ead455963c12b453cdb2358cad34969c76daf180
base_revision: ead455963c12b453cdb2358cad34969c76daf180
create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
- platform: linux
create_revision: ead455963c12b453cdb2358cad34969c76daf180
base_revision: ead455963c12b453cdb2358cad34969c76daf180
create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
- platform: macos
create_revision: ead455963c12b453cdb2358cad34969c76daf180
base_revision: ead455963c12b453cdb2358cad34969c76daf180
create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
- platform: web
create_revision: ead455963c12b453cdb2358cad34969c76daf180
base_revision: ead455963c12b453cdb2358cad34969c76daf180
create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
- platform: windows
create_revision: ead455963c12b453cdb2358cad34969c76daf180
base_revision: ead455963c12b453cdb2358cad34969c76daf180
create_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
base_revision: 78666c8dc57e9f7548ca9f8dd0740fbf0c658dc9
# User provided section

42
.vscode/launch.json vendored 100644
View File

@ -0,0 +1,42 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "optictext",
"request": "launch",
"type": "dart"
},
{
"name": "optictext (profile mode)",
"request": "launch",
"type": "dart",
"flutterMode": "profile"
},
{
"name": "optictext (release mode)",
"request": "launch",
"type": "dart",
"flutterMode": "release"
},
{
"name": "Flutter Web (Chrome)",
"request": "launch",
"type": "dart",
"program": "lib/main.dart",
"args": [
"--web-port", "8080"
],
"flutterMode": "debug",
"disableWebSecurity": true,
"webRenderer": "auto",
"deviceName": "Chrome",
"args2": []
}
]
}

View File

@ -5,8 +5,9 @@ import 'package:http_parser/http_parser.dart';
class HttpUtils {
var client = http.Client();
Future<String> performHttpRequest(
Uint8List imageBytes, String imageName) async {
Future<String> checkLang(Uint8List imageBytes, String imageName) async {
var client = http.Client();
var postUri = Uri.parse("http://130.61.88.150/upload");
http.MultipartRequest request = http.MultipartRequest("POST", postUri);

View File

@ -0,0 +1,218 @@
import 'dart:io';
import 'package:cpd_app/http_utils.dart';
import 'package:http/http.dart' as http;
import 'package:cpd_app/image_uploader.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'language_utils.dart';
import 'package:image_picker/image_picker.dart';
class ImageTranslation extends StatefulWidget {
const ImageTranslation({Key? key}) : super(key: key);
@override
State<ImageTranslation> createState() => _ImageTranslationState();
}
class _ImageTranslationState extends State<ImageTranslation> {
XFile? _selectedImage;
String _sourceLanguage = 'Auto';
String _targetLanguage = 'English';
String _translatedText = '';
final ImageUploader _imageUploader = ImageUploader();
final HttpUtils _httpUtils = HttpUtils();
/*1. Text extracten
2. Text übersetzen
3. Text aus dem alten bild mit farbe überdecken (koordinaten etwas weiter links oben + rechts unten als die erkannten koordinaten)
4. Übersetzen Text in neues bild einfügen
*/
Future<void> uploadImage(
Uint8List imageBytes,
String sourceLang,
String targetLang,
) async {
setState(() {
_translatedText = '';
});
//preprocessing
String text = '';
if (LanguageUtils.tessLanguages[sourceLang] == 'auto') {
String lang = await _httpUtils.checkLang(imageBytes, 'image.jpg');
text = await _imageUploader.performOcr(imageBytes, 'image.jpg', lang);
sourceLang = LanguageUtils.tessLanguages[sourceLang]!;
} else {
String langCode = LanguageUtils.tessLanguages[sourceLang]!;
text = await _imageUploader.performOcr(imageBytes, 'image.jpg', langCode);
}
if (text != '') {
Map<String, String> requestBody = {
'source_language': sourceLang,
'target_language': LanguageUtils.translatorLanguages[targetLang]!,
'text': text,
};
Map<String, String> headers = {
'content-type': 'application/x-www-form-urlencoded',
'X-RapidAPI-Key': 'd0fa3c2f3cmsh1805e7a2fed7cc2p1683ebjsna722e2bffafe',
'X-RapidAPI-Host': 'text-translator2.p.rapidapi.com',
};
final response = await http.post(
Uri.parse('https://text-translator2.p.rapidapi.com/translate'),
headers: headers,
body: requestBody,
);
if (response.statusCode == 200) {
_translatedText = response.body;
setState(() {
_translatedText = text;
_selectedImage = null;
});
} else {
print('Error: ${response.statusCode}');
}
print(_translatedText);
}
}
List<String> sourceLanguages = LanguageUtils.tessLanguages.keys.toList();
List<String> targetLanguages =
LanguageUtils.translatorLanguages.keys.toList();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Image Translation'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
// From - Subtle blue background
Container(
decoration: BoxDecoration(
color: Colors.blue.shade100,
borderRadius: BorderRadius.circular(8.0),
),
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Text('From', style: TextStyle(color: Colors.blue)),
DropdownButton<String>(
value: _sourceLanguage,
onChanged: (newValue) {
setState(() {
_sourceLanguage = newValue!;
});
},
items: sourceLanguages
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
],
),
),
// Target - Subtle red background
Container(
decoration: BoxDecoration(
color: Colors.red.shade100,
borderRadius: BorderRadius.circular(8.0),
),
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Text('Target', style: TextStyle(color: Colors.red)),
DropdownButton<String>(
value: _targetLanguage,
onChanged: (newValue) {
setState(() {
_targetLanguage = newValue!;
});
},
items: targetLanguages
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
],
),
),
],
),
const SizedBox(height: 20),
TextButton(
onPressed: () async {
final ImagePicker picker = ImagePicker();
final XFile? image =
await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
setState(() {
_selectedImage = image;
});
}
},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(Colors.blue),
),
child: const Text(
'Upload Image',
style: TextStyle(color: Colors.white),
),
),
ElevatedButton(
onPressed: _selectedImage != null
? () async {
if (_selectedImage != null) {
File imageFile = File(_selectedImage!.path);
List<int> imageBytes = imageFile.readAsBytesSync();
await uploadImage(Uint8List.fromList(imageBytes),
_sourceLanguage, _targetLanguage);
}
}
: null,
child: const Text('Extract Text'),
),
_translatedText.isNotEmpty
? Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
readOnly: true,
initialValue: _translatedText,
maxLines: null,
decoration: const InputDecoration(
labelText: 'Extracted Text',
border: OutlineInputBorder(),
),
),
),
)
: Container(),
TextButton(
onPressed: () {
Clipboard.setData(ClipboardData(text: _translatedText));
},
child: const Text('Copy to Clipboard'),
),
],
),
),
);
}
}

View File

@ -4,8 +4,6 @@ import 'package:flutter_tesseract_ocr/flutter_tesseract_ocr.dart';
import 'package:path_provider/path_provider.dart';
class ImageUploader {
//final http.Client client;
ImageUploader();
Future<Uint8List> buildImageFile(String img) async {
@ -30,4 +28,21 @@ class ImageUploader {
return text;
}
Future<String> performAdvancedOCR(
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.extractHocr(tempImageFile.path,
language: lang,
args: {
"psm": "4",
"preserve_interword_spaces": "1",
});
return text;
}
}

View File

@ -0,0 +1,109 @@
class LanguageUtils {
static Map<String, String> tessLanguages = {
'Auto': 'auto',
'English': 'eng',
'German': 'deu',
'French': 'fra',
'Spanish': 'spa',
'Italian': 'ita',
'Portuguese': 'por',
'Dutch': 'dut',
'Polish': 'pol',
'Russian': 'rus',
'Japanese': 'jpn',
'Chinese (Simplified)': 'chi_sim',
'Chinese (Traditional)': 'chi_tra',
'Korean': 'kor',
'Arabic': 'ara',
'Hindi': 'hin',
'Indonesian': 'ind',
'Malay': 'mal',
'Thai': 'tha',
'Vietnamese': 'vie',
'Turkish': 'tur',
'Ukrainian': 'ukr',
'Hungarian': 'hun',
'Czech': 'cze',
'Finnish': 'fin',
'Danish': 'dan',
'Norwegian': 'nor',
'Swedish': 'swe',
'Greek': 'gre',
'Slovak': 'slo',
'Croatian': 'hrv',
'Lithuanian': 'lit',
'Romanian': 'rum',
'Bulgarian': 'bul',
'Latvian': 'lav',
'Estonian': 'est',
'Persian': 'per',
'Hebrew': 'heb',
'Serbian': 'srp',
'Albanian': 'alb',
'Tagalog': 'tgl',
'Azerbaijani': 'aze',
'Basque': 'baq',
'Belarusian': 'bel',
'Bengali': 'ben',
'Bosnian': 'bos',
'Cebuano': 'ceb',
'Esperanto': 'epo',
'Galician': 'glg',
'Georgian': 'geo',
'Gujarati': 'guj',
};
static Map<String, String> translatorLanguages = {
'Auto': 'auto',
'English': 'en',
'German': 'de',
'French': 'fr',
'Spanish': 'spa',
'Italian': 'ita',
'Portuguese': 'por',
'Dutch': 'dut',
'Polish': 'pol',
'Russian': 'rus',
'Japanese': 'jpn',
'Chinese (Simplified)': 'chi_sim',
'Chinese (Traditional)': 'chi_tra',
'Korean': 'kor',
'Arabic': 'ara',
'Hindi': 'hin',
'Indonesian': 'ind',
'Malay': 'mal',
'Thai': 'tha',
'Vietnamese': 'vie',
'Turkish': 'tur',
'Ukrainian': 'ukr',
'Hungarian': 'hun',
'Czech': 'cze',
'Finnish': 'fin',
'Danish': 'dan',
'Norwegian': 'nor',
'Swedish': 'swe',
'Greek': 'gre',
'Slovak': 'slo',
'Croatian': 'hrv',
'Lithuanian': 'lit',
'Romanian': 'rum',
'Bulgarian': 'bul',
'Latvian': 'lav',
'Estonian': 'est',
'Persian': 'per',
'Hebrew': 'heb',
'Serbian': 'srp',
'Albanian': 'alb',
'Tagalog': 'tgl',
'Azerbaijani': 'aze',
'Basque': 'baq',
'Belarusian': 'bel',
'Bengali': 'ben',
'Bosnian': 'bos',
'Cebuano': 'ceb',
'Esperanto': 'epo',
'Galician': 'glg',
'Georgian': 'geo',
'Gujarati': 'guj',
};
}

View File

@ -1,3 +1,4 @@
import 'package:cpd_app/image_translator.dart';
import 'package:cpd_app/ocr_page.dart';
import 'package:cpd_app/bottom_bar.dart';
import 'package:flutter/material.dart';
@ -66,7 +67,13 @@ class ImageToolsPage extends StatelessWidget {
child: const Text('OCR'),
),
ElevatedButton(
onPressed: () {},
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ImageTranslation()),
);
},
child: const Text('Image Translation'),
),
],

View File

@ -1,10 +1,13 @@
import 'dart:io';
import 'package:cpd_app/http_utils.dart';
import 'package:cpd_app/image_uploader.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'language_utils.dart';
import 'package:image_picker/image_picker.dart';
class OCRPage extends StatefulWidget {
const OCRPage({super.key});
const OCRPage({Key? key}) : super(key: key);
@override
State<OCRPage> createState() => _OCRPageState();
@ -14,25 +17,31 @@ class _OCRPageState extends State<OCRPage> {
String _extractedText = '';
final ImageUploader _imageUploader = ImageUploader();
final HttpUtils _httpUtils = HttpUtils();
@override
void initState() {
super.initState();
uploadImage("lorem.png");
}
String _selectedLanguage = 'Auto';
XFile? _selectedImage;
Future<void> 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);
Future<void> uploadImage(
Uint8List imageBytes, String selectedLanguage) async {
setState(() {
_extractedText = '';
});
//preprocessing, maybe
String text = '';
if (selectedLanguage == 'Auto') {
String lang = await _httpUtils.checkLang(imageBytes, 'image.jpg');
text = await _imageUploader.performOcr(imageBytes, 'image.jpg', lang);
} else {
String langCode = LanguageUtils.tessLanguages[selectedLanguage]!;
text = await _imageUploader.performOcr(imageBytes, 'image.jpg', langCode);
}
setState(() {
_extractedText = text;
_selectedImage = null;
});
}
List<String> languages = LanguageUtils.tessLanguages.keys.toList();
@override
Widget build(BuildContext context) {
return Scaffold(
@ -43,11 +52,69 @@ class _OCRPageState extends State<OCRPage> {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () => uploadImage("lorem.png"),
child: const Text('Test Upload Image'),
DropdownButton<String>(
value: _selectedLanguage,
onChanged: (newValue) {
setState(() {
_selectedLanguage = newValue!;
});
},
items: languages.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
Text(_extractedText),
_selectedImage != null
? SizedBox(
width: MediaQuery.of(context).size.width * 0.8,
height: MediaQuery.of(context).size.height * 0.4,
child: Image.file(File(_selectedImage!.path)),
)
: Container(),
ElevatedButton(
onPressed: () async {
final ImagePicker picker = ImagePicker();
final XFile? image =
await picker.pickImage(source: ImageSource.gallery);
if (image != null) {
setState(() {
_selectedImage = image;
});
}
},
child: const Text('Select Image'),
),
ElevatedButton(
onPressed: _selectedImage != null
? () async {
if (_selectedImage != null) {
File imageFile = File(_selectedImage!.path);
List<int> imageBytes = imageFile.readAsBytesSync();
await uploadImage(
Uint8List.fromList(imageBytes), _selectedLanguage);
}
}
: null,
child: const Text('Extract Text'),
),
_extractedText.isNotEmpty
? Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
readOnly: true,
initialValue: _extractedText,
maxLines: null,
decoration: const InputDecoration(
labelText: 'Extracted Text',
border: OutlineInputBorder(),
),
),
),
)
: Container(),
TextButton(
onPressed: () {
Clipboard.setData(ClipboardData(text: _extractedText));

View File

@ -93,10 +93,10 @@ packages:
dependency: transitive
description:
name: collection
sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.17.2"
version: "1.18.0"
convert:
dependency: transitive
description:
@ -388,10 +388,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e
url: "https://pub.dev"
source: hosted
version: "1.9.1"
version: "1.10.0"
mime:
dependency: transitive
description:
@ -521,18 +521,18 @@ packages:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
url: "https://pub.dev"
source: hosted
version: "1.11.0"
version: "1.11.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
string_scanner:
dependency: transitive
description:
@ -553,10 +553,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
url: "https://pub.dev"
source: hosted
version: "0.6.0"
version: "0.6.1"
typed_data:
dependency: transitive
description:
@ -585,10 +585,10 @@ packages:
dependency: transitive
description:
name: web
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152
url: "https://pub.dev"
source: hosted
version: "0.1.4-beta"
version: "0.3.0"
win32:
dependency: transitive
description:
@ -614,5 +614,5 @@ packages:
source: hosted
version: "3.1.2"
sdks:
dart: ">=3.1.3 <4.0.0"
dart: ">=3.2.0-194.0.dev <4.0.0"
flutter: ">=3.7.0"

View File

@ -17,6 +17,7 @@ dependencies:
http: ^1.1.0
flutter_tesseract_ocr:
http_parser: ^4.0.2
#opencv_4: ^1.0.0
dev_dependencies:
flutter_test: