SDK

Adding the SDK to your project

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:

Adding the repository

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" }
}
                
            

Adding the dependency

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.

Starting the scanner

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);
    }
}
                
            

Customizing the scanner

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);
                
            

Calling the OCR API

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();
        }
    });
}

                
            

APK size / APK splitting

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:

  • arm64-v8a.apk: 8.8MB
  • armeabi-v7a.apk: 7.8MB
  • x86_64.apk: 23.4MB
  • x86.apk: 16.5MB

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.

JavaDoc

The JavaDoc of the latest version is available for download here.

Versions

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

Changelog

1.2.0

Changed

  • We upgraded our dependency on the Picasso library to version 2.71828.

1.1.6

Fixed

  • Changed minification to include our package name to prevent duplicate classes.

1.1.4

Fixed

  • Fix bug where you could press the next button when you didn’t create any image yet.

Changed

  • The minSdkVersion is now 14.

1.1.3

Fixed

  • Properly center the warning messages.

1.1.2

Added

  • Added option 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%.
  • Added option 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.

Changed

  • Shutter now directly shows after pressing the button.

1.1.1

Added

  • Added option KlippaScanner.PRIMARY_DARK_COLOR to set the colorPrimaryDark (the color in the status bar)

Changed

  • Color handling is now changed: KlippaScanner.PRIMARY_DARK_COLOR is used for the status bar, KlippaScanner.PRIMARY_COLOR for the app bar and KlippaScanner.ACCENT_COLOR for the controls.

Fixed

  • Give the camera control some more padding for when the status bar is hidden.

1.1.0

Added

  • Added options 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.
  • Added option 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.

1.0.9

Changed

  • Upgrade dependency versions.

1.0.8

Fixed

  • Restore correct position in image list after deleting an image.

1.0.7

Added

  • Add next/previous arrows.

1.0.6

Fixed

  • Prevent crash when opening details when taking picture.
  • Fix when amount of images goes above 100.
  • Fix flashlight staying on when returning from SDK.