Attempting to Upload a Markup Which Is Not Contained by the Markup State

Figure one. The Image Markup plugin at work, showing cartoon and text features.

Introduction

Endless times, I needed to download an paradigm from somewhere on the cyberspace, open information technology up using some paradigm editing application, add some markups, save the image and and then use it somewhere else. For some types of websites it would exist neat, I call up, if the website itself allowed users to annotate the paradigm online (with both text and drawings) and and so save the combining image locally, or ship it to somewhere on the internet. This commodity introduces a jQuery plugin that implements such feature - at least in a simplified way.

Background

The idea behind the Image Markup plugin came recently in a talk with our CEO Marco Raduan at ILang Educação, when he showed the states a nice Mac OS feature which immune image annotations straight on top of the image itself through built-in Os capabilities, without the demand of a separate application. This kind of directly note was considered a adept nugget for our Learning Management Organization (ILang), in a style that teachers could review paradigm-scanned open-concluded answers handwritten by students and annotate them while evaluating them. Such visual annotations could exist persisted to database forth with evaluation and grade assigned to the student cess, in order to stand for the whole process of evaluation of open-ended questions.

System Dependencies

The awarding volition reside 100% on the client browser. Information technology is written in JavaScript and depends on four main frameworks/libraries:

  • jQuery: Unsurprisingly, since Epitome Markup is a jQuery plugin, it could never piece of work without the jquery framework preloaded. The simplicity here is really the primal to success.

  • Newspaper JS: The toolbox provided past Newspaper JS as a vector graphics library never ceases to amaze me. PaperJS does all the heavyweight work of drawing/text on tiptop of HTML5 canvas, which otherwise would have me spend endless hours of difficult working.

  • ContextMenu: The implementation of a context menu (that one which is accessed through the right mouse push) is a nice and clean fashion to provide a display of card options, without the demand to pollute the screen with tools and visual shortcuts. In my stance, the context bill of fare jQuery plugin implemented by Rodney Rehm, Christian Baartse and Addy Osmani excels past its simplicity and power customization capabilities.

  • CommandManager: Drawing and text writing sometimes can atomic number 82 to mistakes, and so it is important to take undo and redo commands every fourth dimension the user makes a modification. Alexander Brevig adult a unproblematic and effective implementation Command pattern in JavaScript.

Using the lawmaking

As whatever good jQuery plugin, Paradigm Markup only requires one line of JavaScript initialization lawmaking, and that is merely for setting up the IMAGE element on elevation of which the CANVAS annotation layer will be built. For example:

          var          markup = $('          #myImage').imageMarkup();

The in a higher place line will create an instance of Epitome Markup attached to the IMAGE having the id equals to myImage.

Users may detect helpful to attach an Image Markup instance to multiple IMAGE elements. In the post-obit example, a single instance of Image Markup will be attached to every IMAGE element inside an element with the "img-container" form:

          var          markup = $('          .img-container img').imageMarkup();

Overriding Default Options

Some properties can be overriden in guild to customize the style the plugin works. They are:

  • color: the color of drawings and text elements. This can be changed in one case yous start working.
  • width: you tin can predefine how thick your drawings are going to be. Plain this only applies to drawings, not to text elements.
  • opacity: because drawings and text over epitome can exist so disruptive, sometimes it is useful to add together a piffling transparency to the annotations. For this reason, you can cull an opacity ranging from 0.0 to i.0.

The line beneath specifies a Epitome Markup instance with red pen, thickness of 4 points and opacity of 50%:

          var          markup = $('          .img-container img').imageMarkup({color:          '          blood-red', width:          x, opacity: .5});

It's All About Layers

Image 2

Annotations should never modify the underlying image. Not because we want to preserve the original image, just rather because we want to continue annotations as an entity and later save it to the browser'south local storage, download or transmit it via web services, for instance.

The divide note layer also has other advantages: since it encapsulate an element set up in a JavaScript object structure, you can easily serialize information technology every bit JSON format, and persist annotations to, allow's say, a cord column in a database table. Conversely, yous could later think that same JSON string and restore the annotations.

Image 3

Figure ii. Image layer (A) under a transparent canvass with annotations (B) produces a new blended image (C).

Once initialized, the Image Markup plugin creates a new HTML5 canvas chemical element that covers the entire prototype. This canvas is appended as a sibling of the image element, therefore they share the same ancestor.

Cartoon Over Image

The plugin features freehand drawing: dragging the mouse over the canvass leaves a trace of straight segments, that becomes a smoothened scribble in one case the user releases the mouse push. Users can draw an unlimited number of points and independent lines.

And information technology begins when the user clicks the mouse button. At this moment, a new case of Path object is created and the default settings are applied to the path (colour, width and opacity):

tool.onMouseDown =          function          (event) {          switch          (effect.upshot.push button) { 		          case          0: 			          if          (path) { 				path.selected =          false; 			}  			path =          new          newspaper.Path(); 			path.data.id = generateUUID(); 			path.strokeColor = settings.color; 			path.strokeWidth = settings.width; 			path.opacity = settings.opacity;          interruption; 			          instance          2:          suspension; 	} }        

Below, the dragging issue is detected and handled by an implementation of the onMouseDrag event of Paper JS library's tool object: New points are added to the current path object while the user drags the mouse.

tool.onMouseDrag =          function          (effect) {          switch          (event.effect.button) { 		          case          0: 			 			          if          (selectedItem) { 				. 				[Elevate (MOVE) THE SELECTED ITEM] 				. 			}          else          if          (path) 				path.add(event.point);          break; 			          case          two:          break; 	} }

Now the onMouseUp event is subscribed, and when the user releases the mouse button the path is simplified (that is, lines between segment points are smoothened).

tool.onMouseUp =          function          (issue) {          switch          (event.event.button) { 		          example          0:          if          (selectedItem) { 				. 				[Finish DRAGGING THE SELECTED ITEM] 				. 			}          else          { 				 				path.simplify(); 				. 				[Salvage THE PATH Command IN Control MANAGER] 				. 			}          pause; 			 			. 			. 			. 	} }        

Image 4

Figure 3. Each "path" or "scribble" is made up past only a few points, but instead of appearing as straight lines, the segments betwixt them are smoothened to resemble handwriting.

Users tin can change pen colors, just choosing from a limited set: Black, Cerise, Dark-green and Xanthous.

          this.setPenColor =          part          (color) { 	self.setOptions({ colour: color }); 	$('          .paradigm-markup-canvas').css('          cursor',          "          url(img/"          + colour +          "          -pen.png) 14 50, car"); }

As seen earlier, the transparency and thickness properties are defined in JavaScript initializing code. Users cannot change them.

Text Over Epitome

This tool works in a "create first, modify later" manner. Users can add text annotations by clicking the Text tool in Context Menu, and a new text element with default bulletin will exist dropped on the canvass. The text can be edited by double-clicking the chemical element and typing in the new text in the browser's input dialog. The text color will be the same as the cartoon pen.

Notice how the text tool is gear up up in ContextMenu through the setText function:

$.contextMenu({ 	selector:          '          .image-markup-canvas', 	callback:          function          (key, options) {          switch          (primal) { 		. 		. 		.          case          '          text': 				self.setText();          suspension; 		. 		. 		. 		} 	}, 	items: { 	. 	. 	.          "          text": { proper name:          "          Text", icon:          "          text"          }, 	. 	. 	. 	} });

Image 5

The default settings are applied to the new case of the PointText object, and the onDoubleClick office prepares the web browser's default input dialog to inquire users for the new text:

          this.setText =          function          () {          var          uid = generateUUID();          var          pos = contextPoint; 	CommandManager.execute({ 		execute:          function          () {          var          TXT_DBL_CLICK =          "          <<double click to edit>>";          var          txt = TXT_DBL_CLICK;          var          text =          new          newspaper.PointText(pos); 			text.content = txt; 			text.fillColor = settings.color; 			text.fontSize =          18; 			text.fontFamily =          '          Verdana'; 			text.data.uid = uid; 			text.opacity = settings.opacity;  			text.onDoubleClick =          function          (event) {          if          (this.className ==          '          PointText') {          var          txt = prompt("          Type in your text",          this.content.supervene upon(TXT_DBL_CLICK,          '          '));          if          (txt.length >          0)          this.content = txt; 				} 			} 		}, 		unexecute:          function          () { 			$(paper.project.activeLayer.children).each(part          (alphabetize, particular) {          if          (detail.data          && detail.data.uid) {          if          (item.data.uid == uid) { 						particular.remove(); 					} 				} 			}); 		} 	}); }

Image 6

Figure 4. By selecting the text carte detail and and so double-clicking the text, users tin can identify text annotations over the image.

Selecting Items

You lot select an item by moving the mouse over it: a series of segment handlers will so indicate which item has been selected.

Image 7

Figure five. Path segments showing the selected element.

Equally the user moves the mouse (without dragging) over an element (path or text), the onMouseMove office of Paper JS library'due south tool object turns the chemical element into selected state, while deselecting all other elements on sail:

tool.onMouseMove =          function          (event) {          if          (!$('          .context-menu-listing').is('          :visible')) { 		position = result.point; 		paper.project.activeLayer.selected =          fake; 		self.setPenColor(settings.color);          if          (outcome.detail) { 			consequence.particular.selected =          true; 			selectedItem = event.detail; 			self.setCursorHandOpen(); 		}          else          { 			selectedItem =          aught; 		} 	} }

Currently, the Image Markup plugin does non support multiple choice. Nevertheless, I intend to implement this feature in future versions.

Deleting Items

Note elements easily be deleted by carte du jour. The Erase carte du jour volition delete the selected item, if there is one. Otherwise, information technology will delete all canvas elements.

The selected chemical element is searched for in the collection of canvas elements, then removed when constitute. Find how the operation is done through the CommandManager object, and so that it can exist undone later at user's request.

          this.erase =          function          () {          var          strPathArray =          new          Array(); 	$(paper.project.activeLayer.children).each(function          (index, particular) {          if          (contextSelectedItemId) {          if          (contextSelectedItemId.length ==          0          || item.data.id == contextSelectedItemId) {          var          strPath = item.exportJSON({ asString:          true          }); 				strPathArray.push(strPath); 			} 		} 	});  	CommandManager.execute({ 		execute:          function          () { 			$(newspaper.project.activeLayer.children).each(function          (index, item) {          if          (contextSelectedItemId) {          if          (contextSelectedItemId.length ==          0          || particular.data.id == contextSelectedItemId) { 						item.remove(); 					} 				} 			}); 		}, 		unexecute:          role          () { 			$(strPathArray).each(function          (index, strPath) { 				path =          new          paper.Path(); 				path.importJSON(strPath); 			}); 		} 	}); }

Image 8

Figure 6. Erasing the selected annotation detail.

Moving Items

Yous can motility a single element (drawing or text) by drag-and-drib over the whole canvas. However, the current version does non permit multiple elements to be moved at in one case.

Every bit the user releases the mouse button, the selected element is placed in its final location, the CommandManager is told to enlist the functioning, so that it tin can be undone later on.

tool.onMouseUp =          function          (event) {          switch          (event.event.button) { 		          case          0:          if          (selectedItem) {          if          (mouseDownPoint) {          var          selectedItemId = selectedItem.id;          var          draggingStartPoint = { 10: mouseDownPoint.x, y: mouseDownPoint.y }; 					CommandManager.execute({ 						execute:          function          () { 							 						}, 						unexecute:          function          () { 							$(paper.project.activeLayer.children).each(function          (index, item) {          if          (detail.id == selectedItemId) {          if          (particular.segments) {          var          middlePoint =          new          paper.Signal( 												((item.segments[detail.segments.length -          ane].point.x) - item.segments[0].bespeak.x) /          two, 												((item.segments[item.segments.length -          1].point.y) - item.segments[0].indicate.y) /          2          ); 										item.position =          new          newspaper.Signal(draggingStartPoint.ten, draggingStartPoint.y); 									}          else          { 										detail.position = draggingStartPoint; 									}          return          faux; 								} 							}); 						} 					}); 					mouseDownPoint =          null; 					. 					. 					.

Downloading Merged Images

Y'all can download composed images (that is, the source image plus the drawings and text annotations) as a single image, by clicking the Download carte du jour item in the context menu. The image will go right to your Downloads folder (or any folder you take assigned every bit your browser's default).

$.contextMenu({ 	selector:          '          .image-markup-sheet', 	callback:          function          (key, options) {          switch          (key) { 			 			. 			. 			.          case          '          download': 				self.download();          suspension; 			. 			. 			.						 		} 	}, 	items: { 	. 	. 	.          "          download": { name:          "          Download", icon:          "          download"          }, 	. 	. 	. 	} });

In the code below, the mergedContext is created every bit a new instance of Canvas html element (though not appended to the HTML folio, and thus not visible). So, the drawImage method is called twice: once to depict the underlying paradigm and once more to describe the canvass containing the image annotations created before with the aid of the Paper JS library.

          this.download =          part          () {          var          sheet = newspaper.projection.activeLayer.view.chemical element;          var          img = $(sail).parent().discover('          img')[0];          var          mergeCanvas = $('          <canvas>') 	.attr({ 		width: $(img).width(), 		meridian: $(img).height() 	});          var          mergedContext = mergeCanvas[0].getContext('          2nd'); 	mergedContext.clearRect(0,          0, $(img).width(), $(img).pinnacle()); 	mergedContext.drawImage(img,          0,          0); 	mergedContext.drawImage(sheet,          0,          0); 	self.downloadCanvas(mergeCanvas[0],          "          image-markup.png"); }

The actual download code is implemented in an ingenious solution provided by Ken Fyrstenberg, that emulates the click event on an ballast (<a>) element:

          this.downloadCanvas =          function          (canvas, filename) { 	          var          lnk =          document.createElement('          a'), 		e;  	 	lnk.download = filename;  	 	 	 	lnk.href = canvass.toDataURL();  	          if          (document.createEvent) {  		eastward =          certificate.createEvent("          MouseEvents"); 		e.initMouseEvent("          click",          true,          true,          window,          0,          0,          0,          0,          0,          false,          false,          false,          false,          0,          null);  		lnk.dispatchEvent(e);  	}          else          if          (lnk.fireEvent) {  		lnk.fireEvent("          onclick"); 	} }

The next version of the plugin will allow downloading the annotation image (a .png image with drawings plus text over a transparent groundwork), then that programmers can identify information technology over the original image and obtain the equanimous image.

Conclusion

As y'all can run across, there is a lot of room for enhancement in the application. The Paper.js framework proved itself upwards to the task of handling complex graphic scripting and event treatment, while providing a clean and simplified gear up of classes and events.

Also, the awarding at this point is mayhap too much for general purpose, but I think the biggest potential lies on how you adapt it to the specific needs of your project. For example, it can exist a strictly educational website, where teachers assess work done online by students. Or it can exist an entertainment game where children spot the differences between pairs of images. Or even a collaborative application (something like Google Hangouts) for remote users in online meetings.

If you have any comments, complaints or suggestions, delight leave a comment below. I'd like to hear from you lot and I'chiliad willing to amend the app as new ideas arrive.

History

2014-07-26: First version.

martinreplach.blogspot.com

Source: https://www.codeproject.com/articles/801111/html-image-markup

0 Response to "Attempting to Upload a Markup Which Is Not Contained by the Markup State"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel