Skip to content

Commit

Permalink
New annotation tools
Browse files Browse the repository at this point in the history
Paper.js library was integrated within Mirador. All annotation shapes are rendered within a single canvas (front layer of the OSD). Five drawing tools were implemented: rectangle, circle, polygon, freehand and pin.

* icons for annotation tools - tools.png
* refactoring of rectangle creation
* rotation cursor replaced with font-awesome icons
* new dependency - awesome-cursor
* update of default settings - the default behavior is: all possible tools are visible
* cancel/choose buttons (color picker buttons) customization
* bug-fixes related to hovered shapes
* bug-fix - encode the  annotation id
* API key is passed as parameter of all the annotation requests
* functionality for drawing old annotation shapes
* new default settings
* when color picker is closed the selected shapes should change it's color
* bug-fix: debounce of show/hide hud (prevents exceptions when the user clicks multiple times)
* bug-fix mouse over line was almost impossible to trigger annotation popup
* ajax request cache prevention (workaround for IE11 cache: false)
* remove edit popups on image switch
  • Loading branch information
stanislavkostadinov committed Feb 25, 2016
1 parent 35f686c commit 8348b29
Show file tree
Hide file tree
Showing 25 changed files with 771 additions and 127 deletions.
1 change: 1 addition & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ module.exports = function(grunt) {
'js/lib/isfahan.js',
'js/lib/paper-full.min.js',
'js/lib/spectrum.js',
'js/lib/jquery.awesome-cursor.js',
'js/lib/i18next.min.js'
],

Expand Down
1 change: 1 addition & 0 deletions bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"URIjs": "https://github.com/medialize/URI.js.git#~1.13.2",
"paper": "git://github.com/paperjs/paper.js.git#*",
"spectrum": "git://github.com/bgrins/spectrum.git#*",
"jquery-awesome-cursor": "git://github.com/jwarby/jquery-awesome-cursor.git#*",
"qTip2": "~2.2.1"
},
"devDependencies": {
Expand Down
57 changes: 8 additions & 49 deletions css/mirador.css
Original file line number Diff line number Diff line change
Expand Up @@ -1176,39 +1176,6 @@ li.highlight {
font-weight: normal;
}

/* annotation tools
---------------------------------------------------------------------------- */

.draw-tool .square {
background-position: 0px;
}

.draw-tool .circle {
background-position: -21px;
}

.draw-tool .freehand {
background-position: -42px;
}

.draw-tool .polygon {
background-position: -63px;
}

.draw-tool .pin {
background-position: -84px;
}

.draw-tool i {
background-image: url('../images/tools.png');
background-repeat: none;
cursor: pointer;
display: inline-block;
height: 21px;
width: 21px;
margin: 3px 0 0 0;
}

/* color picker custom style
---------------------------------------------------------------------------- */

Expand All @@ -1220,33 +1187,25 @@ li.highlight {
text-decoration-line: underline !important;
}

.mirador-osd-edit-mode .sp-container {
.borderColorPicker + div + .sp-container, .fillColorPicker + div + .sp-container {
width: 300px !important;
}

.mirador-osd-edit-mode .sp-preview {
.borderColorPicker + div .sp-preview, .fillColorPicker + div .sp-preview {
height: 10px !important;
}
.mirador-osd-edit-mode .sp-dd {
.borderColorPicker + div .sp-dd, .fillColorPicker + div .sp-dd {
height: 6px !important;
line-height: 6px !important;
}
.mirador-osd-edit-mode .sp-replacer {
.borderColorPicker + div.sp-replacer, .fillColorPicker + div.sp-replacer {
border-radius: 5px !important;
}

.borderColorPicker + div {
background-image: url('../images/border-color-picker.png');
background-repeat: no-repeat;
background-size: auto 100%;
padding-left: 1.5em;
}

.fillColorPicker + div {
background-image: url('../images/fill-color-picker.png');
background-repeat: no-repeat;
background-size: auto 100%;
padding-left: 1.5em;
.borderColorPicker + div .material-icons, .fillColorPicker + div .material-icons {
float: left;
font-size: 12px !important;
margin: 0 5px 0 0;
}

/* image view
Expand Down
Binary file removed images/border-color-picker.png
Binary file not shown.
Binary file removed images/fill-color-picker.png
Binary file not shown.
Binary file removed images/rotate.png
Binary file not shown.
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<link rel="stylesheet" type="text/css" href="build/mirador/css/mirador-combined.css">
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/icon?family=Material+Icons">
<title>Mirador Viewer</title>
<style type="text/css">
#viewer { width: 100%; height: 100%; }
Expand Down
259 changes: 259 additions & 0 deletions js/lib/jquery.awesome-cursor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
/*! jquery-awesome-cursor - v0.1.5 - 2015-12-09
* https://jwarby.github.io/jquery-awesome-cursor
* Copyright (c) 2015 James Warwood; Licensed MIT */
;(function(global, factory) {
if (typeof define === 'function' && define.amd) {
define(['jquery'], factory);
} else if (typeof exports === 'object') {
factory(require('jquery'));
} else {
factory(global.jQuery);
}
})(this, function($) {
'use strict';

/**
* Parse the user-supplied hotspot string. Hotspot values as strings are used
* to set the cursor based on a human-readable value.
*
* ## Examples
*
* - `hotspot: 'center'`: the hotspot is in the center of the cursor
* - `hotspot: 'center left'`: the hotspot is centered vertically, and fixed
* to the left of the cursor horizontally
* - `hotspot: 'top right'`: the hotspot is at the top right
* - `hotspot: 'center top'`: the hotspot is centered horizontally, and fixed
* to the top of the cursor vertically
*
* @param {String} hotspot The string descriptor for the hotspot location
* @param {Number} size The size of the cursor
*
* @return {[Number]} an array with two elements, the x and y offsets for the
* hotspot
*
* @throws {Error} if `hotspot` is not a string, or `cursorSize` is not a
* number
*/
var parseHotspotString = function(hotspot, cursorSize) {
var xOffset = 0,
yOffset = 0;

if (typeof hotspot !== 'string') {
$.error('Hotspot value is not a string and could not be parsed');
}

if (typeof cursorSize !== 'number') {
$.error('Cursor size must be a number');
}

hotspot.split(' ').forEach(function(part) {
switch (part) {
case 'center':
xOffset = cursorSize / 2;
yOffset = cursorSize / 2;
break;
case 'top':
yOffset = 0;
break;
case 'bottom':

/* Browsers will default to 0 0 if yOffset is the very last pixel,
* hence - 1
*/
yOffset = cursorSize - 1;
break;
case 'left':
xOffset = 0;
break;
case 'right':
xOffset = cursorSize - 1;
break;
}
});

return [xOffset, yOffset];
};

/**
* Returns a new canvas with the same contents as `canvas`, flipped
* accordingly.
*
* @param {Canvas} canvas The canvas to flip
* @param {String} direction The direction flip the canvas in. Can be one
* of:
* - 'horizontal'
* - 'vertical'
* - 'both'
*
* @return {Canvas} a new canvas with the flipped contents of the input canvas
*/
function flipCanvas(canvas, direction) {
if ($.inArray(direction, ['horizontal', 'vertical', 'both']) === -1) {
$.error('Flip value must be one of horizontal, vertical or both');
}

var flippedCanvas = $('<canvas />')[0],
flippedContext;

flippedCanvas.width = canvas.width;
flippedCanvas.height = canvas.height;

flippedContext = flippedCanvas.getContext('2d');

if (direction === 'horizontal' || direction === 'both') {
flippedContext.translate(canvas.width, 0);
flippedContext.scale(-1, 1);
}

if (direction === 'vertical' || direction === 'both') {
flippedContext.translate(0, canvas.height);
flippedContext.scale(1, -1);
}

flippedContext.drawImage(canvas, 0, 0, canvas.width, canvas.height);

return flippedCanvas;
}

$.fn.extend({
awesomeCursor: function(iconName, options) {
options = $.extend({}, $.fn.awesomeCursor.defaults, options);

if (typeof iconName !== 'string' || !iconName) {
$.error('First parameter must be the icon name, e.g. \'pencil\'');
}

options.size = typeof options.size === 'string' ?
parseInt(options.size, 10) : options.size;

if (typeof options.hotspot === 'string') {
options.hotspot = parseHotspotString(options.hotspot, options.size);
}

// Clamp hotspot coordinates between 0 and size - 1
options.hotspot = $.map(options.hotspot, function(coordinate) {
return Math.min(options.size - 1, Math.max(0, coordinate));
});

var cssClass = (function(name, template) {
if (typeof template === 'string') {
return template.replace(/%s/g, name);
} else if (typeof template === 'function') {
return template(name);
}

return name;
})(iconName, options.font.cssClass),
srcElement = $('<i />', {
class: cssClass,
style: 'position: absolute; left: -9999px; top: -9999px;'
}),
canvas = $('<canvas />')[0],
canvasSize = options.size,
hotspotOffset, unicode, dataURL, context;

// Render element to the DOM, otherwise `getComputedStyle` will not work
$('body').append(srcElement);

// Get the unicode value of the icon
unicode = window.getComputedStyle(srcElement[0], ':before')
.getPropertyValue('content');

// Remove the source element from the DOM
srcElement.remove();

// Increase the size of the canvas to account for the cursor's outline
if (options.outline) {
canvasSize += 2;
}

if (options.rotate) {

// @TODO: move this into it's own function
canvasSize = Math.ceil(Math.sqrt(
Math.pow(canvasSize, 2) + Math.pow(canvasSize, 2)
));

hotspotOffset = (canvasSize - options.size) / 2;
canvas.width = canvasSize;
canvas.height = canvasSize;

context = canvas.getContext('2d');
context.translate(canvas.width / 2, canvas.height / 2);

// Canvas API works in radians, not degrees, hence `* Math.PI / 180`
context.rotate(options.rotate * Math.PI / 180);
context.translate(-canvas.width / 2, -canvas.height / 2);

// Translate hotspot offset
options.hotspot[0] += options.hotspot[0] !== canvas.width / 2 ?
hotspotOffset : 0;

options.hotspot[1] += options.hotspot[1] !== canvas.height / 2 ?
hotspotOffset : 0;
} else {

canvas.height = canvasSize;
canvas.width = canvasSize;

context = canvas.getContext('2d');
}

/* Firefox wraps the extracted unicode value in double quotes - #10
* Chrome 43+ is wrapping the extracted value in single quotes - #14
*/
unicode = unicode.replace(/['"]/g, '');

// Draw the cursor to the canvas
context.fillStyle = options.color;
context.font = options.size + 'px ' + options.font.family;
context.textAlign = 'center';
context.textBaseline = 'middle';
context.fillText(unicode, canvasSize / 2, canvasSize / 2);

// Check for outline option
if (options.outline) {
context.lineWidth = 0.5;
context.strokeStyle = options.outline;
context.strokeText(unicode, canvasSize / 2, canvasSize / 2);
}

// Check flip option
if (options.flip) {
canvas = flipCanvas(canvas, options.flip);
}

dataURL = canvas.toDataURL('image/png');

$(this)

// Fixes issue with Chrome not setting cursor if already set
.css('cursor', '')
.css('cursor', [
'url(' + dataURL + ')',
options.hotspot[0],
options.hotspot[1],
',',
'auto'
].join(' '))
;

// Maintain chaining
return this;
}
});

// Expose the defaults so that users can override them if they want to
$.fn.awesomeCursor.defaults = {
color: '#000000',
size: 18,
hotspot: [0, 0],
flip: '',
rotate: 0,
outline: null,
font: {
family: 'FontAwesome',
cssClass: 'fa fa-%s'
}
};
});
Loading

0 comments on commit 8348b29

Please sign in to comment.