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
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.
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; . . . } }
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" }, . . . } });
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(); } } }); } }); }
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.
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); }); } }); }
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.
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