Document Plugin


Landscape mode

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

Examples Source Code

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

Plugin Description

Simultaneous Barcode Scanning

Starting from SDK 3.8 Anyline supports simultaneous barcode scanning for any plugin. Additional Information on how to implement this on iOS can be found at Simultaneous Barcode Scanning

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 plugin 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 Plugins in iOS.

Add the Anyline Document Plugins to your UIViewController

The first step is to add the three main components of every Anyline use case: the Scan Plugin (handling the scanning part), the Scan View Plugin (handling the UI) and the Scan View (handling camera, flash and overall communication between Anyline components).

Add the Anyline Document Plugins
// The Anyline plugins used for Document
@property (nonatomic, strong) ALDocumentScanViewPlugin *documentScanViewPlugin;
@property (nonatomic, strong) ALDocumentScanPlugin *documentScanPlugin;
@property (nullable, nonatomic, strong) ALScanView *scanView;

Anyline ScanPlugin

Initialise the Scan Plugin

You have to initialise the plugin with your Anyline License Key Generation, an ID (String) and the delegate inside viewDidLoad.

Initialise the Scan Plugin
    NSError *error = nil;
    self.documentScanPlugin = [[ALDocumentScanPlugin alloc] initWithPluginID:@"DOCUMENT" licenseKey:kDocumentScanLicenseKey delegate:self error:&error];;
    NSAssert(self.documentScanPlugin, @"Setup Error: %@", error.debugDescription);

Anyline ScanViewPlugin

Initialise the Scan View Plugin

After initialising the scanPlugin, the next step is to create the scanViewPlugin with the scanPlugin you just created. The ScanViewPlugin will handle and display the UI used for scanning.

Initialise the Scan View Plugin
    self.documentScanViewPlugin = [[ALDocumentScanViewPlugin alloc] initWithScanPlugin:self.documentScanPlugin];
    NSAssert(self.documentScanViewPlugin, @"Setup Error: %@", error.debugDescription);

Set the ScanViewPluginConfig

The View Configuration configurates the look and feel of the scanning process. You can set it the following way:

Set the ScanViewPluginConfig
NSString *confPath = [[NSBundle mainBundle] pathForResource:@"document_capture_config" ofType:@"json"];
ALScanViewPluginConfig *scanViewPluginConfig = [ALScanViewPluginConfig configurationFromJsonFilePath:confPath];

self.documentScanViewPlugin = [[ALDocumentScanViewPlugin alloc] initWithScanPlugin:self.documentScanPlugin
                                                          scanViewPluginConfig:scanViewPluginConfig];

Anyline ScanView

Initialise the Scan View

The last Anyline Object you have to create is the so called scanView, it will handle the camera, the flash and manage the previously created ScanViewPlugin and ScanPlugin. You need to instantiate the scanView with the previous created scanViewPlugin and the frame it should occupy. Usually, that’s the bounds of the screen. This example also tells the scanView to use the default configuration for the Flash and the Camera.

Initialise the Scan View
    self.scanView = [[ALScanView alloc] initWithFrame:frame
                                       scanViewPlugin:self.documentScanViewPlugin
                                         cameraConfig:[ALCameraConfig defaultDocumentCameraConfig]
                                    flashButtonConfig:[ALFlashButtonConfig defaultFlashConfig]];

Adding the Scan View to the View Hierarchy and start the Camera Feed

To add the scanView to your view hierarchy add the following in viewDidLoad:

Adding the view and starting the camera
    // After setup is complete we add the module to the view of this view controller
    [self.view addSubview:self.scanView];
    // Start Camera:
    [self.scanView startCamera];

Final steps to configure and use the Document Plugins

Setting the desired document formats

Starting with version 3.8, you can set valid document formats where other formats will not be considered valid by the SDK.

You have to tell the plugin which documents to recognize. This is done by setting an array of floats wrapped in an NSNumber. Predefined ratios can be found in ALDocumentScanPlugin.h.

Setting Document Ratios
    [self.documentScanPlugin setDocumentRatios:@[@(ALDocumentRatioLetterPortrait), @(ALDocumentRatioLetterLandscape)]];

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

Setting Document Ratio Deviation
    self.documentScanPlugin.maxDocumentRatioDeviation = @(0.15);

Default

The default deviation is set to 0.15.

Setting the maximum output resolution

New in version 3.19.

Starting with version 3.19, the maximum output resolution of the transformed output image can be limited with the module property maxOutputResolution.

Hint

In order to change the maxOutputResolution, the scan process has to be stopped.

Enabling post processing

New in version 3.24.0.

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

Enable post processing
self.documentScanPlugin.postProcessingEnabled = true;
//or
[self.documentScanPlugin setPostProcessingEnabled:YES];

Hint

The postProcessingEnabled property is optional and will only used by Anyline if it is set before [documentScanViewPlugin startAndReturnError:].

The Document Delegate

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

In case of the Document Module, the protocol is called ALDocumentScanPluginDelegate. This section describes the ALDocumentScanPluginDelegate callbacks in detail.

(void)anylineDocumentScanPlugin:hasResult:fullImage:documentCorners:

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 callback returns the transformedImage, as well as the fullFrame, which is the picture that was last processed by the Anyline SDK and lead to the result.

Getting a result
/*
 This is the main delegate method Anyline uses to report its scanned codes
 */
- (void)anylineDocumentScanPlugin:(ALDocumentScanPlugin *)anylineDocumentScanPlugin
                        hasResult:(UIImage *)transformedImage
                        fullImage:(UIImage *)fullFrame
                  documentCorners:(ALSquare *)corners {
    UIViewController *viewController = [[UIViewController alloc] init];
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:viewController.view.bounds];
    imageView.center = CGPointMake(imageView.center.x, imageView.center.y + 30);
    imageView.contentMode = UIViewContentModeScaleAspectFit;
    imageView.image = transformedImage;
    [viewController.view addSubview:imageView];
    [self.navigationController pushViewController:viewController animated:YES];
}
  • transformedImage
Type Description
UIImage An image of the detected document, cropped and perspectively transformed
  • fullFrame
Type Description
UIImage The full picture that was processed and lead to the result
  • documentCorners
Type Description
ALSquare The corners of the document that was found

(void)anylineDocumentScanPlugin:reportsPreviewResult:

Optional

This Protocol method is optional

This method is called if the preview scan detected a sharp and correctly placed document.

After this callback, a full frame scan of the document starts automatically.

  • image
Type Description
UIImage The image of the successful preview. The image is cropped to the document bounds but not perspectively transformed

(void)anylineDocumentScanPlugin:reportsPreviewProcessingFailure:

Optional

This Protocol method is optional

This method is called if the preview run failed on an image. The error is provided, and the next run is started automatically.

Preview Failure
                           error:(NSError * *)error {
    [self.scanViewPlugin transformImageWithSquare:square image:image error:&error];
}

- (BOOL)transformALImageWithSquare:(ALSquare *)square
                             image:(ALImage *)image
                             error:(NSError * *)error {
  • error
Type Description
ALDocumentError The error that occured during the preview

(void)anylineDocumentScanPlugin:reportsPictureProcessingFailure:

Optional

This Protocol method is optional

This method is called if the run on the full frame was unsuccessful.

The scanning process automatically starts again with a preview scan.

Picture Failure
 *  In any case call [AnylineDocumentModuleView cancelScanningAndReturnError:] before using this method.
 *
 *  Return value indicating the success / failure of the call.
 *
 *
 */
- (BOOL)transformImageWithSquare:(ALSquare *)square
  • error
Type Description
ALDocumentError The error that occured during the full frame image processing

(void)anylineDocumentScanPlugin:detectedPictureCorners:inImage:

This delegate methode will report the result after triggerPictureCornerDetectionAndReturnError: is called and a manual picture was taken and processed.

Getting a manual result
}

/**
 *  A manual picture will be taken. Anyline tries to detected any document corners and will report the result
 *  with the delegate method anylineDocumentModuleView:detectedPictureCorners:inImage:
 */
- (BOOL)triggerPictureCornerDetectionAndReturnError:(NSError **)error {
    [self.documentModuleView triggerPictureCornerDetectionAndReturnError:&error];
}
  • inImage
Type Description
UIImage An image of the manual taken picture
  • detectedPictureCorners
Type Description
ALSquare The corners of the document that was found (can be nil)

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
    [self.documentScanPlugin setDocumentRatios:@[@(ALDocumentRatioLetterPortrait), @(ALDocumentRatioLetterLandscape)]];

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

Enabling or Disabling the Reporting

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

Start Scanning

Hint

Use [scanView startCamera] within viewDidload before starting Anyline.

After everything is initialised, you can start the scanning process, by calling startAndReturnError: on the plugin.

It is advised to place this call in the viewDidAppear: lifecycle method of the UIViewController.

startScanning
    /*
     This is the place where we tell Anyline to start receiving and displaying images from the camera.
     Success/error tells us if everything went fine.
     */
    NSError *error;
    BOOL success = [self.documentScanViewPlugin startAndReturnError:&error];
    if( !success ) {
        // Something went wrong. The error object contains the error description
        [[[UIAlertView alloc] initWithTitle:@"Start Scanning Error"
                                    message:error.debugDescription
                                   delegate:self
                          cancelButtonTitle:@"OK"
                          otherButtonTitles:nil] show];
    }

Stop Scanning

To stop the scanning process, call stopAndReturnError: on the plugin.

To make sure the SDK is properly stopped upon leaving the Activity, make sure to place stopAndReturnError: in the viewWillDisappear: lifecycle method of the UIViewController.

cancelScanning
/*
 Cancel scanning to allow the module to clean up
 */
- (void)viewWillDisappear:(BOOL)animated {
    [self.documentScanViewPlugin stopAndReturnError:nil];
}

Trigger Manual Corner Detection

triggerPictureCornerDetectionAndReturnError: will request the manual corner detection. Anyline will process a fullframe image and will try to detect document corners. The result will be reported by the delegate method anylineDocumentModuleView:detectedPictureCorners:inImage:.

Trigger Manual Corner Detection
/**
 *  A manual picture will be taken. Anyline tries to detected any document corners and will report the result
 *  with the delegate method anylineDocumentModuleView:detectedPictureCorners:inImage:
 */
- (BOOL)triggerPictureCornerDetectionAndReturnError:(NSError **)error {
    [self.documentModuleView triggerPictureCornerDetectionAndReturnError:&error];
}

Implement a manual crop button

  1. Create a button which calls triggerPictureCornerDetectionAndReturnError as action
  2. Implement detectedPictureCorners, which will be called after clicking the button with the current image and the detected outline.
  3. Implement further logic, if corners are nil (e.g. User could create corners by input)
  4. Call transformImageWithSquare with the image and corners
  5. Implement hasResult, which is the delegate of transformImageWithSquare with the result
Manuel Crop Example
@implementation AnylineDocumentScanViewController
// This is not a full example. This only shows the necessary parts for a manual trigger button
// The default implementation for the Anyline Dcoument Plugin is still required.

...

- (void)viewDidLoad {
    [super viewDidLoad];
    ...

    // (1.1): Create some button
    self.triggerCameraButton = [[ALManualTriggerButton alloc] initWithFrame:CGRectMake(self.scanView.center.x-25, self.scanView.bounds.size.height-56, 52, 52)
                                                                  tintColor:[UIColor blueColor];

    // (1.2): Target will be a method to trigger Anyline to take a manual picture run
    [self.triggerCameraButton addTarget:self action:@selector(onManualTrigger:) forControlEvents:UIControlEventTouchUpInside];
    
    // (1.3): Add the button to the view
    [self.view addSubview:self.triggerCameraButton];

    ...
}


- (IBAction)onManualTrigger:(id)sender {
    NSError *error = nil;
    // (1.4): First Stop Anline
    [self.documentScanViewPlugin stopAndReturnError:&error];
    // (1.5): Trigger a manual picture run
    [self.documentScanPlugin triggerPictureCornerDetectionAndReturnError:&error];

   ...
}



#pragma mark - ALDocumentScanPluginDelegate

/*
 This Delegate will return a result, after documentScanPlugin triggerPictureCornerDetectionAndReturnError: was called.
 (2): Implement this delegate
 */
- (void)anylineDocumentScanPlugin:(AnylineDocumentModuleView *)anylineDocumentModuleView
           detectedPictureCorners:(ALSquare *)corners
                          inImage:(UIImage *)image {

        // (3): If corners is nil, implement logic for setting own corners (e.g. by User input)
        
        ...
                              
        // (4): Transform and Crop image with ALSquare
        [self.documentScanPlugin transformImageWithSquare:corners
                                                    image:image
                                                    error:nil];
}


- (void)anylineDocumentModuleView:(AnylineDocumentModuleView *)anylineDocumentModuleView
                        hasResult:(UIImage *)transformedImage
                        fullImage:(UIImage *)fullFrame
                  documentCorners:(ALSquare *)corners {

    // (5): Transformed and cropped output image can be handled her.
}

Transform Image With Square

To crop and transform an image you can use transformImageWithSquare:image:error: or transformALImageWithSquare:image:error:. Crops an arbitrary rectangle (e.g. trapezoid) of the input image and perspectively transforms it to a rectangle (e.g. square). After the transformation is complete the result delegate anylineDocumentScanPlugin:hasResult:fullImage:documentCorners will be triggered.

Transform Image With Square
/**
 *  Crops an arbitrary rectangle (e.g. trapezoid) of the input image and perspectively transforms it to a rectangle (e.g. square).
 *  After the transformation is complete the result delegate anylineDocumentScanPlugin:hasResult:fullImage:documentCorners will be triggered.
 *  In any case call [AnylineDocumentModuleView cancelScanningAndReturnError:] before using this method.
 *
 *  Return value indicating the success / failure of the call.
 *
 *
 */
- (BOOL)transformImageWithSquare:(ALSquare *)square
                           image:(UIImage *)image
                           error:(NSError * *)error {
    [self.scanViewPlugin transformImageWithSquare:square image:image error:&error];
}

- (BOOL)transformALImageWithSquare:(ALSquare *)square
                             image:(ALImage *)image
                             error:(NSError * *)error {
    [self.scanViewPlugin transformALImageWithSquare:square image:image error:&error];
}

Full Example

Document Module Example
NSString * const kDocumentScanLicenseKey = kDemoAppLicenseKey;

@class AnylineDocumentModuleView;

@interface ALDocumentScanViewController () <ALDocumentScanPluginDelegate, ALInfoDelegate, ALDocumentInfoDelegate>

// The Anyline plugins used for Document
@property (nonatomic, strong) ALDocumentScanViewPlugin *documentScanViewPlugin;
@property (nonatomic, strong) ALDocumentScanPlugin *documentScanPlugin;
@property (nullable, nonatomic, strong) ALScanView *scanView;

@property (nonatomic, strong) ALRoundedView *roundedView;
@property (nonatomic, assign) NSInteger showingLabel;

@end

@implementation ALDocumentScanViewController

/*
 We will do our main setup in viewDidLoad. Its called once the view controller is getting ready to be displayed.
 */
- (void)viewDidLoad {
    [super viewDidLoad];
    // Set the background color to black to have a nicer transition
    self.view.backgroundColor = [UIColor blackColor];
   
    [super viewDidLoad];
    // Set the background color to black to have a nicer transition
    self.view.backgroundColor = [UIColor blackColor];
    self.title = NSLocalizedString(@"Scan Document", @"Scan Document");
    // Initializing the module. Its a UIView subclass. We set the frame to fill the whole screen
    CGRect frame = [[UIScreen mainScreen] applicationFrame];
    frame = CGRectMake(frame.origin.x, frame.origin.y + self.navigationController.navigationBar.frame.size.height, frame.size.width, frame.size.height - self.navigationController.navigationBar.frame.size.height);

    NSError *error = nil;
    self.documentScanPlugin = [[ALDocumentScanPlugin alloc] initWithPluginID:@"DOCUMENT" licenseKey:kDocumentScanLicenseKey delegate:self error:&error];;
    NSAssert(self.documentScanPlugin, @"Setup Error: %@", error.debugDescription);

    [self.documentScanPlugin addInfoDelegate:self];
    
    [self.documentScanPlugin setDocumentRatios:@[@(ALDocumentRatioLetterPortrait), @(ALDocumentRatioLetterLandscape)]];
    self.documentScanPlugin.maxDocumentRatioDeviation = @(0.15);
    
    self.documentScanViewPlugin = [[ALDocumentScanViewPlugin alloc] initWithScanPlugin:self.documentScanPlugin];
    NSAssert(self.documentScanViewPlugin, @"Setup Error: %@", error.debugDescription);
    
    self.scanView = [[ALScanView alloc] initWithFrame:frame
                                       scanViewPlugin:self.documentScanViewPlugin
                                         cameraConfig:[ALCameraConfig defaultDocumentCameraConfig]
                                    flashButtonConfig:[ALFlashButtonConfig defaultFlashConfig]];
    
    // Enable PostProcessing
    [self.documentScanPlugin setPostProcessingEnabled:YES];
    // Disable justDetectCornersIfPossible
    self.documentScanPlugin.justDetectCornersIfPossible = NO;
    
    [self.documentScanPlugin enableReporting:[NSUserDefaults AL_reportingEnabled]];
    self.controllerType = ALScanHistoryDocument;
    self.documentScanViewPlugin.translatesAutoresizingMaskIntoConstraints = NO;
    
    // After setup is complete we add the module to the view of this view controller
    [self.view addSubview:self.scanView];
    // Start Camera:
    [self.scanView startCamera];
    
    [self.view sendSubviewToBack:self.scanView];
    [self startListeningForMotion];

    // This view notifies the user of any problems that occur while he is scanning
    self.roundedView = [[ALRoundedView alloc] initWithFrame:CGRectMake(20, 115, self.view.bounds.size.width - 40, 30)];
    self.roundedView.fillColor = [UIColor colorWithRed:98.0/255.0 green:39.0/255.0 blue:232.0/255.0 alpha:0.6];
    self.roundedView.textLabel.text = @"";
    self.roundedView.alpha = 0;
    [self.view addSubview:self.roundedView];
}

/*
 This method will be called once the view controller and its subviews have appeared on screen
 */
-(void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    /*
     This is the place where we tell Anyline to start receiving and displaying images from the camera.
     Success/error tells us if everything went fine.
     */
    NSError *error;
    BOOL success = [self.documentScanViewPlugin startAndReturnError:&error];
    if( !success ) {
        // Something went wrong. The error object contains the error description
        [[[UIAlertView alloc] initWithTitle:@"Start Scanning Error"
                                    message:error.debugDescription
                                   delegate:self
                          cancelButtonTitle:@"OK"
                          otherButtonTitles:nil] show];
    }
    
    //Update Position of Warning Indicator
    [self updateWarningPosition:
     self.documentScanViewPlugin.cutoutRect.origin.y +
     self.documentScanViewPlugin.cutoutRect.size.height +
     self.documentScanViewPlugin.frame.origin.y - 100];
}

/*
 Cancel scanning to allow the module to clean up
 */
- (void)viewWillDisappear:(BOOL)animated {
    [self.documentScanViewPlugin stopAndReturnError:nil];
}

#pragma mark -- AnylineDocumentModuleDelegate

/*
 This is the main delegate method Anyline uses to report its scanned codes
 */
- (void)anylineDocumentScanPlugin:(ALDocumentScanPlugin *)anylineDocumentScanPlugin
                        hasResult:(UIImage *)transformedImage
                        fullImage:(UIImage *)fullFrame
                  documentCorners:(ALSquare *)corners {
    UIViewController *viewController = [[UIViewController alloc] init];
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:viewController.view.bounds];
    imageView.center = CGPointMake(imageView.center.x, imageView.center.y + 30);
    imageView.contentMode = UIViewContentModeScaleAspectFit;
    imageView.image = transformedImage;
    [viewController.view addSubview:imageView];
    [self.navigationController pushViewController:viewController animated:YES];
}

/*
 This method receives errors that occured during the scan.
 */
- (void)anylineDocumentScanPlugin:(ALDocumentScanPlugin *)anylineDocumentScanPlugin
  reportsPictureProcessingFailure:(ALDocumentError)error {
    [self showUserLabel:error];
}

/*
 This method receives errors that occured during the scan.
 */
- (void)anylineDocumentScanPlugin:(ALDocumentScanPlugin *)anylineDocumentScanPlugin
  reportsPreviewProcessingFailure:(ALDocumentError)error {
    [self showUserLabel:error];
}

/**
 *  Crops an arbitrary rectangle (e.g. trapezoid) of the input image and perspectively transforms it to a rectangle (e.g. square).
 *  After the transformation is complete the result delegate anylineDocumentScanPlugin:hasResult:fullImage:documentCorners will be triggered.
 *  In any case call [AnylineDocumentModuleView cancelScanningAndReturnError:] before using this method.
 *
 *  Return value indicating the success / failure of the call.
 *
 *
 */
- (BOOL)transformImageWithSquare:(ALSquare *)square
                           image:(UIImage *)image
                           error:(NSError * *)error {
    [self.scanViewPlugin transformImageWithSquare:square image:image error:&error];
}

- (BOOL)transformALImageWithSquare:(ALSquare *)square
                             image:(ALImage *)image
                             error:(NSError * *)error {
    [self.scanViewPlugin transformALImageWithSquare:square image:image error:&error];
}

/**
 *  A manual picture will be taken. Anyline tries to detected any document corners and will report the result
 *  with the delegate method anylineDocumentModuleView:detectedPictureCorners:inImage:
 */
- (BOOL)triggerPictureCornerDetectionAndReturnError:(NSError **)error {
    [self.documentModuleView triggerPictureCornerDetectionAndReturnError:&error];
}

#pragma mark -- Helper Methods

/*
 Shows a little round label at the bottom of the screen to inform the user what happended
 */
- (void)showUserLabel:(ALDocumentError)error {
    NSString *helpString = nil;
    switch (error) {
        case ALDocumentErrorNotSharp:
            helpString = @"Document not Sharp";
            break;
        case ALDocumentErrorSkewTooHigh:
            helpString = @"Wrong Perspective";
            break;
        case ALDocumentErrorImageTooDark:
            helpString = @"Too Dark";
            break;
        case ALDocumentErrorShakeDetected:
            helpString = @"Shake";
            break;
        default:
            break;
    }
    
    //Display Error to indicate what the User should do.
}

@end