We have seen a lot of interest in our HTML5 SDK, and I recently had a customer ask about using our annotation framework on their own viewer or canvas. If you read my
recent post about using LEADTOOLS HTML5 annotations in Windows Metro, you will notice that one of the arguments to the constructor for the main
AnnAutomation
object is an instance of an object which implements
IAnnAutomationControl
. In that particular demo, we used the
ImageViewerAutomationControl
which was attached to a LEADTOOLS
ImageViewer
, but since the viewer is based on the HTML5 canvas it is possible to use other LEADTOOLS features such as annotations on
ANY canvas.
The
IAnnAutomationControl
interface is basically the glue between the drawing surface and the internal annotation framework. In order to accurately draw annotations, the framework needs to know about things like mouse and touch events, surface resolution, surface transforms, etc. The
ImageViewerAutomationControl
object is nothing more than a class which implements the
IAnnAutomationControl
interface, but it is specifically designed to be used with our image viewer control.
Back to the customer’s question, “If I have my own canvas that I am using to display image data but I still want to use the LEADTOOLS automated annotation framework, how do I attach the automation object to my canvas?” Even though you don’t use
ImageViewerAutomationControl
, it’s still pretty simple: (1) create your own class which implements
IAnnAutomationControl
and (2) pass that to the
AnnAutomation
constructor. I have attached the complete source code for this example at the end of this post but let’s go over a few key items in the interface first.
The most important pieces of information information for the annotation framework are the user initiated events occurring on the drawing surface. The easiest way to provide this information is to use the LEADTOOLS
InteractiveService
, which handles the task of interpreting mouse, touch, drag, pinch, and other types of events regardless of the type of browser or device we are running on. For example, the
IAnnAutomationControl
interface requires we implement
PointerDown
,
PointerUp
, and
PointerMove
events. To make things easy, I used the IntractiveService to notify me of all the various events that could be interpreted as
PointerDown
,
PointerUp
, and
PointerMove
so that I could pass them on to the underlying framework.
_service_DoubleTap: function Leadtools_Annotations_Automation_MyAutomationControl$_service_DoubleTap(sender, e) {
if (this.__automationDoubleClick != null) {
this.__automationDoubleClick(this,
Leadtools.Annotations.Core.AnnPointerEventArgs.create(
Leadtools.Annotations.Core.AnnMouseButton.left,
Leadtools.LeadPointD.get_empty())
);
}
},
_service_DragCompleted: function Leadtools_Annotations_Automation_MyAutomationControl$_service_DragCompleted(sender, e) {
if (this.__automationPointerUp != null) {
this.__automationPointerUp(
this, Leadtools.Annotations.Core.AnnPointerEventArgs.create(
Leadtools.Annotations.Core.AnnMouseButton.left,
this.get_automationContainer().get_mapper().pointToContainerCoordinates(
Leadtools.LeadPointD.create(
e.get_position().get_x() - e.get_change().get_x(),
e.get_position().get_y() - e.get_change().get_y()
)
)
)
);
}
},
_service_DragDelta: function Leadtools_Annotations_Automation_MyAutomationControl$_service_DragDelta(sender, e) {
if (this.__automationPointerMove != null) {
this.__automationPointerMove(this,
Leadtools.Annotations.Core.AnnPointerEventArgs.create(
Leadtools.Annotations.Core.AnnMouseButton.left,
this.get_automationContainer().get_mapper().pointToContainerCoordinates(
Leadtools.LeadPointD.create(
e.get_position().get_x() - e.get_change().get_x(),
e.get_position().get_y() - e.get_change().get_y())
)
)
);
}
},
_service_DragStarted: function Leadtools_Annotations_Automation_MyAutomationControl$_service_DragStarted(sender, e) {
if (!this._hasFocus) {
this.handleGotFocus(null);
}
if (this.__automationPointerDown != null) {
this.__automationPointerDown(this,
Leadtools.Annotations.Core.AnnPointerEventArgs.create(
Leadtools.Annotations.Core.AnnMouseButton.left,
this.get_automationContainer().get_mapper().pointToContainerCoordinates(
Leadtools.LeadPointD.create(
e.get_position().get_x() - e.get_change().get_x(),
e.get_position().get_y() - e.get_change().get_y())
)
)
);
}
},
Since the framework will also handle rendering the annotations on your drawing surface, it must also be informed about any transforms which have been applied to the underlying drawing surface (scale, rotate, translate). The same holds true about the resolution; for the purpose of simplicity, we use 96 DPI and an Identity matrix for this demo.
get_automationXResolution: function Leadtools_Annotations_Automation_MyAutomationControl$get_automationXResolution() {
return 96;
},
get_automationYResolution: function Leadtools_Annotations_Automation_MyAutomationControl$get_automationYResolution() {
return 96;
},
get_automationTransform: function Leadtools_Annotations_Automation_MyAutomationControl$get_automationTransform() {
//For this example, we will use the identity matrix.
//If you make any changes to the matrix of your background canvas,
//you should apply that same matrix here.
return Leadtools.LeadMatrix.get_identity();
},
The next step is to create a canvas for the annotations, which is done internally. It is important that the canvas on which the annotations are rendered and the background canvas are maintained as separate canvases so that manipulating one does not affect the other. For example, if we need to clear all of the annotations, we do not want to affect any objects rendered on the background surface. Rendering in the annotation framework is handled by the
AnnHtml5RenderingEngine
object. So in our class, we create an instance of the renderer and utilize it within the
AutomationInvalidate
function to render the annotations to our canvas.
//Create a canvas for the annotation container. We will render the annotations directly to this container
this._annotationCanvas = document.createElement('canvas');
this._annotationCanvas.width = backgroundCanvas.width;
this._annotationCanvas.height = backgroundCanvas.height;
this._annotationCanvas.id = backgroundCanvas.id + '_annotationCanvas';
this._annotationCanvas.style.pixelLeft = 0;
this._annotationCanvas.style.pixelTop = 0;
this._annotationCanvas.style.marginTop = '0px';
this._annotationCanvas.style.marginRight = 'auto';
this._annotationCanvas.style.marginBottom = '0px';
this._annotationCanvas.style.marginLeft = 'auto';
this._annotationCanvas.style.position = 'absolute';
this._annotationCanvas.style.zIndex = 100;
//Add our annotation container to the parent of the background canvas
backgroundCanvas.parentNode.appendChild(this._annotationCanvas);
//Get the context for our canvas
this._context = this._annotationCanvas.getContext('2d');
//The automation object is attaching. We will create our renderer here.
automationAttach: function Leadtools_Annotations_Automation_MyAutomationControl$automationAttach(container) {
this._container = container;
if (this._annotationCanvas != null) {
if (this._context != null) {
//Create a new rendererer that will render to our annotation canvas
this._engine = new Leadtools.Annotations.Rendering.AnnHtml5RenderingEngine(
this._container, this._context, false);
if (this._engine != null) {
this._engine.render(Leadtools.LeadRectD.create(0, 0,
this._annotationCanvas.width, this._annotationCanvas.height), true);
}
}
}
},
//Render the annotations to our canvas
automationInvalidate: function Leadtools_Annotations_Automation_MyAutomationControl$automationInvalidate(rc) {
if (this._engine != null) {
this._engine.render(Leadtools.LeadRectD.create(0, 0,
this._annotationCanvas.width, this._annotationCanvas.height), true);
}
},
Even though you are using a non-LEADTOOLS object for the canvas, it is still possible to permanently realize (burn in) the annotation to the drawing surface by implementing
getImageData
and
putImageData
. This is very important if you plan on using the redaction object, which is often used to hide confidential information in a document. It is also possible to restore the data which was overwritten when the redaction was realized. In order to do this, your class must handle replacing the image data on your drawing surface for realizing, and returning the image data under the redact object for restoring.
getImageData: function Leadtools_Annotations_Automation_MyAutomationControl$getImageData(rc) {
if (rc.get_isEmpty()) {
return null;
}
var context = this._backgroundCanvas.getContext('2d');
return context.getImageData(parseInt(rc.get_left()), parseInt(rc.get_top()),
parseInt(rc.get_width()), parseInt(rc.get_height()));
},
putImageData: function Leadtools_Annotations_Automation_MyAutomationControl$putImageData(data, position) {
if (position.get_isEmpty() || data == null) {
return;
}
var context = this._backgroundCanvas.getContext('2d');
var pixelData = data.data;
context.putImageData(data, parseInt(position.get_x()), parseInt(position.get_y()));
}
You can download the complete source for this demo
here. The class we discussed in the article can be found in ‘Scripts\MyAutomationControl.js’. If you have any questions about this article, feel free to contact us at support@leadtools.com.
Thanks,
Otis Goodwin