new zoom component
authorHarris Baptiste <harris.baptiste@iri.centrepompidou.fr>
Wed, 13 Jul 2016 17:57:35 +0200
changeset 60 8f1af46307e1
parent 59 b30fa6fabee8
child 61 22c88b2c325f
new zoom component
src/iconolab/static/iconolab/css/iconolab.css
src/iconolab/static/iconolab/js/iconolab-bundle/index.html
src/iconolab/static/iconolab/js/iconolab-bundle/package.json
src/iconolab/static/iconolab/js/iconolab-bundle/src/components/cutout/Cutout.vue
src/iconolab/static/iconolab/js/iconolab-bundle/src/components/cutout/shape-resizer.js
src/iconolab/static/iconolab/js/iconolab-bundle/src/components/cutout/snapsvg-zoom.js
src/iconolab/static/iconolab/js/iconolab-bundle/src/components/cutout/svgboard.js
src/iconolab/static/iconolab/js/iconolab-bundle/src/components/typeahead/Typeahead.vue
src/iconolab/static/iconolab/js/iconolab-bundle/src/components/utils/index.js
src/iconolab/static/iconolab/js/iconolab-bundle/src/components/zoomview/Zoomview.vue
src/iconolab/static/iconolab/js/iconolab-bundle/src/components/zoomview/template.html
src/iconolab/templates/iconolab/change_annotation.html
--- a/src/iconolab/static/iconolab/css/iconolab.css	Tue Jul 05 14:28:04 2016 +0200
+++ b/src/iconolab/static/iconolab/css/iconolab.css	Wed Jul 13 17:57:35 2016 +0200
@@ -5,4 +5,5 @@
 .form-drawing {border-bottom: 1px solid #C3C3C3; }
 
 .form-drawing-wrapper .selected {border: 1px solid orange; color: white; background-color: orange}
-.showPointer {cursor: pointer;}
\ No newline at end of file
+.showPointer {cursor: pointer;}
+.zoomview-wrapper {border: 1px solid red; width: 135px; height: 100px; text-align: center;}
\ No newline at end of file
--- a/src/iconolab/static/iconolab/js/iconolab-bundle/index.html	Tue Jul 05 14:28:04 2016 +0200
+++ b/src/iconolab/static/iconolab/js/iconolab-bundle/index.html	Wed Jul 13 17:57:35 2016 +0200
@@ -5,12 +5,10 @@
     <title>iconolab-bundle</title>
   </head>
   <body>
-
     	<div id="AppContainer">
     		<cutout></cutout>
 			<typeahead></typeahead>    		
     	</div>
-
     <script src="dist/build.js"></script>
   </body>
 </html>
--- a/src/iconolab/static/iconolab/js/iconolab-bundle/package.json	Tue Jul 05 14:28:04 2016 +0200
+++ b/src/iconolab/static/iconolab/js/iconolab-bundle/package.json	Wed Jul 13 17:57:35 2016 +0200
@@ -4,7 +4,6 @@
   "author": "hbaptiste",
   "private": true,
   "scripts": {
-    "dev": "webpack-dev-server --inline --hot",
     "start": "webpack --progress --colors --watch",
     "build": "cross-env NODE_ENV=production webpack --progress --hide-modules"
   },
--- a/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/cutout/Cutout.vue	Tue Jul 05 14:28:04 2016 +0200
+++ b/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/cutout/Cutout.vue	Wed Jul 13 17:57:35 2016 +0200
@@ -2,14 +2,17 @@
 
 	import svgboard from './svgboard'
 	import Typeahead from '../typeahead/Typeahead.vue'
+	import Zoomview from '../zoomview/Zoomview.vue'
 	
 	export default {
 
 		el: '#drawing-zone',
 		MODE_RECT: 'RECT',
-		MODE_FREE: 'FREE', 
+		MODE_FREE: 'FREE',
+		ZOOM_IN: 'in',
+		ZOOM_OUT: 'out',
 
-		components: {typeahead: Typeahead},
+		components: {typeahead: Typeahead, zoomview: Zoomview},
 		data: { 
 			mode:"",
 			isRect: true,
@@ -19,6 +22,7 @@
 			useClipPath: false,
 			transformMatrix: "",
 			fragmentPath: "",
+			canZoom: true,
 			displayMask: false
 		},
 
@@ -38,8 +42,71 @@
 		},
 
 		methods: {
+			
+			incraseDrawingZoom: function () {
+				this.drawingComponent.increaseZoom();
+			},
 
-			
+			decreaseDrawingZoom: function () {
+				this.drawingComponent.decreaseZoom();
+			},
+
+			resetDrawingZoom: function () {
+				this.drawingComponent.reset();
+			},
+
+			computeZoomedViewBox: function () {
+				var viewBoxArray = [];
+				var zoomSvg = this.$refs.zoomSvg;
+				var shapeBBox = this.drawingComponent.getShapeBBox();
+				var imageWidth = zoomSvg.getAttribute("width");
+				var imageHeight = zoomSvg.getAttribute("height");
+				var xRatio = imageWidth / 100;
+				var yRatio = imageHeight / 100;
+				/* denormalize coordonate, max is imageX * 100x = imageHeigth*/
+				shapeBBox.x = shapeBBox.x * xRatio;
+				shapeBBox.y = shapeBBox.y * yRatio;
+
+				shapeBBox.w = shapeBBox.w * xRatio;
+				shapeBBox.h = shapeBBox.h * yRatio;
+
+	        	var imgRatio = imageWidth / imageHeight;
+		        if (shapeBBox.w > shapeBBox.h) {
+		            shapeBBox.y = Math.max(0, shapeBBox.y - (((shapeBBox.w * imgRatio) - shapeBBox.h) / 2));
+		            shapeBBox.h = shapeBBox.w * imgRatio;
+		        }
+		       	else {
+		       		shapeBBox.x = Math.max(0, shapeBBox.x - (((shapeBBox.h / imgRatio) - shapeBBox.w) / 2));
+		            shapeBBox.w = shapeBBox.h / imgRatio;
+		         	console.log(shapeBBox.y);
+		       	}
+		       	viewBoxArray = [shapeBBox.x, shapeBBox.y, shapeBBox.w, shapeBBox.h];
+
+				if (!shapeBBox) { return false; }
+				
+				/* test compute zoom
+					agrandir de sorte max bbox.h==viewPort.h && bbox.w
+				*/
+				//var viewBoxArray = [shapeBBox.x, shapeBBox.y, imageWidth, imageHeight]  
+				console.log(viewBoxArray);
+				return viewBoxArray.join(" ");
+			},
+
+			zoom: function (direction) {
+
+				var mainSvgWrapper = this.$refs.smallSvgWrapper;
+				if (this.$options.ZOOM_OUT === direction) {
+					var defaultViewBox = [0, 0, mainSvgWrapper.getAttribute("width"), mainSvgWrapper.getAttribute("height")];
+					mainSvgWrapper.setAttribute("viewBox", defaultViewBox.join(" "));
+					this.canZoom = true;
+				}
+
+				if (this.$options.ZOOM_IN === direction) { 
+					mainSvgWrapper.setAttribute("viewBox", this.computeZoomedViewBox());
+					this.canZoom = false; 
+				}
+			},
+
 			setDrawingMode: function (mode, updateComponent) {
 				if (!this.initialDrawingMode) {
 					this.initialDrawingMode = mode;//useful for cancel
@@ -61,10 +128,6 @@
 				}
 			},
 
-			showEditor: function () {
-				this.formView = false;
-			},
-
 			highLightZone: function () {
 				if (!this.displayMask) {
 					this.displayMask = true;
@@ -77,14 +140,23 @@
 			displayEditedPath: function () {
 				var normalizePath = this.drawingComponent.getPath();
 			},
+			
+			resetZoom: function () {
+				this.zoom(this.$options.ZOOM_OUT);
+			},
+
+			showEditor: function () {
+				this.formView = false;
+				this.resetZoom();
+				/* on edit mode reset*/
+			},
 
 			showForm: function () {
 				this.normalizePath = this.drawingComponent.getPath();
 				var smallImage = this.$refs.smallImage;
-				console.log(this.$refs);
 				this.formView = true;
-				var xRatio = smallImage.width / 100;
-				var yRatio = smallImage.height / 100;
+				var xRatio = smallImage.getAttribute("width") / 100;
+				var yRatio = smallImage.getAttribute("height") / 100;
 				var transformMatrix = [xRatio, 0, 0, yRatio, 0, 0].join(',');
 				this.transformMatrix ="matrix(" + transformMatrix + ")";
 				this.fragmentPath = this.normalizePath.split(';')[0];
--- a/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/cutout/shape-resizer.js	Tue Jul 05 14:28:04 2016 +0200
+++ b/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/cutout/shape-resizer.js	Wed Jul 13 17:57:35 2016 +0200
@@ -123,7 +123,7 @@
 
 export default {
 
-	apply_resize : function (paper, rect) {
+	enable_resizer : function (paper, rect) {
 		new ShapeResizer(paper, rect);
 	}
 }
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/cutout/snapsvg-zoom.js	Wed Jul 13 17:57:35 2016 +0200
@@ -0,0 +1,131 @@
+/* enable zoom */
+import { eventEmitter } from '../utils'
+
+class ZoomHandler {
+
+	constructor (params) {
+		this.zoomFactor = 0.1 || params.zoomFactor;
+		this.paper = params.paper;
+		this.viewport = {width: 529, height: 800};
+		this.scale = 1;
+		this.disableDrag = false;
+		this.paper.attr({"preserveAspectRatio": "xMidYMid meet"});
+		this.currentViewBox = [0, 0, this.viewport.width, this.viewport.height];
+		this.paper.drag(this.onDrag.bind(this), this.onStart.bind(this), this.onStop.bind(this));
+		this.lastPosition = []; 
+	}
+
+	zoomIn () {
+		var viewBoxW = this.viewport.width - (this.viewport.width * this.zoomFactor * this.scale);
+		var viewBoxH = this.viewport.height - (this.viewport.height * this.zoomFactor * this.scale);
+		if (!viewBoxW || !viewBoxH) { return false; }
+
+		/* center the viewbox */
+		var topX = (this.viewport.width - viewBoxW) / 2; 
+		var topY = (this.viewport.height - viewBoxH) / 2; 
+
+		this.currentViewBox[0] = topX; 
+		this.currentViewBox[1] = topY;
+
+		this.currentViewBox[2] = viewBoxW;
+		this.currentViewBox[3] = viewBoxH;
+
+		this.updateViewBox();
+		this.scale ++;
+	}
+	
+	updateViewBox () {
+		this.paper.attr({"viewBox": this.currentViewBox});
+		eventEmitter.emit("zoomChanged", {
+			"zoomFactor": this.getZoomFactor(),
+			"viewport": this.viewport,
+			"currentViewBox": this.currentViewBox
+		});
+	}
+	
+	getZoomFactor () {
+		return {
+			x: this.viewport.width / this.currentViewBox[2],
+			y: this.viewport.height / this.currentViewBox[3]	
+		};
+	}
+
+	onStart (x, y, e) {
+		
+		this.lastPosition[0] = this.currentViewBox[0];
+		this.lastPosition[1] = this.currentViewBox[1];
+
+		if (e.target.className.baseVal === "drawingHandler") {
+			this.disableDrag = true;
+		}
+	}
+
+	canDrag () {
+		return !this.disableDrag;
+	}
+
+	onStop () {
+		this.disableDrag = false;
+	}
+
+	onDrag (dx, dy, x, y, event) {
+
+		if (!this.canDrag()) { return true; }
+
+		var newX = this.lastPosition[0] - dx;
+		var newY = this.lastPosition[1] - dy;
+
+		/* maxX bound */
+		if (newX + this.currentViewBox[2] >= this.viewport.width) {
+			newX = this.viewport.width - this.currentViewBox[2];
+		}
+
+		/* maxY bound */
+		if (newY + this.currentViewBox[3] >= this.viewport.height) {
+			newY = this.viewport.height - this.currentViewBox[3];
+		}
+
+		if (newX <= 0) { newX = 0; }
+
+		if(newY <= 0) { newY = 0; }
+
+		this.currentViewBox[0] = newX;
+		this.currentViewBox[1] = newY;
+
+		this.updateViewBox();
+	}
+
+	reset () {
+		this.scale = 1;
+		this.currentViewBox = [0, 0, this.viewport.width, this.viewport.height];
+		this.updateViewBox();
+	}
+
+	zoomOut () {
+		if (this.scale === 1) { return false; }
+		var viewBoxW = this.currentViewBox[2] + (this.viewport.width * this.zoomFactor);
+		var viewBoxH = this.currentViewBox[3] + (this.viewport.height * this.zoomFactor);
+		if (viewBoxW > this.viewport.width || viewBoxW > this.viewport.height) {
+			return false;
+		}
+
+		var topX = (this.viewport.width - viewBoxW) / 2;
+		var topY = (this.viewport.height - viewBoxH) / 2; 
+  
+		this.currentViewBox[0] = topX;
+		this.currentViewBox[1] = topY; 
+		this.currentViewBox[2] = viewBoxW;
+		this.currentViewBox[3] = viewBoxH;
+
+		this.scale--;
+		this.updateViewBox();
+		
+	}
+}
+
+export default {
+
+	enable_zoom: function (params) {
+		return new ZoomHandler(params);
+	}
+}
\ No newline at end of file
--- a/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/cutout/svgboard.js	Tue Jul 05 14:28:04 2016 +0200
+++ b/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/cutout/svgboard.js	Wed Jul 13 17:57:35 2016 +0200
@@ -1,12 +1,13 @@
 
 
-var Snap = require('snapsvg');
-//import Snap from 'snapsvg'
+import Snap from 'snapsvg'
 import ShapeResizer from './shape-resizer'
 import EventEmitter from 'event-emitter'
+import SnapsvgZoom from './snapsvg-zoom'
+
 var eventEmitter = EventEmitter({});
 
-/* custom plugin */
+/* custom plugins */
 Snap.plugin(function (Snap, Element, Paper, glob) {
 	var elproto = Element.prototype;
 
@@ -28,6 +29,7 @@
 var mainImage = null;
 var pointData = [];
 var viewBoxBounds = {X: 100, Y:100};
+var zoomManager = null;
 var config = null;
 var readOnly = false;
 var startPoint = null;
@@ -73,7 +75,7 @@
 	drawing_path = rectZone;
 	canDraw = false;
 	pathIsClosed = true;
-	ShapeResizer.apply_resize(paper, drawing_path);
+	ShapeResizer.enable_resizer(paper, drawing_path);
 };
 
 
@@ -188,6 +190,10 @@
 var onMoveHandler = function (dx, dy, posX, posY, e) {
 	isDragged = true;
 	/* update point then update the view */
+	var zoomFactor = zoomManager.getZoomFactor();
+	console.log(zoomFactor);
+	dx = dx / zoomFactor.x;
+	dy = dy / zoomFactor.y;
 	var transformValue = this.data('origTransform') + (this.data('origTransform') ? "T" : "t") + [dx, dy];
 	this.attr({ transform: transformValue});
 	var boxSize = this.getBBox();
@@ -210,6 +216,7 @@
 	});
 	
 	point.handler.drag(onMoveHandler, function () {
+		console.log(this.transform().globalMatrix);
         this.data('origTransform', this.transform().local );
 	}, function () {
 		if (!isDragged) { return true; }
@@ -224,7 +231,7 @@
 	var handleY = point.y - HANDLE_SIZE/2;
 
 	handler = p.rect(handleX, handleY, HANDLE_SIZE, HANDLE_SIZE);
-
+	handler.addClass("drawingHandler");
 	point.handler = handler;
 	point.handler.data('point', point);
 	if (pointData.length === 0) {
@@ -341,11 +348,23 @@
 };
 
 var API = {
-	
+
+	increaseZoom: function () {
+		zoomManager.zoomIn();
+	},	
+
+	reset: function () {
+		zoomManager.reset();
+	},
+
+	decreaseZoom: function () {
+		zoomManager.zoomOut();
+	},
+
 	setPath: function (pathString) {
 		/* redraw the path */
 		var pathInfos = pathString.split(';');
-		if( availableModes.indexOf(pathInfos[1]) === -1) {
+		if ( availableModes.indexOf(pathInfos[1]) === -1) {
 			/* We assume then it is a free path */
 			pathInfos[1] = "FREE"; 
 		}
@@ -353,14 +372,14 @@
 		this.setDrawingMode(pathInfos[1]);
 		var pathData = pathInfos[0];
 		
-		if(!pathData.length) {
+		if (!pathData.length) {
 			return;
 		}
-		/* deal with path nomalization x = ImageWith/MaxXBound*/
+		/* deal with path nomalization x = ImageWith/MaxXBound */
 		var xRatio = mainImage.width() / viewBoxBounds.X;
 		var yRatio = mainImage.height() / viewBoxBounds.Y;
 		
-		if(isNaN(xRatio) || isNaN(yRatio)) {
+		if (isNaN(xRatio) || isNaN(yRatio)) {
 			new Error('Ratio should be a number.');
 		}
 
@@ -416,6 +435,11 @@
 		ENABLE_NEW_NODE = true;
 	},
 
+	getShapeBBox: function () {
+		var currentPath = this.getPath();
+		return Snap.path.getBBox(currentPath);
+	},
+
 	getPath: function () {
 		/* retourne le chemin */
 		/* send path and BBox | implement edit and load path */
@@ -478,22 +502,25 @@
 export default {
 
 	init: function(config) {
-		mainImage = jQuery(config.wrapperId).find('img').eq(0);
+		mainImage = jQuery(config.wrapperId).find('.main-image').eq(0);
 		var cutCanvas = jQuery(config.wrapperId).find('.cut-canvas').eq(0);
 		var path = jQuery(config.wrapperId).find('.image-path').eq(0);
 
 		if (typeof config.onDrawingModeChange === 'function') {
 			onChangeCallback = config.onDrawingModeChange;
 		}
+		
+		if (!mainImage.length) {
+			throw new Error("The main image Can't be found ...");
+		}
 
 		if (!cutCanvas.length) {
 			var cutCanvas = jQuery('<svg version="1.1"></svg>').addClass('cut-canvas');
 			jQuery(config.wrapperId).append(cutCanvas);
+			cutCanvas.append(mainImage);
 		}
 
-		if (!mainImage) {
-			new Error(config.wrapperId + "Can't be found ...");
-		}
+		
 
 		cutCanvas.css({
 			position: 'absolute', 
@@ -501,9 +528,9 @@
 			left: '15px',
 			marginLeft: 'auto',
 			marginRight: 'auto',
-			width: mainImage.width(),
-			height: mainImage.height(),
-			viewBox: '0 0 100 100'
+			width: mainImage.attr('width'),
+			height: mainImage.attr('height'),
+			//viewBox: '0 0 100 100'
 		});
 
 		if (typeof config.readOnly === 'boolean' && config.readOnly === true) {
@@ -514,9 +541,12 @@
 
 		if (path.length) {
 			jQuery(cutCanvas).append(path);
-			API.setPath(path.attr("d"));
+			var pathData = path.attr("d");
+			API.setPath(pathData);
+			path.remove();
 		}
-		
+		/* enable zoom */
+		zoomManager = SnapsvgZoom.enable_zoom({paper : paper});
 		attachPointEvents(paper);
 		attachRectEvents(paper);
 
--- a/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/typeahead/Typeahead.vue	Tue Jul 05 14:28:04 2016 +0200
+++ b/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/typeahead/Typeahead.vue	Wed Jul 13 17:57:35 2016 +0200
@@ -6,7 +6,7 @@
 	import Taglist from '../taglist/Taglist.vue'
 
 	var autoCompletePath = "http://lookup.dbpedia.org/api/search/PrefixSearch?MaxHits=5";
-	var wikipediaPath= "http://fr.wikipedia.org/w/api.php"
+	var wikipediaPath = "http://fr.wikipedia.org/w/api.php"
 	var parentsMethods = {
 		reset: typeahead.methods.reset
 	};
--- a/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/utils/index.js	Tue Jul 05 14:28:04 2016 +0200
+++ b/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/utils/index.js	Wed Jul 13 17:57:35 2016 +0200
@@ -1,3 +1,6 @@
+import EventEmitter from 'event-emitter' 
+var eventEmitter = EventEmitter({});
+
 var generateId = (function () {
 		var cpt = 0;
 		var defautPrefix = "item_"; 
@@ -7,4 +10,5 @@
 			return prefix + cpt; 
 		}
 	}());
-export { generateId }
\ No newline at end of file
+
+export { generateId , eventEmitter }
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/zoomview/Zoomview.vue	Wed Jul 13 17:57:35 2016 +0200
@@ -0,0 +1,36 @@
+
+<template src='./template.html'></template>
+
+<script>
+
+import {eventEmitter} from '../utils'
+
+import Snap from 'snapsvg'
+
+export default {
+
+	props: ['image-url', 'width', 'height'],
+	
+	mounted () {
+		this.root = new Snap(this.$refs['root-svg']);
+		this.handleEvents();
+	},
+
+	/* reduire size en fonction */
+	methods: {
+		
+		handleEvents: function () {
+			eventEmitter.on("zoomChanged", function (zoomInfos) {
+				console.log("zoomInfos", zoomInfos.currentViewBox);
+			});
+		},
+
+		createHandler: function () {},
+
+		handlerZoomChanged: function (params) {
+			console.log("params", params);
+		} 
+	}
+}
+
+</script>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/iconolab/static/iconolab/js/iconolab-bundle/src/components/zoomview/template.html	Wed Jul 13 17:57:35 2016 +0200
@@ -0,0 +1,12 @@
+<div class="zoomview-wrapper">
+	<svg ref="root-svg">
+		<image 
+			x="0" 
+			y="0" 
+			xmlns:xlink="http://www.w3.org/1999/xlink" 
+			:xlink:href="imageUrl" 
+			:width="width" 
+			:height="height"></image>
+		<rect ref='handler' x="0" y="0" width="10" height="10" style="fill:orange; opacity:0.7"></rect>
+	</svg>
+</div>
\ No newline at end of file
--- a/src/iconolab/templates/iconolab/change_annotation.html	Tue Jul 05 14:28:04 2016 +0200
+++ b/src/iconolab/templates/iconolab/change_annotation.html	Wed Jul 13 17:57:35 2016 +0200
@@ -14,6 +14,17 @@
 					<li @click="setDrawingMode('RECT')" v-bind:class="{ 'selected': isRect }" class='pull-md-right drawingModeBtn'>Rect.</li>
 					<li @click="setDrawingMode('FREE')" v-bind:class="{ 'selected': !isRect }" class='pull-md-right drawingModeBtn'>Libre</li>
 				</ul>
+				
+				<ul class="form-drawing-wrapper list-inline">
+					<p class='form-drawing pullright'><strong>Zoom</strong></p>
+					<li class="small" @click="incraseDrawingZoom"><i class="fa fa-plus"></i> Zoomer</li>
+					<li class="small" @click="resetDrawingZoom"><i class="fa fa-square"></i> Taille réelle</li>
+					<li class="small" @click="decreaseDrawingZoom"><i class="fa fa-minus"></i> Dézoomer</li>
+				</ul>
+				{% thumbnail image.media "x100" crop="center" as im %}
+					<zoomview :image-url="'{{ im.url }}'" :width="{{ im.width }}" :height="{{ im.height }}">
+					</zoomview>
+				{% endthumbnail %}
 
 				<ul class='form-drawing-wrapper list-inline'>
 					<p class='form-drawing pullright'><strong>Actions</strong></p>
@@ -27,12 +38,14 @@
 			<div class="col-md-8">
 				<div ref="image" id="iconolab-image-wrapper">
 					{% thumbnail image.media "x800" crop="center" as im %}
-	    				<img src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
-	    				<path class="image-path" d="{% if annotation %}{{ annotation.current_revision.fragment }}{% endif %}"></path>
+						<svg class="cut-canvas">
+							<image class="main-image" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="{{ im.url }}" x="0" y="0" width="{{ im.width }}" height="{{ im.height }}"></image>
+	    					<path class="image-path" d="{% if annotation %}{{ annotation.current_revision.fragment }}{% endif %}"></path>
+	    				</svg>
 					{% endthumbnail %}
 				</div>
 			</div>
-			
+
 			<div class="col-md-2">
 				<a @click="cancel"><i class="fa fa-close"></i> Annuler</a>
 			</div>
@@ -41,13 +54,14 @@
 
 		<div v-show="formView" class="col-md-12">
 			
-			<div class="col-xs-6">
+			<div class="col-md-6">
 				<div class="small-image-wrapper" style="position: relative">
 					{% thumbnail image.media "x300" crop="center" as im %}
-						<img ref="smallImage" @click="showEditor" src="{{ im.url }}" width="{{ im.width }}" height="{{ im.height }}">
 
-						<svg width="{{ im.width }}" height="{{ im.height }}" version="1.1" style="position:absolute; top:0px; left: 0px">
+						<svg ref="smallSvgWrapper" width="{{ im.width }}" preserveAspectRatio="xMinYMin meet" height="{{ im.height }}" version="1.1">
 							
+							<image ref="smallImage" xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="{{ im.url }}" x="0" y="0"  width="{{ im.width }}" height="{{ im.height }}"></image>
+
 							<defs>
 								<mask id="smallImage">
 									<rect x="0" y="0" width="{{ im.width }}" height="{{ im.height }}" fill="white"/>
@@ -63,6 +77,15 @@
 
 							<rect v-show="displayMask" ref="smallMask" x="0" y="0" mask="url(#smallImage)" opacity="0.7" fill="white" width="{{ im.width }}" height="{{ im.height }}"></rect>
 						</svg>
+
+						<svg style="display:none" ref="zoomSvg" width="{{ im.width }}" height="{{ im.height }}" version="1.1">
+							
+							<image xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="{{ im.url }}" x="0" y="0" preserveAspectRatio="none" width="{{ im.width }}" height="{{ im.height }}"></image>
+
+							<g v-bind:transform="transformMatrix">
+								<path v-bind:d="fragmentPath" opacity="0.7" fill="orange"></path>
+							</g>
+						</svg>
 					
 					{% endthumbnail %}
 				</div>
@@ -70,10 +93,12 @@
 					<li @click="showEditor" class="showPointer"> <i class='fa fa-edit'></i> Editer le fragment</li>
 					<li v-show="!displayMask" @click="highLightZone" class="showPointer"> <i class='fa fa-eye-slash'></i> Afficher la zone</li>
 					<li v-show="displayMask" @click="highLightZone" class="showPointer"> <i class='fa fa-eye-slash'></i> Masquer la zone</li>
+					<li v-if="canZoom" @click="zoom('in')"><i class="fa fa-zoom-in"></i>Zoomer</li>
+					<li v-if="!canZoom" @click="zoom('out')"><i class="fa fa-zoom-out"></i>Dezoomer</li>
 				</ul>
 			</div>
 
-			<div class='col-xs-6' style="">
+			<div class='col-md-6' style="">
 				 <form class="form" action="{% if annotation %}{% url 'annotation_edit' collection_name image_guid annotation_guid %}{% else %}{% url 'annotation_create' collection_name image_guid %}{% endif %}" method="POST">
 				 	{% if form.errors %}
                     <div id="errors" style="display: none;">