You can easily create your own annotation objects with the LEADTOOLS Annotations framework. This tutorial shows how to create two new annotations objects and how to write the required designer for them so the objects can be plugged into the LEADTOOLS Annotations Automation framework.
Start by creating a new HTML file with the following markup:
<!DOCTYPE html><html xmlns="http://www.w3.org/1999/xhtml"><head><title>Implementing User Defined Objects With LEADTOOLS Annotations</title><meta http-equiv="X-UA-Compatible" content="IE=9" /><meta http-equiv="content-type" content="text/html; charset=utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0" /><style> #imageViewerDiv { border: 1px solid #000000; width: 600px; height: 400px; background-color: #7F7F7F; } </style></head><body onload="pageLoad()"><div id="imageViewerDiv"></div></body></html>
Add script tags, inside the page's head tag, for the LEADTOOLS JavaScript libraries. NOTE: In this example, the LEADTOOLS .js files are located in a lib subfolder:
<script type="text/javascript" src="lib/Leadtools.js"></script><script type="text/javascript" src="lib/Leadtools.Controls.js"></script><script type="text/javascript" src="lib/Leadtools.Annotations.Engine.js"></script><script type="text/javascript" src="lib/Leadtools.Annotations.Rendering.JavaScript.js"></script><script type="text/javascript" src="lib/Leadtools.Annotations.Designers.js"></script><script type="text/javascript" src="lib/Leadtools.Annotations.Automation.js"></script><script type="text/javascript" src="lib/Leadtools.Demos.js"></script><script type="text/javascript" src="lib/Leadtools.Demos.Annotations.js"></script>
The object being created is a simple object, a triangle. This object will have three points for the end points of the triangle and it will use a stroke for the triangle edges and a fill for the interior.
We need to create a custom annotation object type. Since a triangle is a polygon with only three points, we can derive one from theAnnPolylineObject class. Add the following code to your script tag:
////////////////////////////////////////////////////////////////////////////////// AnnTriangleObjectAnnTriangleObject = function AnnTriangleObject() {AnnTriangleObject.initializeBase(this);this.isClosed = true; // Triangle is a closed figurethis.setId(-99); // Set the object ID}AnnTriangleObject.prototype = {create: function AnnTriangleObject$create() {// Define the custom annotation object (the triangle is a polyline with only 3 points)return new AnnTriangleObject();}}AnnTriangleObject.registerClass('AnnTriangleObject', lt.Annotations.Engine.AnnPolylineObject);
Since there are no existing draw and edit designers that can be used with this AnnTriangleObject class, one needs to be created. First, create the draw designer class, derived fromAnnDrawDesigner. Add the following code to your script tag, after the AnnTriangleObject code:
////////////////////////////////////////////////////////////////////////////////// AnnTriangleDrawDesignerAnnTriangleDrawDesigner = function AnnTriangleDrawDesigner(automationControl, container, annPolyineObject) {AnnTriangleDrawDesigner.initializeBase(this, [automationControl, container, annPolyineObject]);}AnnTriangleDrawDesigner.prototype = {// Override the onPointerDown method and add 3 points for the triangleonPointerDown: function AnnTriangleDrawDesigner$onPointerDown(sender, e) {var handled = AnnTriangleDrawDesigner.callBaseMethod(this, 'onPointerDown', [sender, e]);if (this.targetObject.points.count < 3)if (e.button === lt.Annotations.Engine.AnnMouseButton.left) {if (this.startWorking())this.targetObject.points.add(e.location);handled = true;}return handled;},// Override the onPointerUp method and end the drawing when the 3 points are drawnonPointerUp: function AnnTriangleDrawDesigner$onPointerUp(sender, e) {var handled = AnnTriangleDrawDesigner.callBaseMethod(this, 'onPointerUp', [sender, e]);handled = true;if (this.targetObject.points.count >= 3)this.endWorking();this.invalidate(lt.LeadRectD.empty);return handled;}}AnnTriangleDrawDesigner.registerClass('AnnTriangleDrawDesigner', lt.Annotations.Designers.AnnDrawDesigner);
Next, create the edit designer class, derived from the AnnPolylineEditDesigner class. Add the following code to your script, after the AnnTriangleDrawDesigner code. Note that this object is designed to work with the LEADTOOLSAnnHtml5RenderingEngine rendering engine. The code to create a rendering engine to work with AnnSvgRenderingEngine is implemented at the end of this tutorial.
////////////////////////////////////////////////////////////////////////////////// AnnTriangleEditDesigner// No customization of this class needed.AnnTriangleEditDesigner = function AnnTriangleEditDesigner(automationControl, container, annPolylineObject) {AnnTriangleEditDesigner.initializeBase(this, [automationControl, container, annPolylineObject]);}AnnTriangleEditDesigner.registerClass('AnnTriangleEditDesigner', lt.Annotations.Designers.AnnPolylineEditDesigner);
Now create a custom renderer for the AnnTriangleObject, derived from the AnnPolylineEditDesigner class. Add the following code to your script, after the AnnTriangleDrawDesigner code:
////////////////////////////////////////////////////////////////////////////////// AnnTriangleRenderer (HTML5 Canvas version)AnnTriangleRenderer = function AnnTriangleRenderer() {AnnTriangleRenderer.initializeBase(this);}AnnTriangleRenderer.prototype = {// Override the Render method in order to draw the 3 points as the user creates them.render: function AnnTriangleRenderer$render(mapper, annObject) {AnnTriangleRenderer.callBaseMethod(this, 'render', [mapper, annObject]);// If drawing has finished, allow the base class AnnPolylineObjectRenderer to handle the jobif (annObject.points.count === 3)return;var engine = Type.safeCast(this.renderingEngine, lt.Annotations.Rendering.AnnHtml5RenderingEngine);if (engine != null) {var context = engine.context;if (context != null) {context.save();var points = mapper.pointsFromContainerCoordinates(annObject.points.toArray(), annObject.fixedStateOperations);lt.Annotations.Rendering.AnnHtml5RenderingEngine.setStroke(context, lt.Annotations.Engine.AnnStroke.create(lt.Annotations.Engine.AnnSolidColorBrush.create('green'), lt.LeadLengthD.create(1)));context.beginPath();for (var x = 0; x < points.length; x++) {var point = points[x];if (!point.isEmpty) {var rect = lt.LeadRectD.create(point.x - 10, point.y - 10, 20, 20);lt.Annotations.Rendering.AnnHtml5RenderingEngine.drawEllipse(context, rect);}}context.closePath();context.stroke();context.restore();}}}}AnnTriangleRenderer.registerClass('AnnTriangleRenderer', lt.Annotations.Rendering.AnnPolylineObjectRenderer);
The final step is to write the code that glues the LEADTOOLS ImageViewer and the automated annotation objects together.
// Shared variablevar automation;// Create the custom automation object and hook the designersfunction createTriangleAutomationObject(annObject) {// Create the automation objectvar automationObj = new lt.Annotations.Automation.AnnAutomationObject();automationObj.id = annObject.id;automationObj.name = "Triangle";automationObj.drawDesignerType = AnnTriangleDrawDesigner; // Hook the custom draw designerautomationObj.editDesignerType = AnnTriangleEditDesigner; // Hook the custom edit designerautomationObj.runDesignerType = lt.Annotations.Designers.AnnRunDesigner;// Get the current rendering enginevar renderingEngine = annAutomation.manager.renderingEngine;// Set up the thumbsvar annTriangleRenderer = new AnnTriangleRenderer();var annPolylineRenderer = renderingEngine.renderers[lt.Annotations.Engine.AnnObject.polylineObjectId];annTriangleRenderer.locationsThumbStyle = annPolylineRenderer.locationsThumbStyle;annTriangleRenderer.rotateCenterThumbStyle = annPolylineRenderer.rotateCenterThumbStyle;annTriangleRenderer.rotateGripperThumbStyle = annPolylineRenderer.rotateGripperThumbStyle;renderingEngine.renderers[annObject.id] = annTriangleRenderer; // Hook the custom rendererautomationObj.objectTemplate = annObject;return automationObj;}function setupTriangleAnnotation() {// Create the triangle objectvar triangle = new AnnTriangleObject();triangle.fill = lt.Annotations.Engine.AnnSolidColorBrush.create("blue");// Create a user defined automation objectvar automationObj = createTriangleAutomationObject(triangle);annAutomation.manager.objects.add(automationObj);}function pageLoad() {// Create the viewervar createOptions = new lt.Controls.ImageViewerCreateOptions(document.getElementById('imageViewerDiv'));var imageViewer = new lt.Controls.ImageViewer(createOptions);// Watch item changed event to set container size and the current object id when the image gets loadedimageViewer.itemChanged.add(imageViewer_ItemChanged);// Set the image URL - load using browser supportimageViewer.imageUrl = "https://demo.leadtools.com/images/jpeg/cactus.jpg";// Fit the image within the viewerimageViewer.zoom(lt.Controls.ControlSizeMode.fit, 1.0, imageViewer.defaultZoomOrigin);// Create the annotation automation control and attach an image viewer to itvar imageViewerAutomationControl = new lt.Demos.Annotations.ImageViewerAutomationControl();imageViewerAutomationControl.imageViewer = imageViewer;// Create the automation manager, and default objectsvar manager = new lt.Annotations.Automation.AnnAutomationManager();manager.createDefaultObjects();// Create an annotations rendering engine and bind it to the automation manager// In this example, an HTML5 5 rendering engine is used.// Replace this with AnnSvgRenderingEngine to use the SVG engine.var renderingEngine = new lt.Annotations.Rendering.AnnHtml5RenderingEngine();manager.renderingEngine = renderingEngine;// Create an automation interactive mode instance and bind the automation control to itvar automationInteractiveMode = new lt.Demos.Annotations.AutomationInteractiveMode();automationInteractiveMode.automationControl = imageViewerAutomationControl;// Add the automation interactive mode to the viewer interactive modes list and enable itimageViewer.interactiveModes.beginUpdate();imageViewer.interactiveModes.add(automationInteractiveMode);automationInteractiveMode.isEnabled = true;imageViewer.interactiveModes.endUpdate();// Create the annotation automation objectannAutomation = new lt.Annotations.Automation.AnnAutomation(manager, imageViewerAutomationControl);// Set up the triangle annotationsetupTriangleAnnotation();// Activate the annotation automationannAutomation.active = true;}function imageViewer_ItemChanged(sender, e) {if (e.reason == lt.Controls.ImageViewerItemChangedReason.url) {// Get loaded image sizevar imageSize = sender.activeItem.imageSize;// Set container size with loaded image sizevar container = annAutomation.container;container.size = container.mapper.sizeToContainerCoordinates(lt.LeadSizeD.create(imageSize.width, imageSize.height));// Set the current object ID to the triangleannAutomation.manager.currentObjectId = -99;}}
Save your HTML file and load it in your favorite browser. Then, click/tap three separate points inside the viewer control to draw an AnnTriangleObject.
To use the custom AnnTriangleObject with LEADTOOLSAnnSvgRenderingEngine, update step 6 as follows:
////////////////////////////////////////////////////////////////////////////////// AnnTriangleRenderer (SVG version)AnnTriangleRenderer = function AnnTriangleRenderer() {this._activeId$2 = '';AnnTriangleRenderer.initializeBase(this);}AnnTriangleRenderer.prototype = {setActivePolyLineID: function AnnTriangleRenderer$setActivePolyLineID(Id) {this._activeId$2 = Id;},createObject: function AnnTriangleRenderer$createObject(annObject) {AnnTriangleRenderer.callBaseMethod(this, 'createObject', [annObject]);var g = lt.Annotations.Rendering.AnnSvgHelpers.getElementById(annObject.get_stateId());var polyline = lt.Annotations.Rendering.AnnSvgHelpers.createPolyline();var polylineId = lt.Annotations.Rendering.AnnSvgHelpers.getPolylineId(annObject.get_stateId());polyline.id = polylineId;var clipPathPolyline = lt.Annotations.Rendering.AnnSvgHelpers.createPolyline();var clipPathId = lt.Annotations.Rendering.AnnSvgHelpers.getClipPathId(annObject.get_stateId());clipPathPolyline.id = lt.Annotations.Rendering.AnnSvgHelpers.getPolylineId(clipPathId);var element = lt.Annotations.Rendering.AnnSvgHelpers.getElementById(clipPathId);element.appendChild(clipPathPolyline);this.setActivePolyLineID('');g.appendChild(polyline);},removeObject: function AnnTriangleRenderer$removeObject(annObject) {var id = annObject.get_stateId();if (!String.isNullOrEmpty(id)) {var clipPathId = lt.Annotations.Rendering.AnnSvgHelpers.getClipPathId(annObject.get_stateId());if (!String.isNullOrEmpty(clipPathId)) {var element = lt.Annotations.Rendering.AnnSvgHelpers.getElementById(clipPathId);if (element != null) {lt.Annotations.Rendering.AnnSvgHelpers.removeElement(element, lt.Annotations.Rendering.AnnSvgHelpers.getElementById(lt.Annotations.Rendering.AnnSvgHelpers.getPolylineId(clipPathId)));}}var objectElement = lt.Annotations.Rendering.AnnSvgHelpers.getElementById(id);if (objectElement != null) {lt.Annotations.Rendering.AnnSvgHelpers.removeElement(objectElement, lt.Annotations.Rendering.AnnSvgHelpers.getElementById(lt.Annotations.Rendering.AnnSvgHelpers.getPolylineId(id)));}}AnnTriangleRenderer.callBaseMethod(this, 'removeObject', [annObject]);},render: function AnnTriangleRenderer$render(mapper, annObject) {if (mapper == null) {lt.Annotations.Engine.ExceptionHelper.argumentNullException('mapper');}if (annObject == null) {lt.Annotations.Engine.ExceptionHelper.argumentNullException('annObject');}var svgEngine = Type.safeCast(this.get_renderingEngine(), lt.Annotations.Rendering.AnnSvgRenderingEngine);if (svgEngine != null) {var stateId = annObject.get_stateId();var oldStateID = annObject.get_stateId();var objectElement = lt.Annotations.Rendering.AnnSvgHelpers.getElementById(stateId);if (objectElement != null) {if (svgEngine.get_element().contains(objectElement)) {if (String.isNullOrEmpty(this._activeId$2)) {this._activeId$2 = lt.Annotations.Rendering.AnnSvgHelpers.getPolylineId(stateId);}var polyline = lt.Annotations.Rendering.AnnSvgHelpers.getElementById(this._activeId$2);if (this.get_clipPath()) {var clipPathId = lt.Annotations.Rendering.AnnSvgHelpers.getClipPathId(stateId);lt.Annotations.Rendering.SvgDrawingHelper.drawPolyline(lt.Annotations.Rendering.AnnSvgHelpers.getElementById(lt.Annotations.Rendering.AnnSvgHelpers.getPolylineId(clipPathId)), mapper, annObject);}lt.Annotations.Rendering.SvgDrawingHelper.drawPolyline(polyline, mapper, annObject);}else {this.addObject(annObject);this.render(mapper, annObject);annObject.set_stateId(oldStateID);}}AnnTriangleRenderer.callBaseMethod(this, 'render', [mapper, annObject]);this._activeId$2 = '';}}}AnnTriangleRenderer.registerClass('AnnTriangleRenderer', lt.Annotations.Rendering.AnnSvgObjectRenderer);