To add our SDK to your own application, you should add it to the build process of your application. Currently we only support doing this using Gradle.
Adding the SDK to your gradle build file consist of two steps:
To add the repository to your gradle file, open the build.gradle file in your application. Make sure you use the build.grade of your project, not the top-level one.
Edit the repositories part of your build.gradle, so that it contains the following:
maven {
credentials {
username 'ExampleUsername'
password 'ExamplePassword'
}
url "https://custom-ocr.klippa.com/sdk/android/maven"
}
maven { url "https://jitpack.io" }
We add jitpack.io because some of our dependencies are hosted there. If your build.gradle already contains jitpack.io, don't add it.
The full repositories section of your build.gradle might look like this now:
repositories {
jcenter()
maven {
credentials {
username 'ExampleUsername'
password 'ExamplePassword'
}
url "https://custom-ocr.klippa.com/sdk/android/maven"
}
maven { url "https://jitpack.io" }
}
Edit the dependencies part of your build.gradle, so that it contains the following:
implementation 'com.klippa:scanner:1.2.0'
The full dependencies section of your build.gradle might look like this now:
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
implementation 'com.klippa:scanner:1.2.0'
}
When you build your app now, it should download our library as dependency, and with that all dependencies of our library.
To start the scanner, import our package:
// Add to the top of your file
import com.klippa.scanner.KlippaScanner;
Then start our scanner Activity from your own Activity:
// We use this constant to keep track of our activity request.
public static int KLIPPA_SCANNER_REQUEST_CODE = 1;
private void startKlippaScanner() {
// Launch the Klippa Scanner
Intent klippaScannerIntent = new Intent(this, KlippaScanner.class);
klippaScannerIntent.putExtra(KlippaScanner.LICENSE, "replace-with-received-license");
startActivityForResult(klippaScannerIntent, KLIPPA_SCANNER_REQUEST_CODE);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == KLIPPA_SCANNER_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
// Get the ArrayList of scanned images
ArrayList images = data.getParcelableArrayListExtra(KlippaScanner.IMAGES);
for (Image image : images) {
// Use the result image.
Log.i(TAG, "Got image: " + image.toString());
Log.i(TAG, "Image path: " + image.getFilePath());
// Get image as Java File object.
File imageFile = image.getFile();
}
} else if (requestCode == KLIPPA_SCANNER_REQUEST_CODE && resultCode == Activity.RESULT_CANCELED) {
if (data.hasExtra(KlippaScanner.ERROR) && data.getStringExtra(KlippaScanner.ERROR) != null) {
String error = data.getStringExtra(KlippaScanner.ERROR);
Log.e(TAG, "Scanner was canceled with error: " + error);
} else {
Log.e(TAG, "Scanner was canceled");
}
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
The SDK has a few customizing settings, the following methods are available:
// The primary color of the interface, should be a resource. This is used for the app bar.
klippaScannerIntent.putExtra(KlippaScanner.PRIMARY_COLOR, getResources().getColor(android.R.color.holo_orange_light));
// The primary dark color of the interface, should be a resource. This is used for the status bar.
klippaScannerIntent.putExtra(KlippaScanner.PRIMARY_DARK_COLOR, getResources().getColor(android.R.color.holo_orange_dark));
// The overlay color (when using document detection), should be a resource.
klippaScannerIntent.putExtra(KlippaScanner.OVERLAY_COLOR, getResources().getColor(android.R.color.holo_blue_bright));
// The accent color of the interface, should be a resource. This is used for control elements.
klippaScannerIntent.putExtra(KlippaScanner.ACCENT_COLOR, getResources().getColor(android.R.color.holo_red_dark));
// The color of the background of the warning message, should be a resource.
klippaScannerIntent.putExtra(KlippaScanner.WARNING_COLOR, getResources().getColor(android.R.color.holo_red_light));
// The warning message when someone should move closer to a document, should be a string.
klippaScannerIntent.putExtra(KlippaScanner.MOVE_CLOSER_MESSAGE, "Move closer to the document");
// Where to put the image results.
klippaScannerIntent.putExtra(KlippaScanner.OUTPUT_DIRECTORY, "/sdcard/scanner");
// Whether to show the icon to enable "multi-document-mode"
klippaScannerIntent.putExtra(KlippaScanner.ALLOW_CREATE_MULTIPLE_RECEIPTS, true);
// Whether the "multi-document-mode" should be enabled by default.
klippaScannerIntent.putExtra(KlippaScanner.DEFAULT_CREATE_MULTIPLE_RECEIPTS, true);
// Whether the crop mode (auto edge detection) should be enabled by default.
klippaScannerIntent.putExtra(KlippaScanner.DEFAULT_CROP, true);
// What the default color conversion will be (grayscale, original).
klippaScannerIntent.putExtra(KlippaScanner.DEFAULT_COLOR, true);
// Define the max resolution of the output file. It’s possible to set only one of these values. We will make sure the picture fits in the given resolution. We will also keep the aspect ratio of the image. Default is max resolution of camera.
klippaScannerIntent.putExtra(KlippaScanner.RESOLUTION_MAX_WIDTH, 1920);
klippaScannerIntent.putExtra(KlippaScanner.RESOLUTION_MAX_HEIGHT, 1080);
// Set the output quality (between 0-100) of the jpg encoder. Default is 100.
klippaScannerIntent.putExtra(KlippaScanner.OUTPUT_QUALITY, 95);
// The options above also return the value that the option has when the activity finished:
boolean createMultipleReceipts = data.getBooleanExtra(KlippaScanner.CREATE_MULTIPLE_RECEIPTS, false);
boolean cropperWasEnabled = data.getBooleanExtra(KlippaScanner.CROP, false);
String color = data.getStringExtra(KlippaScanner.COLOR);
It's possible to use the results of the scanner with the OCR API.
To use it, generate a Public API key with our API through your own backend, this makes sure your API key won't be leaked and/or abused.
Edit the dependencies part of your build.gradle, so that it contains the following:
implementation 'com.klippa:ocrapi:0.0.4'
You then have the possibility to call the OCR API client and run it on the images of the scanner.
The following methods runs the OCR on every image and then shows the total amount in a Toast.
// Place this at the top of class.
import com.klippa.ocrapi.ApiCallback;
import com.klippa.ocrapi.ApiClient;
import com.klippa.ocrapi.ApiException;
import com.klippa.ocrapi.Configuration;
import com.klippa.ocrapi.api.ParsingApi;
import com.klippa.ocrapi.auth.ApiKeyAuth;
import com.klippa.ocrapi.model.ReceiptBody;
import com.klippa.ocrapi.model.Receipt;
import com.klippa.scanner.KlippaScanner;
import com.klippa.scanner.object.Image;
// Place this inside your Activity class.
private int ocrResults = 0;
private double totalAmount = 0;
// Call this method with the result images for the scanner.
private void processImageOCR(ArrayList<Image> images) {
// Generate a public API key for your customer here.
String ocrAPIPublicKey = "";
Activity context = this;
ocrResults = 0;
totalAmount = 0;
ApiClient apiClient = Configuration.getDefaultApiClient();
ApiKeyAuth authentication = (ApiKeyAuth) apiClient.getAuthentication("APIPublicKeyHeader");
authentication.setApiKey(ocrAPIPublicKey);
ParsingApi parsingAPI = new ParsingApi(apiClient);
for (int i=0;i<images.size();i++) {
final Image image = images.get(i);
try {
parsingAPI.parseDocumentAsync(image.getFile(), null, null, null, null, new ApiCallback<ReceiptBody>() {
@Override
public void onFailure(ApiException e, int statusCode, Map<String, List<String>> responseHeaders) {
Log.e(TAG, e.toString());
// @todo: handle error.
ocrResults++;
if (ocrResults == images.size()) {
showTotalAmountToast(context);
}
}
@Override
public void onSuccess(ReceiptBody result, int statusCode, Map<String, List<String>> responseHeaders) {
Receipt receipt = result.getData();
if (receipt != null) {
Log.i(TAG, "Got receipt: " + receipt.toString());
totalAmount += receipt.getAmount();
} else {
Log.i(TAG, "Got no receipt");
}
ocrResults++;
if (ocrResults == images.size()) {
showTotalAmountToast(context);
}
}
@Override
public void onUploadProgress(long bytesWritten, long contentLength, boolean done) {
}
@Override
public void onDownloadProgress(long bytesRead, long contentLength, boolean done) {
}
});
} catch (ApiException e) {
e.printStackTrace();
}
}
}
private void showTotalAmountToast(Activity context) {
context.runOnUiThread(new Runnable() {
@Override
public void run() {
NumberFormat formatter = new DecimalFormat("#0.00");
Toast.makeText(context, "Total amount: " + formatter.format(totalAmount / 100.0), Toast.LENGTH_LONG).show();
}
});
}
Because our SDK includes OpenCV, which includes native code which in turn generates a native library (.so file) for every architecture, the APK size can increase quite a bit by using our SDK.
To minimize the impact of the SDK on your app size, you can enable APK splitting in your build.grade, like this:
android {
// Other options
splits {
abi {
enable true
universalApk false
reset()
include 'arm64-v8a', 'armeabi-v7a', 'x86', 'x86_64'
}
}
// Other options
}
In our example app (which contains not much more than the SDK), this resulted in the following APK files:
While creating a single APK file creates a file that is 41MB, since most phones are ARM, on average APK splitting will result in a 32.7MB smaller APK.
Please note that all APK files should be signed and uploaded to the Play Store to get a release that works on all devices.
The JavaDoc of the latest version is available for download here.
The following versions are available:
Version | Gradle dependency | JavaDoc |
---|---|---|
1.2.0 | implementation 'com.klippa:scanner:1.2.0' | Download |
1.1.6 | implementation 'com.klippa:scanner:1.1.6' | Download |
1.1.5 | implementation 'com.klippa:scanner:1.1.5' | Download |
1.1.4 | implementation 'com.klippa:scanner:1.1.4' | Download |
1.1.3 | implementation 'com.klippa:scanner:1.1.3' | Download |
1.1.2 | implementation 'com.klippa:scanner:1.1.2' | Download |
1.1.1 | implementation 'com.klippa:scanner:1.1.1' | Download |
1.0.9 | implementation 'com.klippa:scanner:1.0.9' | Download |
minSdkVersion
is now 14
.KlippaScanner.OUTPUT_FILENAME
to set the output filename. By default it will be a random UUID string. There are 2 replacements available: %randomUUID%
and %dateTime%
, so you could do something like this: KlippaScanner-%randomUUID%-%dateTime%
.KlippaScanner.IMAGE_LIMIT
to set a maximum amount of files to be taken. After reaching the maximum the user will be shown a message that they reached the limit. The message is configured using KlippaScanner.IMAGE_LIMIT_REACHED_MESSAGE
.KlippaScanner.PRIMARY_DARK_COLOR
to set the colorPrimaryDark (the color in the status bar)KlippaScanner.PRIMARY_DARK_COLOR
is used for the status bar, KlippaScanner.PRIMARY_COLOR
for the app bar and KlippaScanner.ACCENT_COLOR
for the controls.KlippaScanner.RESOLUTION_MAX_WIDTH
and KlippaScanner.RESOLUTION_MAX_HEIGHT
to set the max resolution of the output file. It’s possible to set only one of these values. We will make sure the picture fits in the given resolution. We will also keep the aspect ratio of the image.KlippaScanner.OUTPUT_QUALITY
to set the output quality (between 0-100) of the jpg encoder. You can see what the quality level does to the output quality/size here: https://sirv.sirv.com/Examples/example-coast.jpg?q=80
change the last value to see the quality change.