Document Plugin


Landscape mode

As of version 3.11, the Document Module is limited to Portrait Mode

Examples Source Code

The source code of all example use cases of the Document Plugin can be found in the Android SDK Bundle

Plugin Description

The Anyline Document Plugin detects document outlines, validates the angles of the document to ensure it is not too skewed, determines the sharpness of the text and rectifies the document.

In the first step the preview frames are analyzed. Once a valid and sharp document is detected, a high resolution image is taken from the camera, analyzed, and, if valid and sharp, perspectively corrected and cropped to the document bounds.

Note

The module does not perform the OCR step, but instead provides a high resolution, perspectively transformed and rectified, cropped document image, which is ensured to hold sharp text.

The description of all parameters can be found at Plugins > Document

How to implement the Document Plugin

This is a step by step guide on how to implement the Document Module in Android.

Get the Document Module in Java

First add the Scan View to your xml-layout.

Add the Document ScanView to the layout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:app="http://schemas.android.com/apk/res-auto"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                xmlns:tools="http://schemas.android.com/tools"
                tools:context="io.anyline.examples.barcode.ScanBarcodeActivity"
                android:id="@+id/main_layout">


    <io.anyline.view.ScanView
        android:id="@+id/scan_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

    <ImageView
        android:id="@+id/image_result"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:adjustViewBounds="true"/>

    <ImageView
        android:id="@+id/full_image"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:adjustViewBounds="true"/>

</RelativeLayout>

In the next step, go to your Java Activity code and get the view from the layout in onCreate() or onActivityCreated()

Get the Scan View
		documentScanView = (ScanView) findViewById(R.id.scan_view);

Initialize and Configure Anyline

ScanViewPlugin

Initialize ScanViewPlugin configuration

The ScanViewPlugin configuration holds the UI properties for cutout and visual feedback.

Initialize the ScanViewPlugin configuration
		//init the scanViewPluginConfig
		ScanViewPluginConfig documentScanViewPluginConfig = new ScanViewPluginConfig(getApplicationContext(), "document_view_config.json");

Initialize ScanViewPlugin

Together with your Anyline License Key Generation, you can now initialise MeterScanViewPugin. The DocumentScanViewPlugin will internally initialize the DocumentScanPlugin. In java, this should be called in onCreate() or onActivityCreated().

Initialize the ScanViewPlugin
		//init the Document Scan View Plugin
		final DocumentScanViewPlugin documentScanViewPlugin = new DocumentScanViewPlugin(getApplicationContext(),getString(R.string.anyline_license_key), documentScanViewPluginConfig, "Document");

ScanView

Set the Base Configuration

The base configurations are holding the camera and the flash properties. You can set it the following way, also in onCreate() or onActivityCreated()

Set the Base Configuration(Camera and Flash configuration) and the View Configuration (Cutout and ScanView configuration)
		//init the base config for camera and flash
		BaseScanViewConfig documentBaseScanViewconfig  = new BaseScanViewConfig(getApplicationContext(), "document_view_config.json");
		//set the base config to the scan view
		documentScanView.setScanViewConfig(documentBaseScanViewconfig);

Set the ScanViewPlugin to the ScanView

A ScanView is holding a ScanViewPlugin, therefore a ScanViewPlugin has to be set to the ScanView.

Set the ScanViewPlugin to the ScanView
		//set the scan view plugin to the scan view
		documentScanView.setScanViewPlugin(documentScanViewPlugin);

The Document Result Listener

In the Document Module of Anyline SDK, you will be provided the final results via a ResultListener.

In case of the Document Plugin, the listener is called DocumentScanResultListener. This section describes the DocumentResultListener callback in detail.

onResult

This is the main callback method for the Document Module. It is called once a valid result has been found.

As stated above, the Document Module does not perform OCR, but detects document outlines, validates the angles of the document to ensure the it is not too skewed, determines the sharpness of the text and rectifies the document. Therefore the result of a Document Module scan is an image.

The result is an instance of ScanResult, which holds the transformed image as the result, the full image that was processed, as well as a cutout image.

See DocumentResult in the Javadocs

onResult
			@Override
			public void onResult(ScanResult result) {
				if (progressDialog != null && progressDialog.isShowing()) {
					progressDialog.dismiss();
				}

				// handle the result document images here
				if (progressDialog != null && progressDialog.isShowing()) {
					progressDialog.dismiss();
				}

				AnylineImage transformedImage = (AnylineImage) result.getResult();
				AnylineImage fullFrame = result.getFullImage();

				showToast(getString(R.string.document_picture_success));


				// show the picture on the screen
				displayPicture(transformedImage);

				/**
				 * IMPORTANT: cache provided frames here, and release them at the end of this onResult. Because
				 * keeping them in memory (e.g. setting the full frame to an ImageView)
				 * will result in a OutOfMemoryError soon. This error is reported in {@link #onTakePictureError
				 * (Throwable)}
				 *
				 * Use a DiskCache http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html#disk-cache
				 * for example
				 *
				 */
				File outDir = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "ok");
				outDir.mkdir();
				File outFile = new File(outDir, "" + System.currentTimeMillis() + ".jpg");
				try {
					transformedImage.save(outFile, 100);
					showToast("image saved to " + outFile.getAbsolutePath());
				} catch (IOException e) {
					e.printStackTrace();
				}

				// release the images
				transformedImage.release();
				fullFrame.release();
			}
		});
	}
  • result

Changed in version 3.10.

Parameter Type Description
getResult() AnylineImage The image of the detected document, cropped and perspectively transformed
getFullImage() AnylineImage The full image the document was detected on
getOutline() List<PointF> The outline of the detected digits in the full image

Release the Images

As it is shown in the example code, it is important to release the images as soon as they are cached. Otherwise, as the Document Module deals with large images (depending on the camera.pictureResolution), this might result in OutOfMemoryError

onPreviewProcessingSuccess

This is called after the preview of the document is completed, and a full picture will be processed automatically in the next step.

onPreviewProcessingSuccess
			@Override
			public void onPreviewProcessingSuccess(AnylineImage anylineImage) {

				notificationToast = Toast.makeText(ScanDocumentActivity.this, "Scanning full document. Please hold " +
						"still", Toast.LENGTH_LONG);
				notificationToast.show();

				showToast(getString(R.string.document_preview_success));
			}
  • anylineImage
Type Description
AnylineImage The cropped preview image that was last processed (not perspectively transformed)

onPreviewProcessingFailure

This method is called if a preview picture was not valid according to the quality checks.
This can be used to present meaningful hints and error messages to the user (like Adjust Perspective for example).

After this callback, the preview processing is going to restart automatically.

onPreviewProcessingFailure
			@Override
			public void onPreviewProcessingFailure(DocumentScanViewPlugin.DocumentError error) {
				// this is called on any error while processing the document image
				// Note: this is called every time an error occurs in a run, so that might be quite often
				// An error message should only be presented to the user after some time
				Log.d("Callback", "onPictureProcessingFailure: " + error.name());

			}

Important

The SDK will not stop scanning when this callback is called, but process the next frame.
This means that this callback is solely for information purposes.

  • documentError
Type Description
DocumentError The Error that caused the full frame processing to fail

onPictureProcessingFailure

This method is called, once the full picture was taken, but the checks on the document in the picture failed.

After this callback, the preview processing is going to restart automatically.

onPictureProcessingFailure
			@Override
			public void onPictureProcessingFailure(DocumentScanViewPlugin.DocumentError error) {

				// handle an error while processing the full picture here
				// the preview will be restarted automatically
				String text = getString(R.string.document_picture_error);
				switch (error) {
					case DOCUMENT_NOT_SHARP:
						text += getString(R.string.document_error_not_sharp);
						break;
					case DOCUMENT_SKEW_TOO_HIGH:
						text += getString(R.string.document_error_skew_too_high);
						break;
					case DOCUMENT_OUTLINE_NOT_FOUND:
						text += getString(R.string.document_error_outline_not_found);
						break;
					case GLARE_DETECTED:
						text += getString(R.string.document_error_glare_detected);
						break;
					case IMAGE_TOO_DARK:
						text += getString(R.string.document_error_too_dark);
						break;
				}

				showToast(text);
				if (progressDialog != null && progressDialog.isShowing()) {
					progressDialog.dismiss();
				}

				AnylineImage image = documentScanViewPlugin.getCurrentFullImage();

				if (image != null) {
					File outDir = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "error");
					outDir.mkdir();
					File outFile = new File(outDir, "" + System.currentTimeMillis() + error.name() + ".jpg");
					Log.d(TAG, "saved image to: " + outFile.getAbsolutePath());
					try {
						image.save(outFile, 100);
						Log.d(TAG, "error image saved to " + outFile.getAbsolutePath());
					} catch (IOException e) {
						e.printStackTrace();
					}
					image.release();
				}
			}

			@Override
			public void onTakePictureSuccess() {
				progressDialog = ProgressDialog.show(ScanDocumentActivity.this, "Processing", "Processing the picture" +
						". Please wait", true);

				if (notificationToast != null) {
					notificationToast.cancel();
				}
  • documentError
Type Description
DocumentError The Error that caused the full frame processing to fail

onDocumentOutlineDetected

This method is called on every successful preview run and returns the detected document outlines as a List of PointF.

This can be used to draw visual feedback for the user.

Automatic Visual Feedback

If this callback returns false, the Anyline SDK will draw the visual feedback for you. Otherwise the SDK assumes that visual feedback is going to be handled by your app

onDocumentOutlineDetected
			@Override
			public boolean onDocumentOutlineDetected(List rect, boolean documentShapeAndBrightnessValid) {

				lastOutline = rect;
				return true;

			}
  • list
Type Description
List<PointF> The outline of the document as a list of points going clockwise. The points are relative to the preview screen
  • documentShapeAndBrightnessValid
Type Description
boolean Indicating if the detected document outline is of valid shape, and the brightness of the image is valid

onTakePictureSuccess

This callback is called after the full frame image was successfully captured from the camera and is about to be processed.

onTakePictureSuccess
			@Override
			public void onTakePictureSuccess() {
				progressDialog = ProgressDialog.show(ScanDocumentActivity.this, "Processing", "Processing the picture" +
						". Please wait", true);

				if (notificationToast != null) {
					notificationToast.cancel();
				}

			}

onTakePictureError

This callback is called if the full frame image could not be captured from the camera. This is most probably because of an OutOfMemoryError, meaning that there is not enough memory for the Android Camera to return a picture.

onTakePictureError
			@Override
			public void onTakePictureError(Throwable error) {
				Log.d("Callback", "onTakePictureError: " + error.getMessage());
			}
  • throwable
Type Description
Throwable The throwable that was returned from the Android Camera

onPictureCornersDetected

New in version 3.17.

Called after android_document_module_trigger_picture_corner_detection and after a picture was successfully taken from the camera.
The picture is processed, and the document corners are detected and returned.

If no document corners are found, the returned corners are set to the image corners.

Document Corners

The document corners returned are relative to the fullFrame

onPictureCornersDetected
			@Override
			public void onPictureCornersDetected(AnylineImage fullFrame, List corners) {
				Log.d("Callback", "onPictureCornersDetected");
			}
  • fullFrame
Type Description
AnylineImage The image the corner detection was performed on
  • corners
Type Description
List<PointF> The document corners found. The order is: upLeft, upRight, downRight, downLeft

onPictureTransformed

New in version 3.17.

Called as a result of transformPicture. Returns the perspectivelly transformed image.

onPictureCornersDetected
			@Override
			public void onPictureTransformed(AnylineImage transformedImage) {
				Log.d("Callback", "onPictureTransformed");
			}
  • transformedImage
Type Description
AnylineImage The image cropped to the corners of the document and perspectively tranformed

onPictureTransformError

New in version 3.17.

Called if an error occured within transformPicture.

onPictureCornersDetected
			@Override
			public void onPictureTransformError(DocumentScanViewPlugin.DocumentError error) {
				Log.d("Callback", "onPictureTransformError: " + error.name());
			}
  • error
Type Description
DocumentError The error that was thrown during processing

Setting the desired document formats

New in version 3.8.

Starting with version 3.8, you can set valid document formats with

Limit the document formats
				// Note: this is called every time an error occurs in a run, so that might be quite often
				// An error message should only be presented to the user after some time

Other formats will not be considered valid by the SDK.

To make it easier to use, some common formats are already implemented as an enum, as you can see above. But you are free to set any custom format as well.

Default

The default document format is set to DIN A formats. This is also the only valid format to Anyline SDK versions < 3.8

You can set a maximum deviation of the desired document formats with

Allow a deviation on the format
scanViewPlugin.setMaxDocumentRatioDeviation(0.19);

Default

The default deviation is set to 0.15

Manual corner detection

New in version 3.17.

With this mode, the outline detection can be triggered manually, without checks on sharpness.

triggerPictureCornerDetection

Starts the corner detection. This mode takes a high resolution image from the camera, and performs an outline detection on the full frame.
The results are returned in onPictureCornersDetected

Manually Triggering Corner Detection
// tell the scan view to take a picture, and perform corner detection on it
documentScanViewPlugin.triggerPictureCornerDetection();

Manual picture transformation

New in version 3.17.

This mode takes a picture and an outline (as a List<PointF>), and performs the perspective transformation on the document in the image.
A sharpness check is not performed in this mode.

transformPicture

Starts the picture transformation.
The results are returned in android_document_module_listener_on_picture_transformed, or android_document_module_listener_on_picture_transform_error respectively.

Manually Triggering Picture Transformation
// pass a full frame image and a list of 4 corners to the SDK for transformation
documentScanViewPlugin.transformPicture(fullImage, corners);

Parameters

  • fullImage
Type Description
AnylineImage The image on which to perform the transformation
  • corners
Type Description
List<PointF> The corners of the document which will be used to transform the image.
The order of the points must be upLeft, upRight, downRight, downLeft

Setting the maximum output resolution

New in version 3.19.

Starting with version 3.19, the maximum document output resolution can be set. The aspect ratio of the document will be preserved, and the largest possible size chosen, so that it is within the defined maximum output bounds.

Setting the maximum output resolution
// set the maximum output resolution to 600x600
scanView.setMaxDocumentOutputResolution(600.0, 600.0);

Implement a manual crop button

  1. Create a button which calls triggerPictureCornerDetection onClick
  2. Implement onPictureCornersDetected, which will be called after clicking the button with the current image and the detected outline.
  3. Implement further logic, if corners are null (e.g. User could create corners by input)
  4. Implement onPictureTransformed, which you can call in onPictureCornersDetected to transform the image with the given image and outline
Manuel Crop Example
/**
 * Example activity for the Anyline-Document-Detection-Module
 */
public class DocumentActivity implements CameraOpenListener {

    private ScanView documentScanView;
    private ImageView triggerManualButton;
    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        ...

        documentScanView = (ScanView) findViewById(getResources().getIdentifier("document_scan_view", "id", getPackageName()));
        triggerManualButton = (ImageView) findViewById(getResources().getIdentifier("manual_trigger_button", "id", getPackageName()));
            triggerManualButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    stopScanning(true);
                    documentScanViewPlugin.triggerPictureCornerDetection(); // triggers corner detection -> callback on onPictureCornersDetected
                }
            });

        ...

        // initialise anyline
	...

	//add the result listener
        documentScanViewPlugin.addScanResultListener(new DocumentScanResultListener() {

            ...

            @Override
            public void onPictureCornersDetected(AnylineImage fullFrame, List<PointF> corners) {

                // gets called after manual trigger button was pressed
                // if corners could not be detected -> the corners are the outline of the image
                documentScanViewPlugin.transformPicture(fullFrame, corners);

            }


            @Override
            public void onPictureTransformed(AnylineImage transformedImage) {

                // gets called after transformation is finished
                // if corners could not be detected -> the corners are the outline of the image

                // implement further logic, e.g. save the image to the Filesystem

                ...

                transformedImage.release(); // release the AnylineImage
            }

            ...

        }
    }
}

Enabling or disabling the reporting

The reporting of the (intermediate) results is turned off for this Module.

Start Scanning

After everything is initialised, you can start the scanning process, by calling start() on the scanView. It is advised to place this call in the onResume() lifecycle method of Android. The start() method on the scanView will internally call the start() on the ScanPlugin.

startScanning
	@Override
	protected void onResume() {
		super.onResume();
		//start the actual scanning
		documentScanView.start();
	}

Stop Scanning

To stop the scanning process, call stop() on the scanView.

To make sure the SDK is properly stopped upon leaving the Activity, make sure to place stop() and releaseCamera()/releaseCameraInBackground() in the onPause() lifecycle method of Android.

stopScanning
	@Override
	protected void onPause() {
		super.onPause();
		//stop the scanning
		documentScanView.stop();
		//release the camera (must be called in onPause, because there are situations where
		// it cannot be auto-detected that the camera should be released)
		documentScanView.releaseCameraInBackground();
	}

Enabling post processing

New in version 3.24.

By default the post processing is disabled. If post processing is enabled, Anyline will post process the detected document image. See postProcessingEnabled.

Set Document Post Processing
// set the document post processing
documentScanViewPlugin.setPostProcessingEnabled(true);

Hint

The setPostProcessingEnabled property is optional and will only used by Anyline if it is set before documentScanView.start();.

Full Example Activity

Document Module Example
package io.anyline.examples.document;

import android.app.ProgressDialog;
import android.graphics.Bitmap;
import android.graphics.PointF;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.ScaleAnimation;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.Toast;

import java.io.File;
import java.io.IOException;
import java.util.List;

import at.nineyards.anyline.camera.CameraController;
import at.nineyards.anyline.camera.CameraOpenListener;
import at.nineyards.anyline.models.AnylineImage;
import at.nineyards.anyline.modules.AnylineBaseModuleView;
import io.anyline.examples.R;
import io.anyline.examples.ScanActivity;
import io.anyline.examples.ScanModuleEnum;
import io.anyline.plugin.ScanResult;
import io.anyline.plugin.document.DocumentScanResultListener;
import io.anyline.plugin.document.DocumentScanViewPlugin;
import io.anyline.view.BaseScanViewConfig;
import io.anyline.view.ScanView;
import io.anyline.view.ScanViewPluginConfig;

/**
 * Example activity for the Anyline-Document-Detection-Module
 */
public class ScanDocumentActivity extends AppCompatActivity implements CameraOpenListener {

	private static final String TAG = ScanDocumentActivity.class.getSimpleName();
	private ScanView documentScanView;
	private Toast notificationToast;
	private ImageView imageViewResult;
	private ProgressDialog progressDialog;
	private ImageView imageViewFull;
	private List<PointF> lastOutline;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		getLayoutInflater().inflate(R.layout.document_scan_view, (ViewGroup) findViewById(R.id.scan_view_placeholder));

		imageViewFull = (ImageView) findViewById(R.id.image_result);

		imageViewResult = (ImageView) findViewById(R.id.full_image);

		documentScanView = (ScanView) findViewById(R.id.scan_view);
		// add a camera open listener that will be called when the camera is opened or an error occurred
		//  this is optional (if not set a RuntimeException will be thrown if an error occurs)
		documentScanView.setCameraOpenListener(this);
		//init the scanViewPluginConfig
		ScanViewPluginConfig documentScanViewPluginConfig = new ScanViewPluginConfig(getApplicationContext(), "document_view_config.json");
		//init the Document Scan View Plugin
		final DocumentScanViewPlugin documentScanViewPlugin = new DocumentScanViewPlugin(getApplicationContext(),getString(R.string.anyline_license_key), documentScanViewPluginConfig, "Document");
		//init the base config for camera and flash
		BaseScanViewConfig documentBaseScanViewconfig  = new BaseScanViewConfig(getApplicationContext(), "document_view_config.json");
		//set the base config to the scan view
		documentScanView.setScanViewConfig(documentBaseScanViewconfig);
		//set the scan view plugin to the scan view
		documentScanView.setScanViewPlugin(documentScanViewPlugin);

		//set cancel on result on true
		documentScanViewPlugin.getScanViewPluginConfig().setCancelOnResult(true);
		documentScanViewPlugin.cancelOnResult();

		documentScanViewPlugin.addScanResultListener(new DocumentScanResultListener() {

			@Override
			public void onPreviewProcessingSuccess(AnylineImage anylineImage) {

				notificationToast = Toast.makeText(ScanDocumentActivity.this, "Scanning full document. Please hold " +
						"still", Toast.LENGTH_LONG);
				notificationToast.show();

				showToast(getString(R.string.document_preview_success));
			}

			@Override
			public void onPreviewProcessingFailure(DocumentScanViewPlugin.DocumentError error) {
				// this is called on any error while processing the document image
				// Note: this is called every time an error occurs in a run, so that might be quite often
				// An error message should only be presented to the user after some time
				Log.d("Callback", "onPictureProcessingFailure: " + error.name());

			}

			@Override
			public void onPictureProcessingFailure(DocumentScanViewPlugin.DocumentError error) {

				// handle an error while processing the full picture here
				// the preview will be restarted automatically
				String text = getString(R.string.document_picture_error);
				switch (error) {
					case DOCUMENT_NOT_SHARP:
						text += getString(R.string.document_error_not_sharp);
						break;
					case DOCUMENT_SKEW_TOO_HIGH:
						text += getString(R.string.document_error_skew_too_high);
						break;
					case DOCUMENT_OUTLINE_NOT_FOUND:
						text += getString(R.string.document_error_outline_not_found);
						break;
					case GLARE_DETECTED:
						text += getString(R.string.document_error_glare_detected);
						break;
					case IMAGE_TOO_DARK:
						text += getString(R.string.document_error_too_dark);
						break;
				}

				showToast(text);
				if (progressDialog != null && progressDialog.isShowing()) {
					progressDialog.dismiss();
				}

				AnylineImage image = documentScanViewPlugin.getCurrentFullImage();

				if (image != null) {
					File outDir = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "error");
					outDir.mkdir();
					File outFile = new File(outDir, "" + System.currentTimeMillis() + error.name() + ".jpg");
					Log.d(TAG, "saved image to: " + outFile.getAbsolutePath());
					try {
						image.save(outFile, 100);
						Log.d(TAG, "error image saved to " + outFile.getAbsolutePath());
					} catch (IOException e) {
						e.printStackTrace();
					}
					image.release();
				}
			}

			@Override
			public void onTakePictureSuccess() {
				progressDialog = ProgressDialog.show(ScanDocumentActivity.this, "Processing", "Processing the picture" +
						". Please wait", true);

				if (notificationToast != null) {
					notificationToast.cancel();
				}

			}

			@Override
			public void onTakePictureError(Throwable error) {
				Log.d("Callback", "onTakePictureError: " + error.getMessage());
			}

			@Override
			public void onPictureTransformed(AnylineImage transformedImage) {
				Log.d("Callback", "onPictureTransformed");
			}

			@Override
			public void onPictureTransformError(DocumentScanViewPlugin.DocumentError error) {
				Log.d("Callback", "onPictureTransformError: " + error.name());
			}

			@Override
			public void onPictureCornersDetected(AnylineImage fullFrame, List corners) {
				Log.d("Callback", "onPictureCornersDetected");
			}


			@Override
			public boolean onDocumentOutlineDetected(List rect, boolean documentShapeAndBrightnessValid) {

				lastOutline = rect;
				return true;

			}

			@Override
			public void onResult(ScanResult result) {
				if (progressDialog != null && progressDialog.isShowing()) {
					progressDialog.dismiss();
				}

				// handle the result document images here
				if (progressDialog != null && progressDialog.isShowing()) {
					progressDialog.dismiss();
				}

				AnylineImage transformedImage = (AnylineImage) result.getResult();
				AnylineImage fullFrame = result.getFullImage();

				showToast(getString(R.string.document_picture_success));


				// show the picture on the screen
				displayPicture(transformedImage);

				/**
				 * IMPORTANT: cache provided frames here, and release them at the end of this onResult. Because
				 * keeping them in memory (e.g. setting the full frame to an ImageView)
				 * will result in a OutOfMemoryError soon. This error is reported in {@link #onTakePictureError
				 * (Throwable)}
				 *
				 * Use a DiskCache http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html#disk-cache
				 * for example
				 *
				 */
				File outDir = new File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "ok");
				outDir.mkdir();
				File outFile = new File(outDir, "" + System.currentTimeMillis() + ".jpg");
				try {
					transformedImage.save(outFile, 100);
					showToast("image saved to " + outFile.getAbsolutePath());
				} catch (IOException e) {
					e.printStackTrace();
				}

				// release the images
				transformedImage.release();
				fullFrame.release();
			}
		});
	}

	/**
	 * Performs an animation after the final image was successfully processed. This is just an example.
	 *
	 * @param transformedImage The transformed final image
	 */
	private void displayPicture(AnylineImage transformedImage) {

		imageViewResult.setImageBitmap(Bitmap.createScaledBitmap(transformedImage.getBitmap(), imageViewResult.getWidth(), imageViewResult.getHeight(), false));
		imageViewResult.setVisibility(View.VISIBLE);

		imageViewResult.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View view) {
				resetScanning();
			}
		});

	}


	@Override
	protected AnylineBaseModuleView getScanView() {
		return null;
	}


	private void showToast(String text) {
		try {
			notificationToast.setText(text);
		} catch (Exception e) {
			notificationToast = Toast.makeText(this, text, Toast.LENGTH_LONG);
		}
		notificationToast.show();
	}

	@Override
	protected void onResume() {
		super.onResume();
		//start the actual scanning
		documentScanView.start();
	}

	@Override
	protected void onPause() {
		super.onPause();
		//stop the scanning
		documentScanView.stop();
		//release the camera (must be called in onPause, because there are situations where
		// it cannot be auto-detected that the camera should be released)
		documentScanView.releaseCameraInBackground();
	}

	@Override
	public void onCameraOpened(CameraController cameraController, int width, int height) {
		//the camera is opened async and this is called when the opening is finished
		Log.d(TAG, "Camera opened successfully. Frame resolution " + width + " x " + height);
	}

	@Override
	public void onCameraError(Exception e) {
		//This is called if the camera could not be opened.
		// (e.g. If there is no camera or the permission is denied)
		// This is useful to present an alternative way to enter the required data if no camera exists.
		throw new RuntimeException(e);
	}

	@Override
	protected ScanModuleEnum.ScanModule getScanModule() {
		return ScanModuleEnum.ScanModule.DOCUMENT;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {

		if (item.getItemId() == android.R.id.home) {
			if(imageViewResult.getVisibility() == View.VISIBLE){
				resetScanning();
			}else {
				onBackPressed();
			}
			return true;
		}

		return false;
	}

	@Override
	public void onBackPressed() {
		if(imageViewResult.getVisibility() == View.VISIBLE){
			resetScanning();
		}else {
			super.onBackPressed();
			overridePendingTransition(R.anim.fade_in, R.anim.activity_close_translate);
		}
	}

	private void resetScanning(){
		if(imageViewResult.getVisibility() == View.VISIBLE){
			imageViewResult.setVisibility(View.GONE);
			if(documentScanView.getScanViewPlugin() != null && !documentScanView.getScanViewPlugin().isRunning() ){
				documentScanView.start();
			}
		}
	}
}