Commit 4a36d812e9628ac17637678c2f9d9f121819fed6

  • avatar
  • Tommi Tuovinen <tommi.s.tuovinen @stu…nt.jyu.fi> (Committer)
  • Thu Apr 19 16:42:30 EEST 2012
  • avatar
  • Tommi Tuovinen <tommi.s.tuovinen @stu…nt.jyu.fi> (Author)
  • Thu Apr 19 16:42:30 EEST 2012
analyses folder renamed to selections.
app/assets/javascripts/analyses/analysis.js.erb
(0 / 373)
  
1/* This content is released under the MIT License.
2 *
3 * Please see the full license in the attached LICENCE-file
4 * or at http://www.opensource.org/licenses/mit-license.php
5 *
6 * Copyright (c) 2012 Pekka Iso-Ahola
7 * Copyright (c) 2012 Jussi Perttola
8 * Copyright (c) 2012 Tommi Tuovinen
9 * ---------------------------------------------------------
10 * Functions for the selection view.
11*/
12
13// Init global variables
14selectionTable = {};
15selectionTools = [];
16selectionTool = null;
17layeredImageTable = {}
18showAllSelections = false;
19drawLayer = null;
20drawCanvas = null;
21activeSelection = null;
22activeImage = null;
23deleteMode = false;
24zoomScale = 1;
25SELECTION_WIDTH = 86;
26MIN_ZOOM = 0.125;
27MAX_ZOOM = 64;
28
29
30// Function for adding Selection-groups
31function addSelectionFromForm() {
32 var name = $('#selectionNameField').val();
33 if (selectionTable[name] != null) {
34 alert('Selection ' + name + ' already exists!');
35 } else {
36 var s = new Selection(name);
37 s.enable();
38 }
39 $('#selection_popup').toggle();
40
41 // FOR EASY TESTING PURPOSES
42 if (name.indexOf("-") > -1) {
43 var splitName = name.split("-");
44 $('#selectionNameField').val(splitName[0] + "-" + (++splitName[1]));
45 }
46}
47
48// The general-purpose event handler. This function determines the mouse
49// position relative to the canvas element.
50function evCanvas(ev) {
51 if (!ev) var ev = window.event;
52 ev._x = 0;
53 ev._y = 0;
54
55 if (ev.layerX || ev.layerY) { // layerX and layerY are soon depricated.
56 ev._x = ev.layerX;
57 ev._y = ev.layerY;
58 }
59 else if (ev.pageX || ev.pageY) { //TODO: Does not work when zooming
60 ev._x = ev.pageX - $('#image_container').get(0).offsetLeft
61 + $('#image_container').get(0).scrollLeft;
62 ev._y = ev.pageY - $('#image_container').get(0).offsetTop
63 + $('#image_container').get(0).scrollTop;
64 }
65 else if (ev.clientX || ev.clientY) { //TODO: Does not work when zooming
66 ev._x = ev.clientX - $('#image_container').get(0).offsetLeft
67 + window.pageXOffset + $('#image_container').get(0).scrollLeft;
68 ev._y = ev.clientY - $('#image_container').get(0).offsetTop
69 + window.pageYOffset + $('#image_container').get(0).scrollTop;
70 }
71
72 // Positions the coordinate floater and shows coordinates
73 $('#coordinate_floater').html("(" + ev._x + ", " + ev._y + ")").css({
74 left: ev.pageX + 10,
75 top: ev.pageY + 10
76 });
77
78 // If no selectionToolselected then just return.
79 if (selectionTool== null)
80 return;
81
82 if (activeSelection == null) {
83 if (ev.type == "mousedown")
84 alert("You must have a selection active first.")
85 } else {
86
87 // Call the event handler of the selectionTool.
88 var func = selectionTool[ev.type];
89 if (func) {
90 func(ev);
91 }
92 }
93}
94
95// Attach the mousedown, mousemove and mouseup event listeners
96function initCanvas() {
97 // Init canvas
98 drawLayer = $('#draw_layer').get(0);
99 drawLayer.addEventListener('mousedown', evCanvas, false);
100 drawLayer.addEventListener('mousemove', evCanvas, false);
101 drawLayer.addEventListener('mouseup', evCanvas, false);
102
103 // Init context settings
104 drawCanvas = drawLayer.getContext('2d');
105}
106
107// Expands the image and selection lists so that all the elements fit inside.
108function setupUIElements() {
109 $("#selection_list").width($("#selection_list > div").size() * SELECTION_WIDTH);
110 $("#image_list").width($("#image_list > div").size() * SELECTION_WIDTH);
111
112 // Positions the loading div
113 var lo = $('#loading').get(0);
114 lo.setAttribute('left', $('document').width() / 2 + 'px');
115 lo.setAttribute('top', $('document').height() / 2 + 'px');
116
117 //Changing imgContainer to fit better on the screen
118 var imgContainer = $('#image_container');
119 var oldWidth = drawLayer.width;
120 imgContainer.width("80%");
121 var ratio = imgContainer.width() / oldWidth;
122 var newHeight = drawLayer.height * ratio;
123 imgContainer.height(newHeight);
124 imgContainer.get(0).style.overflow = "auto";
125
126}
127
128// Updates the current viewed image and resizes canvases accordingly.
129// Called when activeImage changes.
130function updateCanvases() {
131 x = activeImage.img.width;
132 y = activeImage.img.height;
133 contexto = $('#image_holder').get(0).getContext('2d');
134 contexto.canvas.width = x;
135 contexto.canvas.height = y;
136 helpContext = $('#help_layer').get(0).getContext('2d');
137 helpContext.canvas.width = x;
138 helpContext.canvas.height = y;
139 drawLayer.width = x;
140 drawLayer.height = y;
141 drawCanvas = drawLayer.getContext('2d');
142 contexto.drawImage(activeImage.img, 0, 0);
143}
144
145// Updates the current viewed image but does not resize canvases.
146// Called when image number changes.
147function updateImage() {
148 contexto = $('#image_holder').get(0).getContext('2d');
149 contexto.drawImage(activeImage.img, 0, 0);
150}
151
152// Clears current selections
153function clearSelections() {
154 for (var key in selectionTable) {
155 selectionTable[key].localDestruct();
156 }
157}
158
159// Actions that are commited in UI when a hyperspectral image
160// is selected from the list.
161function imageListItemEnabled() {
162 updateCanvases();
163 initPartialImageSelector();
164}
165
166// Loads selections from the server and displays a loading animation
167function loadSelections() {
168 zoomScale = 1;
169 zoom(zoomScale);
170 clearSelections();
171 $.ajax({
172 type: "POST",
173 url: "/selections/index",
174 dataType: "json",
175 data: {
176 id: activeImage.id
177 },
178
179 // Display loading animation
180 beforeSend: function(xhr) {
181 $('#loading_text').html("Loading ...");
182 $('#loading_div').show();
183 },
184
185 success: function(data) {
186 for (i = 0; i < data.length; i++) {
187 sel = new Selection(data[i].name, data[i]._id,
188 data[i].merged_bitmask);
189 sel.present();
190 sel.drawPreview();
191 }
192 },
193
194 error: function(data) {
195 alert('Something went wrong! ' + data)
196 },
197
198 complete: function() {
199 // Hide loading animation
200 $('#loading_div').hide();
201 setupUIElements();
202 }
203 });
204}
205
206function loadImages() {
207 $.ajax({
208 type: "GET",
209 url: "/layered_images",
210 dataType: "json",
211
212 // Display loading animation
213 beforeSend: function(xhr) {
214 $('#loading_text').html("Loading Images ...");
215 $('#loading_div').show();
216 },
217
218 success: function(data) {
219 for (i = 0; i < data.length; i++) {
220 img = new LayeredImage(data[i]);
221 }
222 },
223
224 error: function(data) {
225 alert('Something went wrong! ' + data)
226 },
227
228 complete: function() {
229 // Hide loading animation
230 // fixCanvases();
231 $('#loading_div').hide();
232 setupUIElements();
233 }
234 });
235}
236
237// Define handler function for slide-event
238function partialSliderSlide(event, ui) {
239 $('#partialImageSelector > a').text(ui.value).css(
240 {'text-align' : 'center',
241 'color' : '#4B81BE',
242 'background-color': '#EBEBFF'});
243}
244
245// Initializes the slider for selecting current displayed PartialImage
246function initPartialImageSelector() {
247 $('#top_toolbar').show();
248 // Initialize slider twice to have the start value work correctly
249 $('#partialImageSelector').slider({
250 value: activeImage.currentLayer,
251 min: 0,
252 max: activeImage.layerCount-1,
253 step: 1,
254 stop: function( event, ui ) {
255 activeImage.getImage(ui.value);
256 },
257 slide: partialSliderSlide
258 });
259 $('#partialImageSelector').slider({
260 value: activeImage.currentLayer,
261 min: 0,
262 max: activeImage.layerCount-1,
263 step: 1,
264 stop: function( event, ui ) {
265 activeImage.getImage(ui.value);
266 },
267 slide: partialSliderSlide
268 });
269 // Call partialSliderSlide routine to set up initial values and location.
270 partialSliderSlide("", { "value": activeImage.currentLayer});
271}
272
273function initThresholdSlider() {
274 $('#thresholdSlider').slider({
275 value:10,
276 min: 1,
277 max: 100,
278 slide: function( event, ui ) {
279 $( "#thresholdField" ).val( ui.value );
280 }
281
282 });
283
284 $('#thresholdField').focusout(validateThreshold);
285
286 $('#thresholdField').keyup($.proxy(function(event) {
287 if (event.keyCode == 13) {
288 validateThreshold();
289 }
290 },
291 this));
292}
293
294// Validates threshold slider value
295function validateThreshold() {
296 var threshold = parseInt($('#thresholdField').val());
297
298 if (isNaN(threshold) || threshold > 100 || threshold < 1)
299 threshold = 10;
300
301 $('#thresholdSlider').slider('value', threshold);
302 $('#thresholdField').val(threshold);
303}
304
305// Initializes the tools.
306function initTools() {
307 initDeleteModeButton();
308 initShowAllButton();
309 initZoomInTool();
310 initZoomOutTool();
311 initUndoTool();
312 initRectangleTool();
313 initEllipseTool();
314 initPolygonTool();
315 initMagicWandTool();
316
317}
318
319$(document).ready(function() {
320 initCanvas();
321 contexto = $('#image_holder').get(0).getContext('2d');
322
323 // Attach Enter press to naming button
324 $("#selectionNameField").keyup(function(event) {
325 if (event.keyCode == 13) {
326 $("#selectionNameButton").click();
327 }
328 });
329
330 // Create a new image.
331 var img = new Image();
332
333
334 initThresholdSlider();
335
336 initTools();
337
338 rectangle.enable();
339 $('#toolbar').css('height', Math.ceil($('#toolbar > img').length / 2)
340 * 50 - 5 + 'px');
341 lockSelectionTools();
342
343 loadImages();
344
345 setupUIElements();
346
347 // Hide and display coordinates when inside the canvas area
348 $('#draw_layer').bind('mouseout',
349 function() {
350 $('#coordinate_floater').toggleClass('hide', true);
351 }).bind('mouseover',
352 function() {
353 $('#coordinate_floater').toggleClass('hide', false);
354 });
355
356 $('#fileuploadInput').fileupload({
357 dataType: 'json',
358 url: '/layered_images/upload',
359 send: function(e, data) {
360 $('#loading_text').html("File upload in progress.");
361 $('#file_upload').fadeOut(500);
362 $('#loading_div').show();
363 },
364 failed: function(e, data) {
365 $('#loading_div').hide();
366 },
367 done: function(e, data) {
368 for (i = 0; i < data.length; i++)
369 img = new LayeredImage(data[i]);
370 $('#loading_div').hide();
371 }
372 });
373});
app/assets/javascripts/analyses/index.js
(0 / 3)
  
1// Place all the behaviors and hooks related to the matching controller here.
2// All this logic will automatically be available in application.js.
3//= require_tree .
app/assets/javascripts/analyses/layeredimage_class.js.erb
(0 / 258)
  
1/* This content is released under the MIT License.
2 *
3 * Please see the full license in the attached LICENCE-file
4 * or at http://www.opensource.org/licenses/mit-license.php
5 *
6 * Copyright (c) 2012 Pekka Iso-Ahola
7 * Copyright (c) 2012 Jussi Perttola
8 * Copyright (c) 2012 Tommi Tuovinen
9 * ---------------------------------------------------------
10 * A LayeredImage, represents a single hyperspectral image.
11*/
12function LayeredImage(obj) {
13 // Check whether a LayeredImage with this name already exists.
14 layeredImageTable[name] = this;
15
16 this.currentLayer = null;
17
18 if (obj == null)
19 return;
20
21 if (obj._id != null)
22 this.id = obj._id;
23
24 if (obj.name != null)
25 this.name = obj.name;
26
27 if (obj.attachment_filename != null)
28 this.filename = obj.attachment_filename;
29
30 if (obj.layer_count != null) {
31 this.layerCount = obj.layer_count;
32 this.currentLayer = Math.floor(this.layerCount/2);
33 }
34
35 this.img = null;
36
37 // Create list element div for the LayeredImage object
38 this.listElement = jQuery('<div/>', {
39 id: this.name + "_element",
40 'class': "selection_list_item"
41 });
42
43 // Container div for the namediv and nameInput </div>s
44 this.innerdiv = jQuery('<div/>', {
45 'class': "selection_innerdiv"
46 });
47
48 // Container for default name text display
49 this.namediv = jQuery('<div/>', {
50 text: this.name,
51 'class': "selection_list_name"
52 });
53
54 // Input field for renaming this
55 this.nameInput = jQuery('<input/>', {
56 'class': "selection_list_rename hide"
57 });
58
59 // Create jQuery canvas object for the LayeredImage preview image
60 this.previewCanvasJQ = jQuery('<canvas/>', {
61 'class': "preview_canvas"
62 });
63
64 // Get the actual DOM object for height/width settings
65 this.previewCanvas = this.previewCanvasJQ.get().pop();
66 this.previewCanvas.height = 50;
67 this.previewCanvas.width = 50;
68 this.previewContext = this.previewCanvas.getContext('2d');
69
70 // Draws the preview image from this.canvas => this.previewCanvas
71 this.drawPreview = function() {
72 // Loads the first image as the active image
73 this.previewContext.clearRect(0, 0, this.previewCanvas.width,
74 this.previewCanvas.height);
75 this.previewContext.drawImage(this.img,
76 0, 0, this.img.width, this.img.height,
77 0, 0, this.previewCanvas.width, this.previewCanvas.height);
78 }
79
80 // Append the elements into innerdiv and innerdiv into listElement
81 this.namediv.appendTo(this.innerdiv);
82 this.nameInput.appendTo(this.innerdiv);
83 this.innerdiv.appendTo(this.listElement);
84 this.previewCanvasJQ.appendTo(this.listElement);
85
86 // Bind doubleclick-event for renaming this
87 this.innerdiv.bind("dblclick",
88 $.proxy(function() {
89 this.namediv.toggleClass('hide', true);
90 this.nameInput.toggleClass('hide', false);
91 // Quick fix for avoiding #thresholdField getting reset on reload
92 this.nameInput.val(this.name);
93 this.nameInput.focus();
94 },
95 this));
96
97 // Binds the focusout-event for the nameInput field.
98 // Shows the default name field.
99 this.nameInput.bind('focusout', $.proxy(function() {
100 this.namediv.toggleClass('hide', false);
101 this.nameInput.toggleClass('hide', true);
102 },
103 this));
104
105 // Bind enter-up event for the renaming functionality
106 this.innerdiv.keyup($.proxy(function(event) {
107 if (event.keyCode == 13) {
108 this.nameInput.blur();
109 if (layeredImageTable[this.nameInput.val()] == null) {
110 // Renames this object and moves it to it's rightful
111 // place in layeredImageTable
112 this.namediv.text(this.nameInput.val());
113 layeredImageTable[this.nameInput.val()] = this;
114 delete layeredImageTable[this.name];
115 this.name = this.nameInput.val();
116 // Update the LayeredImage name to the server
117 this.update_name();
118 // More fixes for avoiding #thresholdField resets
119 this.nameInput.val("10");
120 }
121 else if (this.nameInput.val() == this.name) {
122 // Don't complain if name doesn't change.
123 return;
124 } else {
125 // Copy name from the namediv field (which still contains
126 // the old name)
127 alert('Image ' + this.nameInput.val() + ' already exists!');
128 this.nameInput.val(this.namediv.text());
129 // More fixes for avoiding #thresholdField resets
130 this.nameInput.val("10");
131 }
132 }
133 },
134 this));
135
136 // Adds the list element, calls setupUIElements to expand the
137 // list to match correct dimensions
138 this.listElement.hide().appendTo('#image_list').fadeIn(300);
139 setupUIElements();
140
141 // Activates this image
142 this.enable = function() {
143 // Disables the previous image
144 if (activeImage != this && activeImage != null) {
145 activeImage.disable();
146 activeImage = null;
147 }
148 // Sets this as the active image
149 if (activeImage == null) {
150 activeImage = this;
151
152 this.listElement.toggleClass('list_item_selected', true);
153
154 loadSelections();
155 imageListItemEnabled();
156 }
157 }
158
159 // Disables this image
160 this.disable = function() {
161 this.listElement.toggleClass('list_item_selected', false);
162 }
163
164 // Proxies the correct scope for the click-event
165 this.listElement.click($.proxy(this.enable, this));
166
167 // Destructor for the object - removes the list element as well as the
168 // canvas. Also removes the remaining JS objects.
169 // NOTE: This is not bound to any events and is only used for debugging
170 // purposes.
171 this.destruct = function() {
172 this.post_delete();
173 // Some candy for the list item removal!
174 this.listElement.fadeOut(300,
175 function() {
176 $(this).remove();
177 });
178 setupUIElements();
179 delete this;
180 }
181
182 // Function for debugging purposes, should not be used.
183 this.post_delete = function() {
184 $.post("/layered_images/destroy", {
185 id: this.id
186 },
187 // Executes this after the call returns.
188 function(data) {
189 //console.log('delete successful');
190 },
191 "json"
192 );
193 }
194
195 // Only updates the name of this this
196 this.update_name = function() {
197 $.post("/layered_images/update_name", {
198 layered_image: {
199 name: this.name,
200 },
201 id: this.id
202 },
203 // Executes this after the call returns.
204 function(data) {
205 //console.log('update successful');
206 },
207 "json"
208 );
209 }
210
211 // Gets an image at index imageNumber and draws it to the view.
212 // NOTE: Does not overwrite the preview image.
213 this.getImage = function(imageNumber) {
214 this.currentLayer = imageNumber;
215 $.ajax({
216 type: 'POST',
217 url: "/layered_images/get_image",
218 data: {
219 index: imageNumber,
220 id: this.id
221 },
222 success: $.proxy(function(data) {
223 this.img = new Image();
224 this.img.src = data.data.toString();
225 this.img.onload = function() {
226 updateCanvases();
227 }
228 }, this),
229 dataType: "json"
230 });
231 }
232
233 // Gets and draws the preview. Uses the image in index max/2.
234 // NOTE: Only a small difference to getImage function.
235 // TODO: Pass onload-function as a parameter? Problems with proxying
236 // "this" as well as a function.
237 this.getPreviewImage = function() {
238 this.currentLayer = Math.floor(this.layerCount/2);
239 $.ajax({
240 type: 'POST',
241 url: "/layered_images/get_image",
242 data: {
243 index: this.currentLayer,
244 id: this.id
245 },
246 success: $.proxy(function(data) {
247 this.img = new Image();
248 this.img.src = data.data.toString();
249 this.img.onload = $.proxy(function() {
250 this.drawPreview();
251 }, this)
252 }, this),
253 dataType: "json"
254 });
255 }
256
257 this.getPreviewImage();
258}
app/assets/javascripts/analyses/selection_class.js.erb
(0 / 524)
  
1/* This content is released under the MIT License.
2 *
3 * Please see the full license in the attached LICENCE-file
4 * or at http://www.opensource.org/licenses/mit-license.php
5 *
6 * Copyright (c) 2012 Pekka Iso-Ahola
7 * Copyright (c) 2012 Jussi Perttola
8 * Copyright (c) 2012 Tommi Tuovinen
9 * ---------------------------------------------------------
10 * A Selection-object, represents a single selection group.
11 * It contains the related bit masks, canvases
12 * and other UI elements.
13*/
14function Selection(name, id, bitmask) {
15 // Check whether a selection with this name already exists.
16 if (selectionTable[name] == null) {
17 selectionTable[name] = this;
18 }
19 else {
20 alert('Selection ' + name + ' already exists!');
21 }
22
23 this.image = activeImage.id; // sets the selection under the active image
24
25 // Initialize an array for holding restore points to be used
26 // with the undo functionality
27 this.restorePoints = [];
28
29 // Initialize an array for holding the small undo canvases
30 this.historyItems = [];
31
32 this.fillStyle = 'rgb(0, 200, 0)';
33
34 if (id != null) {
35 this.id = id;
36 }
37
38 this.name = name;
39
40 // Bitmask is the bitmask that has been downloaded from the server
41 this.bitmask = null;
42 if (bitmask != null)
43 this.bitmask = bitmask;
44
45 /* Initializes the data array, containing the selections
46 * that are made locally.
47 * Data format is a following object:
48 {
49 selectionTool: Name of the tool used
50 x: x_min, Lowest x-coordinate
51 y: y_min, Lowest y-coordinate (origin is top-left)
52 h: y_max - y_min, Height of the selection
53 w: x_max - x_min, Width of the selection
54 bitString: bitMask Bitmask of the selection
55 -- string with a length of h*w
56 }
57 */
58 this.data = [];
59
60 // Helper function for storing a bitmask, called by the tools
61 this.storeBitmask = function(bitmask) {
62 this.data.push(bitmask);
63 }
64
65 // Create canvas-element and fetch it's context for the Selection object
66 this.canvas = jQuery('<canvas/>', {
67 id: this.name + "_canvas",
68 'class': "image_holder selection_canvas hide"
69 });
70 this.context = this.canvas.get(0).getContext('2d');
71
72 // Create list element div and the related UI objects
73 this.listElement = jQuery('<div/>', {
74 id: this.name + "_element",
75 'class': "selection_list_item"
76 });
77
78 // Container div for the namediv and nameInput </div>s
79 this.innerdiv = jQuery('<div/>', {
80 'class': "selection_innerdiv"
81 });
82
83 // Container for default name text display
84 this.namediv = jQuery('<div/>', {
85 text: this.name,
86 'class': "selection_list_name"
87 });
88
89 // Input field for renaming the selection
90 this.nameInput = jQuery('<input/>', {
91 'class': "selection_list_rename hide"
92 });
93
94 // Create jQuery canvas object for the selection preview
95 this.previewCanvasJQ = jQuery('<canvas/>', {
96 'class': "preview_canvas"
97 });
98
99 // Creates a kill button for the Selection-object
100 this.kill_button = jQuery('<img/>', {
101 // text: "x",
102 src: "<%= asset_path 'killbutton.png' %>",
103 'class': "killButton"
104 });
105
106 // Get the actual DOM object for setting height/width settings
107 this.previewCanvas = this.previewCanvasJQ.get(0);
108 this.previewCanvas.height = 50;
109 this.previewCanvas.width = 50;
110 this.previewContext = this.previewCanvas.getContext('2d');
111
112 // Draws the preview image from this.canvas => this.previewCanvas
113 this.drawPreview = function() {
114 this.previewContext.clearRect(0, 0,
115 this.previewCanvas.width, this.previewCanvas.height);
116 this.previewContext.drawImage(this.context.canvas,
117 0, 0, this.context.canvas.width, this.context.canvas.height,
118 0, 0, this.previewCanvas.width, this.previewCanvas.height);
119 }
120
121 // Bind doubleclick-event for renaming the selection
122 this.innerdiv.bind("dblclick",
123 $.proxy(function() {
124 this.namediv.toggleClass('hide', true);
125 this.nameInput.toggleClass('hide', false);
126 // Quick fix for avoiding #thresholdField getting reset on reload
127 this.nameInput.val(this.name);
128 this.nameInput.focus();
129 },
130 this)
131 );
132
133 // Binds the focusout-event for the nameInput field
134 // -- shows the default name field.
135 this.nameInput.bind('focusout', $.proxy(function() {
136 this.namediv.toggleClass('hide', false);
137 this.nameInput.toggleClass('hide', true);
138 },
139 this));
140
141 // Bind enter-up event for the renaming functionality
142 this.innerdiv.keyup($.proxy(function(event) {
143 if (event.keyCode == 13) {
144 this.nameInput.blur();
145 if (selectionTable[this.nameInput.val()] == null) {
146 // Renames this object and moves it to it's
147 // rightful place in selectionTable
148 this.namediv.text(this.nameInput.val());
149 selectionTable[this.nameInput.val()] = this;
150 delete selectionTable[this.name];
151 this.name = this.nameInput.val();
152 // Update the selection name to the server
153 this.update_name();
154 // More fixes for avoiding #thresholdField resets
155 this.nameInput.val("10");
156 }
157 else if (this.nameInput.val() == this.name) {
158 // Don't complain if name doesn't change.
159 return;
160 } else {
161 // Copy name from the namediv field (which still contains
162 // the old name)
163 alert('Selection ' + this.nameInput.val() + ' already exists!');
164 this.nameInput.val(this.namediv.text());
165 // More fixes for avoiding #thresholdField resets
166 this.nameInput.val("10");
167 }
168 }
169 },
170 this));
171
172 // Function for fixing canvas size to the size of the current image
173 this.setSize = function() {
174 var canv = this.canvas.get(0);
175 canv.width = $('#image_holder').width();
176 canv.height = $('#image_holder').height();
177 canv.globalAlpha = 1;
178 }
179
180 // Appends the canvas under it's parent HTML-container
181 this.positionCanvas = function() {
182 this.canvas.appendTo($('#image_container'));
183 }
184
185 // Updates the selection history to represent the current history
186 this.updateSelectionHistory = function() {
187 var historyContainer = $('#history_list').empty();
188 if (this.historyItems.length != 0) {
189 for (i = 0; i < this.historyItems.length; i++) {
190 this.historyItems[i].prependTo(historyContainer);
191 }
192 $('#selection_history').toggleClass('fade', false);
193 }
194 else $('#selection_history').toggleClass('fade', true);
195 }
196
197 // Removes a history item and updates the view accordingly
198 this.removeHistoryItem = function() {
199 this.historyItems.pop();
200 this.updateSelectionHistory();
201 }
202
203 // Adds a history item
204 // Creates a canvas, copies the contents of this.canvas to it
205 // and prepends it to the selection history list
206 this.addHistoryItem = function() {
207 $('#selection_history').toggleClass('fade', false);
208 var historyContainer = $('#history_list');
209 var image = new Image();
210 var historyElement = jQuery('<canvas/>', {
211 'class': "selection_history_item"
212 });
213 historyPreviewCanvas = historyElement.get(0);
214 historyPreviewCanvas.height = 90;
215 historyPreviewCanvas.width = 90;
216 historyPreviewContext = historyPreviewCanvas.getContext('2d');
217 historyPreviewContext.drawImage(this.context.canvas,
218 0, 0, this.context.canvas.width, this.context.canvas.height,
219 0, 0, historyPreviewCanvas.width, historyPreviewCanvas.height);
220 this.historyItems.push(historyElement);
221 historyElement.prependTo(historyContainer);
222 }
223
224 // Takes a snapshot (png) of the current canvas and pushes it
225 // to the restorePoints array
226 this.takeSnapshot = function() {
227 this.restorePoints.push(this.context.canvas.toDataURL("image/png"));
228 updateUndoButton();
229
230 // Some temporary code for a different kind of snapshot-implementation
231 // - don't remove yet.
232 // var restorePoints = document.createElement('canvas');
233 // restorePoints.width = this.context.canvas.width;
234 // restorePoints.height = this.context.canvas.height;
235 // this.restorePoints.push(restorePoints);
236 // this.restorePoints.getContext('2d')
237 // .putImageData(this.context.getImageData(0, 0,
238 // this.context.canvas.width, this.context.canvas.height), 0, 0);
239 }
240
241 // Undoes the previous selection by restoring a PNG stored in
242 // restorePoints-array,
243 // removes the local data entry from this.data and the remote entry
244 // by a POST ajax call
245 this.undo = function() {
246 if (isUndoUsable()) {
247 this.context.clearRect(0, 0,
248 this.context.canvas.width, this.context.canvas.height);
249 this.previewContext.clearRect(0, 0,
250 this.previewContext.canvas.width,
251 this.previewContext.canvas.height);
252
253 // Creates an image from the restorePoints
254 var img = new Image();
255 img.src = this.restorePoints.pop();
256
257 // Use late binding for making the actual changes to avoid
258 // messing the image up.
259 //We also have to redraw the preview.
260 img.onload = $.proxy(function() {
261 // Switching to source-over is necessary to have the
262 // image drawn correctly
263 this.context.globalCompositeOperation = 'source-over';
264 this.context.drawImage(img, 0, 0);
265 if (deleteMode)
266 this.context.globalCompositeOperation = 'destination-out';
267 this.drawPreview();
268 // This removes the local Selection data
269 this.data.pop();
270 // This removes the remote data
271 this.ajax_undo();
272 this.removeHistoryItem();
273 },
274 this);
275
276 updateUndoButton();
277
278 } else alert("Nothing to undo!"
279 + " You cannot undo selections loaded from the server.");
280 }
281
282 // Undoes the previous selection
283 this.ajax_undo = function() {
284 $.post("/selections/undo", {
285 id: this.id
286 },
287 // Callback function for the delete - implement if needed.
288 function(data) {
289 },
290 "json"
291 );
292 }
293
294 // Enables this selection
295 this.enable = function() {
296 // Disables the previous selection
297 if (activeSelection != this && activeSelection != null) {
298 activeSelection.disable();
299 }
300 // Sets this as the active selection
301 if (activeSelection == null) {
302 if (deleteMode) {
303 this.context.globalCompositeOperation = 'destination-out';
304 }
305 activeSelection = this;
306 this.listElement.toggleClass('list_item_selected', true);
307 this.canvas.toggleClass('hide', false);
308 unlockSelectionTools();
309 this.updateSelectionHistory();
310 updateUndoButton();
311 }
312 }
313
314 // Disables this as the active selection
315 this.disable = function() {
316 this.context.globalCompositeOperation = 'source-over';
317 this.listElement.toggleClass('list_item_selected', false);
318 activeSelection = null;
319 if (!showAllSelections)
320 this.canvas.toggleClass('hide', true);
321 }
322
323 // Handler for the mouseover-events for displaying the selection
324 this.mouseOver = function() {
325 if (activeSelection != this && !showAllSelections) {
326 this.canvas.toggleClass('hide', false);
327 }
328 }
329 this.mouseOut = function() {
330 if (activeSelection != this && !showAllSelections) {
331 this.canvas.toggleClass('hide', true);
332 }
333 }
334
335 // Draws the contents from drawLayer to this.context
336 this.draw = function(canvas) {
337 var tempCanvas = document.createElement('canvas');
338 var tempContext = tempCanvas.getContext('2d');
339 tempCanvas.width = drawLayer.height;
340 tempCanvas.height = drawLayer.width;
341 tempContext.drawImage(drawLayer, 0, 0);
342 this.takeSnapshot();
343 this.addHistoryItem();
344
345 if (canvas == null)
346 this.context.drawImage(drawLayer, 0, 0);
347 else
348 this.context.drawImage(canvas, 0, 0);
349
350 this.drawPreview();
351 }
352
353 // Draws the presentation of selections stored in this.data
354 this.present = function() {
355 if (this.bitmask != null) {
356 // Second argument is an array with the colors [R, G, B, A]
357 // ranging from 0 to 255
358 this.drawBits(this.bitmask, [0, 0, 220, 128]);
359 // [138, 159, 255, 128]);
360 }
361 this.takeSnapshot();
362 }
363
364 // Helper function for drawing to canvas from bitmask with given colors
365 this.drawBits = function(bitMask, color) {
366 // Initialize local variables for clarity
367 var bits = bitMask.bitString;
368 var x0 = bitMask.x;
369 var y0 = bitMask.y;
370 var h = bitMask.h;
371 var w = bitMask.w;
372
373 // Create an empty set of data
374 var imgData = drawCanvas.createImageData(w, h);
375 var dataArray = imgData.data;
376
377 // Loop for iterating through x-y values - forms the image data
378 for (var y = 0; y < h; ++y) {
379 for (var x = 0; x < w; ++x) {
380 var ind = y * w + x;
381 var index = ind * 4
382 if (bits[ind] == 1) {
383 dataArray[index] = color[0]; // red
384 dataArray[++index] = color[1]; // green
385 dataArray[++index] = color[2]; // blue
386 dataArray[++index] = color[3]; // alpha
387 }
388 }
389 }
390
391 // Create temporary canvas to be used with drawImage
392 // NOTE: Usage of putImageData directly to this.context would
393 // cause clipping of overlapping selections, thus the workaround
394 var tempCanvas = document.createElement('canvas');
395 var tempContext = tempCanvas.getContext('2d');
396 tempCanvas.width = w;
397 tempCanvas.height = h;
398 tempContext.putImageData(imgData, 0, 0);
399
400 // Draw the formed image into this selection's canvas at
401 // location (x0, y0)
402 this.context.drawImage(tempCanvas, x0, y0);
403 }
404
405 // Removes the local instances of this Selection
406 this.localDestruct = function() {
407 activeSelection = null;
408 updateUndoButton();
409 this.canvas.remove();
410 // Some candy for the list item removal!
411 this.listElement.remove();
412 delete selectionTable[this.name];
413 setupUIElements();
414 delete this;
415 }
416
417 // Removes this Selection from the server
418 this.destruct = function() {
419 this.localDestruct();
420 this.post_delete();
421 }
422
423 // Sends the selection to the server with a simple POST request when
424 // a fresh selection group is made.
425 // Proxy needed for passing correct scope to the callback function.
426 if (this.id == null) {
427 $.post("/selections/create", {
428 selection: {
429 name: this.name,
430 layered_image_id: this.image
431 }
432 },
433 // Gets the correct mongo ID from the server
434 $.proxy(function(data) {
435 this.id = data._id
436 },
437 this), "json");
438 }
439
440 // Deletes the selection from the server with a simple POST request.
441 this.post_delete = function() {
442 $.post("/selections/destroy", {
443 id: this.id
444 },
445 // Executes this after the call returns.
446 function(data) {
447 },
448 "json"
449 );
450 }
451
452 // Updates the selection with a simple POST request.
453 // Sends the previously made selection only, not all of current selections.
454 this.post_update = function() {
455 $.post("/selections/update", {
456 selection: {
457 name: this.name,
458 bits: this.data[this.data.length - 1]
459 // individual selection bitmasks
460 },
461 id: this.id
462 },
463 // Executes this after the call returns.
464 $.proxy(function(data) {
465 },
466 this),
467 "json"
468 );
469 }
470
471 // Only updates the name of this selection
472 this.update_name = function() {
473 $.post("/selections/update", {
474 selection: {
475 name: this.name,
476 },
477 id: this.id
478 },
479 // Executes this after the call returns.
480 function(data) {
481 },
482 "json"
483 );
484 }
485
486 this.getImages = function() {
487
488 }
489
490 // Locks the selected selection.
491 // Calls /selection/lock as jQuery POST with this.ID as the argument.
492 this.lock = function() {
493 // TODO: Functionality.
494 }
495
496 // Unlocks the selection.
497 // Calls /selection/unlock as jQuery POST with this.ID as the argument.
498 this.unlock = function() {
499 // TODO: Functionality
500 }
501
502 // Binds mouse-event handlers using proxy to pass the correct scope
503 this.listElement.mouseover($.proxy(this.mouseOver, this));
504 this.listElement.mouseout($.proxy(this.mouseOut, this));
505 this.kill_button.click($.proxy(this.destruct, this));
506 this.listElement.click($.proxy(this.enable, this));
507
508 // Combines the jQuery elements to form the final list element
509 this.namediv.appendTo(this.innerdiv);
510 this.nameInput.appendTo(this.innerdiv);
511 this.innerdiv.appendTo(this.listElement);
512 this.previewCanvasJQ.appendTo(this.listElement);
513 this.kill_button.appendTo(this.innerdiv);
514
515 // Adjusts the canvas to match the size of the image
516 this.setSize();
517
518 // Adds the list element to the list and expands the list to correct width
519 this.listElement.hide().appendTo('#selection_list').fadeIn(300);
520 setupUIElements();
521
522 // Positions the canvas after the object has been created.
523 this.positionCanvas();
524}
app/assets/javascripts/analyses/selection_tools.js.erb
(0 / 622)
  
1/* This content is released under the MIT License.
2 *
3 * Please see the full license in the attached LICENCE-file
4 * or at http://www.opensource.org/licenses/mit-license.php
5 *
6 * Copyright (c) 2012 Pekka Iso-Ahola
7 * Copyright (c) 2012 Jussi Perttola
8 * Copyright (c) 2012 Tommi Tuovinen
9 * ---------------------------------------------------------
10 */
11
12// SelectionTool-object, contains the image info as well as the methods
13function SelectionTool(img) {
14 // Initialize variables for holding min- and max-values for x and y.
15 this.x_min = 10000;
16 this.y_min = 10000;
17 this.x_max = 0;
18 this.y_max = 0;
19
20 // Set selectionTool to either drawing (false) or deleting (true) mode
21 this.deleteMode = false;
22 this.locked = false;
23
24 this.img = img;
25 this.image = jQuery('<img/>', {
26 src: this.img,
27 'class': "selectiontool_button",
28 alt: "Alternative text",
29 title: "Image title"
30 });
31 this.image.appendTo($('#selection_tools'));
32
33 selectionTools.push(this);
34
35 // Enables this selectionTool
36 this.enable = function() {
37 if (this.locked) {
38 return;
39 }
40
41 $('.toolsettings').toggleClass('hide', true);
42
43 // Disables the previously used selectionTool
44 if (selectionTool != null && selectionTool != this)
45 selectionTool.disable();
46
47 // Enables the selectionTool
48 selectionTool = this;
49
50 // Toggles the DOM element as selected
51 this.image.toggleClass('list_item_selected', true);
52 }
53
54 // Disables this selectionTool
55 this.disable = function() {
56 this.image.toggleClass('list_item_selected', false);
57 selectionTool = null;
58 }
59
60 // Updates values of x_min, x_max, y_min, y_max
61 this.updateBoundaryPoints = function(ev) {
62 if (ev._x > this.x_max)
63 this.x_max = ev._x;
64 if (ev._x < this.x_min)
65 this.x_min = ev._x;
66 if (ev._y > this.y_max)
67 this.y_max = ev._y;
68 if (ev._y < this.y_min)
69 this.y_min = ev._y;
70 }
71
72 // Resets values of x_min, x_max, y_min, y_max points
73 this.resetBoundaryPoints = function() {
74 this.x_min = 10000;
75 this.y_min = 10000;
76 this.x_max = 0;
77 this.y_max = 0;
78 }
79
80 // Debugging..
81 this.debugpoints = function() {
82 console.log("(" + this.x_min + ", " + this.y_min
83 + ") => (" + this.x_max + ", " + this.y_max + ")");
84 }
85
86 // Bind the click event to selection of the selectionTool.
87 this.image.click($.proxy(this.enable, this));
88 this.enable();
89 // Enabled to avoid unnecessary "selectionTool is null" console output
90}
91
92// Initializes the rectangle tool
93function initRectangleTool() {
94 rectangle = new SelectionTool("<%= asset_path 'rectangle_tool_icon.png' %>");
95 rectangle.name = "rectangle";
96 var selectionTool = rectangle;
97 selectionTool.image.attr('alt', 'Rectangle tool');
98 selectionTool.image.attr('title', 'Rectangle');
99 selectionTool.started = false;
100
101 rectangle.mousedown = function(ev) {
102 startShape(ev);
103 };
104
105 rectangle.mousemove = function(ev) {
106 if (!selectionTool.started) {
107 return;
108 }
109
110 var x = Math.min(ev._x, selectionTool.x0),
111 y = Math.min(ev._y, selectionTool.y0),
112 w = Math.abs(ev._x - selectionTool.x0),
113 h = Math.abs(ev._y - selectionTool.y0);
114
115 // Clears the current rectangles from the view
116 drawCanvas.clearRect(0, 0, drawLayer.width, drawLayer.height);
117
118 if (!w) {
119 w = 1;
120 }
121
122 if (!h) {
123 h = 1;
124 }
125
126 drawCanvas.fillRect(x, y, w, h);
127 };
128
129 rectangle.mouseup = function(ev) {
130 endShape(ev);
131 getBitMask();
132 };
133}
134
135// Initializes the ellipse tool.
136function initEllipseTool() {
137 ellipse = new SelectionTool("<%= asset_path 'ellipse_tool_icon.png' %>");
138 ellipse.name = "ellipse";
139 var selectionTool = ellipse;
140 selectionTool.image.attr('alt', 'Ellipse tool');
141 selectionTool.image.attr('title', 'Ellipse');
142 selectionTool.started = false;
143
144 ellipse.mousedown = function(ev) {
145 startShape(ev);
146 };
147
148 ellipse.mousemove = function(ev) {
149 if (!selectionTool.started) {
150 return;
151 }
152
153 var x = Math.min(ev._x, selectionTool.x0),
154 y = Math.min(ev._y, selectionTool.y0),
155 w = Math.abs(ev._x - selectionTool.x0),
156 h = Math.abs(ev._y - selectionTool.y0);
157
158 // Clears the current rectangles from the view
159 drawCanvas.clearRect(0, 0, drawLayer.width, drawLayer.height);
160
161 if (!w)
162 w = 1;
163
164 if (!h)
165 h = 1;
166
167 drawEllipse(x, y, w, h);
168
169 };
170
171 ellipse.mouseup = function(ev) {
172 endShape(ev);
173 getBitMask();
174 };
175}
176
177function drawEllipse(x, y, width, height) {
178
179 var kappa = .5522848, //Curvature of a curve
180 offsetX = (width / 2) * kappa,
181 offsetY = (height / 2) * kappa,
182 xEnd = x + width,
183 yEnd = y + height,
184 xMiddle = x + width / 2,
185 yMiddle = y + height / 2;
186
187 drawCanvas.beginPath();
188 drawCanvas.moveTo(x, yMiddle);
189 drawCanvas.bezierCurveTo(x, yMiddle - offsetY, xMiddle - offsetX,
190 y, xMiddle, y);
191 drawCanvas.bezierCurveTo(xMiddle + offsetX, y, xEnd, yMiddle - offsetY,
192 xEnd, yMiddle);
193 drawCanvas.bezierCurveTo(xEnd, yMiddle + offsetY, xMiddle + offsetX, yEnd,
194 xMiddle, yEnd);
195 drawCanvas.bezierCurveTo(xMiddle - offsetX, yEnd, x, yMiddle + offsetY,
196 x, yMiddle);
197
198 drawCanvas.fill();
199 drawCanvas.closePath();
200
201}
202
203function startShape(ev) {
204 if (activeSelection != null) {
205 if (deleteMode)
206 // Deleting is marked with red color
207 drawCanvas.fillStyle = 'rgb(220, 0, 0)';
208 else
209 drawCanvas.fillStyle = activeSelection.fillStyle;
210 }
211
212 if (!selectionTool.started)
213 selectionTool.resetBoundaryPoints();
214
215 // Delete mode requires non-transparent color, otherwise the opacities will be
216 // added together, which equals to non-zero opacity.
217 if (deleteMode)
218 drawCanvas.globalAlpha = 1;
219 else
220 drawCanvas.globalAlpha = 0.75;
221
222 selectionTool.started = true;
223
224 // Store initial points
225 selectionTool.x0 = ev._x;
226 selectionTool.y0 = ev._y;
227
228 // Updates boundary points for the selectionTool
229 selectionTool.updateBoundaryPoints(ev);
230}
231
232function endShape(ev) {
233 if (selectionTool.started) {
234 selectionTool.mousemove(ev);
235 selectionTool.updateBoundaryPoints(ev);
236 selectionTool.started = false;
237 if (activeSelection != null)
238 activeSelection.draw();
239 drawCanvas.clearRect(0, 0, drawLayer.width, drawLayer.height);
240
241 // Set final points
242 selectionTool.x1 = ev._x;
243 selectionTool.y1 = ev._y;
244 }
245}
246
247
248// Initializes the polygon (alias point to point free hand selection) tool.
249function initPolygonTool() {
250 polygon = new SelectionTool("<%= asset_path 'polygon_tool_icon.png' %>");
251 polygon.name = "polygon";
252 var selectionTool = polygon;
253 selectionTool.image.attr('alt', 'Polygon tool');
254 selectionTool.image.attr('title', 'Polygon');
255 selectionTool.started = false;
256 var points = [];
257 var startRadius = 10;
258
259
260 line_canvas = $('#help_layer').get(0);
261 line_canvas.width = $('#draw_layer').width();
262 line_canvas.height = $('#draw_layer').height();
263 line_context = line_canvas.getContext("2d");
264
265 polygon.mousedown = function(ev) {
266
267 if (!selectionTool.started) {
268 selectionTool.resetBoundaryPoints();
269 selectionTool.startX = ev._x;
270 selectionTool.startY = ev._y;
271 selectionTool.started = true;
272 points.push([selectionTool.startX, selectionTool.startY]);
273 // adds starting point to array
274 startShape(ev);
275
276 line_context.beginPath();
277 line_context.arc(selectionTool.startX, selectionTool.startY,
278 startRadius/zoomScale+1, 0, 2 * Math.PI, false);
279
280 line_context.lineWidth = 1;
281 line_context.strokeStyle = "#0000ff";
282 line_context.stroke();
283
284 return;
285 }
286
287 var distanceToStart = Math.sqrt(Math.pow(ev._x - selectionTool.startX, 2)
288 + Math.pow(ev._y - selectionTool.startY, 2))
289 if (distanceToStart < startRadius/zoomScale+1 && points.length > 2) {
290 selectionTool.started = false;
291 selectionTool.updateBoundaryPoints(ev);
292 line_context.clearRect(0, 0, drawLayer.width, drawLayer.height);
293 drawFilledPolygon();
294 getBitMask();
295 points = [];
296
297 return;
298 }
299
300 line_context.drawImage(drawLayer, 0, 0);
301 drawCanvas.clearRect(0, 0, drawLayer.width, drawLayer.height);
302 points.push([ev._x, ev._y]);
303 startShape(ev);
304 };
305
306 polygon.mousemove = function(ev) {
307 if (!selectionTool.started) {
308 return;
309 }
310
311 // Clears the current rectangles from the view
312 drawCanvas.clearRect(0, 0, drawLayer.width, drawLayer.height);
313
314 drawCanvas.beginPath();
315 drawCanvas.moveTo(selectionTool.x0, selectionTool.y0);
316 drawCanvas.lineTo(ev._x, ev._y);
317 $('#image_holder').get(0).getContext('2d')
318 drawCanvas.strokeStyle = "#ff0000";
319 drawCanvas.stroke();
320 drawCanvas.closePath();
321 };
322
323 polygon.mouseup = function(ev) {
324 // Initialize function to avoid unnecessary errors
325 };
326
327 function drawFilledPolygon() {
328 drawCanvas.clearRect(0, 0, drawLayer.width, drawLayer.height);
329 drawCanvas.beginPath();
330 drawCanvas.moveTo(selectionTool.startX, selectionTool.startY);
331 for (var i = 1; i < points.length; i++) {
332 drawCanvas.lineTo(points[i][0], points[i][1]);
333 }
334 drawCanvas.closePath();
335
336 drawCanvas.fill();
337 if (activeSelection != null)
338 activeSelection.draw();
339 drawCanvas.clearRect(0, 0, drawLayer.width, drawLayer.height);
340 }
341}
342
343// Initializes the magic wand tool.
344function initMagicWandTool() {
345 wand = new SelectionTool("<%= asset_path 'magic_wand_tool_icon.png' %>");
346 wand.name = "wand";
347 var selectionTool = wand;
348 selectionTool.image.attr('alt', 'Area selection tool');
349 selectionTool.image.attr('title', 'Area selection');
350
351 wand.image.click(function() {
352 if (selectionTool === wand) {
353 $('.toolsettings').toggleClass('hide', false);
354
355 }
356 });
357
358 wand.mousedown = function(ev) {
359 var grayThreshold = $('#thresholdField').val();
360
361 if (activeSelection == null) {
362 return;
363 }
364
365 //gets color values that belong to current selection
366 var startIndex = activeSelection.fillStyle.indexOf('(', 0);
367 var endIndex = activeSelection.fillStyle.indexOf(')', 0);
368 var colors = activeSelection.fillStyle
369 .substring(startIndex + 1, endIndex)
370 .split(',');
371 var drawR = parseInt(colors[0]);
372 var drawG = parseInt(colors[1]);
373 var drawB = parseInt(colors[2]);
374
375 var drawContext = $('#image_holder').get(0).getContext('2d');
376 var canvasWidth = drawContext.canvas.width;
377 var canvasHeight = drawContext.canvas.height;
378
379 var canvasdata = drawContext.getImageData(
380 0, 0, drawContext.canvas.width, drawContext.canvas.height);
381 var startPos = (ev._y * canvasWidth + ev._x) * 4;
382
383 var startGraysum = 0.30 * canvasdata.data[startPos]
384 + 0.59 * canvasdata.data[startPos + 1]
385 + 0.11 * canvasdata.data[startPos + 2];
386
387 var imageData = drawCanvas.createImageData(canvasWidth, canvasHeight);
388
389 validateThreshold();
390
391 var unscaledThreshold = parseInt($('#thresholdField').val());
392
393 var grayThreshold = (unscaledThreshold - (unscaledThreshold) *0.3)
394 /100.0 * 255.0;
395
396 var pixelStack = [[ev._x, ev._y]];
397
398 //uses luma. Luma is weighted average of gamma-corrected R, G, and B
399 function matchStartColor(pixelPos)
400 {
401 if ( canvasdata.data[pixelPos] === imageData.data[pixelPos]
402 && canvasdata.data[pixelPos + 1] === imageData.data[pixelPos + 1]
403 && canvasdata.data[pixelPos + 2] === imageData.data[pixelPos + 2])
404 return false;
405
406 var endGraysum = 0.30 * canvasdata.data[pixelPos]
407 + 0.59 * canvasdata.data[pixelPos + 1]
408 + 0.11 * canvasdata.data[pixelPos + 2];
409
410 return (endGraysum < startGraysum + grayThreshold
411 && endGraysum > startGraysum - grayThreshold);
412 }
413
414 function colorPixel(i)
415 {
416 canvasdata.data[i] = drawR;
417 canvasdata.data[i + 1] = drawG;
418 canvasdata.data[i + 2] = drawB;
419 canvasdata.data[i + 3] = 180;//TODO
420
421 imageData.data[i] = drawR;
422 imageData.data[i + 1] = drawG;
423 imageData.data[i + 2] = drawB;
424 imageData.data[i + 3] = 180;//TODO
425 }
426
427
428 //This code is modified from this tutorial:
429 // http://www.williammalone.com/articles
430 // /html5-canvas-javascript-paint-bucket-tool/
431 // (c) William Malone
432 function executeMagicWand() {
433 while (pixelStack.length)
434 {
435 var newPos,
436 x,
437 y,
438 pixelPos,
439 reachLeft,
440 reachRight;
441
442 newPos = pixelStack.pop();
443 x = newPos[0];
444 y = newPos[1];
445
446 if (x < selectionTool.x_min) selectionTool.x_min = x;
447 if (x > selectionTool.x_max) selectionTool.x_max = x;
448
449 pixelPos = (y * canvasWidth + x) * 4;
450
451 if (y <= selectionTool.y_min)
452 selectionTool.y_min = y;
453
454 while (y-->=0 && matchStartColor(pixelPos))
455 {
456 pixelPos -= canvasWidth * 4;
457 if (y <= selectionTool.y_min)
458 selectionTool.y_min = y;
459 }
460 pixelPos += canvasWidth * 4;
461 ++y;
462
463 if (y > selectionTool.y_max)
464 selectionTool.y_max = y;
465
466 reachLeft = false;
467 reachRight = false;
468 while (y++<canvasHeight - 1 && matchStartColor(pixelPos))
469 {
470 if (y > selectionTool.y_max)
471 selectionTool.y_max = y;
472
473 colorPixel(pixelPos);
474
475 if (x > 0)
476 {
477 if (matchStartColor(pixelPos - 4))
478 {
479 if (!reachLeft) {
480 pixelStack.push([x - 1, y]);
481 reachLeft = true;
482 }
483 }
484 else if (reachLeft)
485 {
486 reachLeft = false;
487 }
488 }
489
490 if (x < canvasWidth - 1)
491 {
492 if (matchStartColor(pixelPos + 4))
493 {
494 if (!reachRight)
495 {
496 pixelStack.push([x + 1, y]);
497 reachRight = true;
498 }
499 }
500 else if (reachRight)
501 {
502 reachRight = false;
503 }
504 }
505
506 pixelPos += canvasWidth * 4;
507 }
508
509 }
510 }
511
512 selectionTool.resetBoundaryPoints();
513 executeMagicWand()
514
515 var tempCanvas = document.createElement('canvas');
516 var tempContext = tempCanvas.getContext('2d');
517 tempCanvas.width = canvasWidth;
518 tempCanvas.height = canvasHeight;
519
520 tempContext.putImageData(imageData, 0, 0);
521 activeSelection.draw(tempCanvas);
522
523 drawCanvas.clearRect(0, 0, drawLayer.width, drawLayer.height);
524 }
525
526 wand.mouseup = function(ev) {
527 if (activeSelection == null) {
528 return;
529 }
530
531 getBitMask();
532 }
533
534}
535
536// Locks selection tools
537function lockSelectionTools() {
538 $('.selectiontool_button').toggleClass('fade', true);
539 for (sTool in selectionTools) {
540 selectionTools[sTool].locked = true;
541 }
542
543 if (selectionTool != null)
544 selectionTool.disable();
545}
546
547// Unlocks selection tools
548function unlockSelectionTools() {
549
550 $('.selectiontool_button').toggleClass('fade', false);
551 for (sTool in selectionTools)
552 selectionTools[sTool].locked = false;
553
554 // Use rectangle as default selectionTool
555 if (selectionTool == null)
556 rectangle.enable();
557}
558
559
560/* Gets pixels from the current canvas limited by the
561 * rectangle (x_min, y_min), (x_max, y_max),
562 * forms the corresponding bitmask, pushes the data into the
563 * current selection's data table and
564 * syncs the selection to the server.
565 */
566function getBitMask() {
567 var x_max = selectionTool.x_max;
568 var y_max = selectionTool.y_max;
569 var x_min = selectionTool.x_min;
570 var y_min = selectionTool.y_min;
571
572 var w = Math.max(1, Math.abs(x_max - x_min));
573 var h = Math.max(1, Math.abs(y_max - y_min));
574
575 if (x_min < 0) x_min = 0;
576 if (y_min < 0) y_min = 0;
577
578 // getImageData returns an alpha-RBG array, ie. one pixel in the
579 // image is represented by 4 array elements (red, blue, green, alpha)
580 var pixelData = activeSelection.context
581 .getImageData(x_min, y_min, w, h).data;
582
583 // Form the bitmask as a string to avoid JSON overhead from using arrays
584 var bitString = checkPixels(pixelData, w, h);
585
586 // Form a container object for JSON serialization.
587 // Stores the bitMask as well as the bottom left coordinates,
588 // height and width of the bounding box.
589 activeSelection.storeBitmask({
590 selectionTool: selectionTool.name,
591 x: x_min,
592 y: y_min,
593 h: y_max - y_min,
594 w: x_max - x_min,
595 bitString: bitString
596 });
597
598 // Updates the selection
599 activeSelection.post_update();
600}
601
602 /* Check every 4th pixel starting from index 3 (being the alpha)
603 * and form the bitmask
604 * array element == 0 => pixel is not selected
605 * array element == 1 => pixel is selected
606 * array element == 2 => pixel is marked for removal
607 */
608function checkPixels(pixelData, width, height) {
609 var zerobit = deleteMode ? "2": "0";
610 var mask = "";
611 for (var y = 0; y < height; ++y) {
612 for (var x = 0; x < width; ++x) {
613 var ind = (y * width + x) * 4 + 3;
614 if (pixelData[ind] != 0) {
615 mask += "1";
616 } else {
617 mask += zerobit;
618 }
619 }
620 }
621 return mask;
622}
app/assets/javascripts/analyses/tools.js.erb
(0 / 156)
  
1/* This content is released under the MIT License.
2 *
3 * Please see the full license in the attached LICENCE-file
4 * or at http://www.opensource.org/licenses/mit-license.php
5 *
6 * Copyright (c) 2012 Pekka Iso-Ahola
7 * Copyright (c) 2012 Jussi Perttola
8 * Copyright (c) 2012 Tommi Tuovinen
9 * ---------------------------------------------------------
10 * A Selection-object, represents a single selection selection group.
11 * It contains the related bit masks, canvases
12 * and other UI elements.
13*/
14
15// Button with image.
16function ToolButton(img, altText, title) {
17 this.image = jQuery('<img/>', {
18 src: img,
19 'class': "toolbar_item",
20 alt: "image missing"
21 });
22
23 if (title == null)
24 title = altText;
25
26 this.image.attr('alt',altText);
27 this.image.attr('title',title);
28 this.image.appendTo($('#toolbar'));
29}
30
31// Initializes show all button.
32function initShowAllButton() {
33 showAll = new ToolButton("<%= asset_path 'show_all_icon.png' %>",
34 "Toggle show all selections");
35 this.toggled = false;
36
37 // Toggles visibility of all available selections
38 function toggleAllSelections() {
39 if(!showAllSelections) {
40 showAll.image.toggleClass('list_item_selected', true);
41 $('.selection_canvas').toggleClass('hide', false);
42 showAllSelections = true;
43 } else {
44 showAll.image.toggleClass('list_item_selected', false);
45 $('.selection_canvas').toggleClass('hide', true);
46 if(activeSelection != null)
47 activeSelection.canvas.toggleClass('hide', false);
48 showAllSelections = false;
49 }
50 }
51
52 showAll.image.click(toggleAllSelections);
53}
54
55// Initializes delete mode button.
56function initDeleteModeButton() {
57 deleteModeButton = new ToolButton("<%= asset_path 'delete_icon.png' %>",
58 "Toggle delete mode");
59
60 // Changes deleteMode from true to false and vice versa
61 function toggleDeleteMode() {
62 if (!deleteMode) {
63 if(activeSelection != null)
64 activeSelection.context.globalCompositeOperation = 'destination-out';
65 deleteModeButton.image.toggleClass('list_item_selected', true);
66 deleteMode = true;
67 } else {
68 if(activeSelection != null)
69 activeSelection.context.globalCompositeOperation = 'source-over';
70 deleteModeButton.image.toggleClass('list_item_selected', false);
71 deleteMode = false;
72 }
73 }
74 deleteModeButton.image.click(toggleDeleteMode);
75}
76
77//Zooms image and selections. Uses CSS transform attribute.
78function zoom(zoomScale) {
79
80 $('.image_holder').css('-moz-transform', 'scale(' + zoomScale + ')');
81 $('.image_holder').css('-moz-transform-origin', '0 0');
82
83 $('.image_holder').css('-webkit-transform', 'scale(' + zoomScale + ')');
84 $('.image_holder').css('-webkit-transform-origin', '0 0');
85
86 $('.image_holder').css('-o-transform', 'scale(' + zoomScale + ')');
87 $('.image_holder').css('-o-transform-origin', '0 0');
88
89 $('.image_holder').css('-ms-transform', 'scale(' + zoomScale + ')');
90 $('.image_holder').css('-ms-transform-origin', '0 0');
91
92 $('.image_holder').css('transform', 'scale(' + zoomScale + ')');
93 $('.image_holder').css('transform-origin', '0 0');
94}
95
96
97// Initializes the zoom in tool.
98function initZoomInTool() {
99 zoomIn = new ToolButton("<%= asset_path 'zoom_in_tool_icon.png' %>", "Zoom in");
100 zoomIn.image.click( function() {
101 if (zoomScale >= MAX_ZOOM)
102 return;
103
104 zoomScale *= 2;
105 zoom(zoomScale);
106 });
107}
108
109// Initializes the zoom out tool.
110function initZoomOutTool() {
111 zoomOut = new ToolButton("<%= asset_path 'zoom_out_tool_icon.png' %>","Zoom out");
112 zoomOut.image.click( function() {
113 if (zoomScale <= MIN_ZOOM)
114 return;
115
116 zoomScale /= 2;
117 zoom(zoomScale);
118 });
119}
120
121//TODO: restorePoints has a bug! Selection loaded from server has one restorePoint!?
122// Bug is probably in selection class in takeSnapshot function.
123function isUndoUsable() {
124 if (activeSelection == null || activeSelection.restorePoints == null)
125 return false;
126 else if (activeSelection.restorePoints.length > 0)
127 return true;
128 else
129 return false;
130}
131
132// Changes opacity of undo button.
133function updateUndoButton() {
134 if (isUndoUsable()) {
135 undoTool.image.toggleClass('fade', false);
136 } else {
137 undoTool.image.toggleClass('fade', true);
138 }
139}
140
141
142// Initializes the undo tool.
143function initUndoTool() {
144 undoTool = new ToolButton("<%= asset_path 'undo_icon.png' %>",
145 "Undo last selection","Undo");
146
147 undoTool.image.click( function() {
148 if (!isUndoUsable())
149 return;
150
151 activeSelection.undo();
152 updateUndoButton();
153 });
154
155 undoTool.image.toggleClass('fade', true);
156}
app/assets/javascripts/selections/index.js
(3 / 0)
  
1// Place all the behaviors and hooks related to the matching controller here.
2// All this logic will automatically be available in application.js.
3//= require_tree .
app/assets/javascripts/selections/layeredimage_class.js.erb
(258 / 0)
  
1/* This content is released under the MIT License.
2 *
3 * Please see the full license in the attached LICENCE-file
4 * or at http://www.opensource.org/licenses/mit-license.php
5 *
6 * Copyright (c) 2012 Pekka Iso-Ahola
7 * Copyright (c) 2012 Jussi Perttola
8 * Copyright (c) 2012 Tommi Tuovinen
9 * ---------------------------------------------------------
10 * A LayeredImage, represents a single hyperspectral image.
11*/
12function LayeredImage(obj) {
13 // Check whether a LayeredImage with this name already exists.
14 layeredImageTable[name] = this;
15
16 this.currentLayer = null;
17
18 if (obj == null)
19 return;
20
21 if (obj._id != null)
22 this.id = obj._id;
23
24 if (obj.name != null)
25 this.name = obj.name;
26
27 if (obj.attachment_filename != null)
28 this.filename = obj.attachment_filename;
29
30 if (obj.layer_count != null) {
31 this.layerCount = obj.layer_count;
32 this.currentLayer = Math.floor(this.layerCount/2);
33 }
34
35 this.img = null;
36
37 // Create list element div for the LayeredImage object
38 this.listElement = jQuery('<div/>', {
39 id: this.name + "_element",
40 'class': "selection_list_item"
41 });
42
43 // Container div for the namediv and nameInput </div>s
44 this.innerdiv = jQuery('<div/>', {
45 'class': "selection_innerdiv"
46 });
47
48 // Container for default name text display
49 this.namediv = jQuery('<div/>', {
50 text: this.name,
51 'class': "selection_list_name"
52 });
53
54 // Input field for renaming this
55 this.nameInput = jQuery('<input/>', {
56 'class': "selection_list_rename hide"
57 });
58
59 // Create jQuery canvas object for the LayeredImage preview image
60 this.previewCanvasJQ = jQuery('<canvas/>', {
61 'class': "preview_canvas"
62 });
63
64 // Get the actual DOM object for height/width settings
65 this.previewCanvas = this.previewCanvasJQ.get().pop();
66 this.previewCanvas.height = 50;
67 this.previewCanvas.width = 50;
68 this.previewContext = this.previewCanvas.getContext('2d');
69
70 // Draws the preview image from this.canvas => this.previewCanvas
71 this.drawPreview = function() {
72 // Loads the first image as the active image
73 this.previewContext.clearRect(0, 0, this.previewCanvas.width,
74 this.previewCanvas.height);
75 this.previewContext.drawImage(this.img,
76 0, 0, this.img.width, this.img.height,
77 0, 0, this.previewCanvas.width, this.previewCanvas.height);
78 }
79
80 // Append the elements into innerdiv and innerdiv into listElement
81 this.namediv.appendTo(this.innerdiv);
82 this.nameInput.appendTo(this.innerdiv);
83 this.innerdiv.appendTo(this.listElement);
84 this.previewCanvasJQ.appendTo(this.listElement);
85
86 // Bind doubleclick-event for renaming this
87 this.innerdiv.bind("dblclick",
88 $.proxy(function() {
89 this.namediv.toggleClass('hide', true);
90 this.nameInput.toggleClass('hide', false);
91 // Quick fix for avoiding #thresholdField getting reset on reload
92 this.nameInput.val(this.name);
93 this.nameInput.focus();
94 },
95 this));
96
97 // Binds the focusout-event for the nameInput field.
98 // Shows the default name field.
99 this.nameInput.bind('focusout', $.proxy(function() {
100 this.namediv.toggleClass('hide', false);
101 this.nameInput.toggleClass('hide', true);
102 },
103 this));
104
105 // Bind enter-up event for the renaming functionality
106 this.innerdiv.keyup($.proxy(function(event) {
107 if (event.keyCode == 13) {
108 this.nameInput.blur();
109 if (layeredImageTable[this.nameInput.val()] == null) {
110 // Renames this object and moves it to it's rightful
111 // place in layeredImageTable
112 this.namediv.text(this.nameInput.val());
113 layeredImageTable[this.nameInput.val()] = this;
114 delete layeredImageTable[this.name];
115 this.name = this.nameInput.val();
116 // Update the LayeredImage name to the server
117 this.update_name();
118 // More fixes for avoiding #thresholdField resets
119 this.nameInput.val("10");
120 }
121 else if (this.nameInput.val() == this.name) {
122 // Don't complain if name doesn't change.
123 return;
124 } else {
125 // Copy name from the namediv field (which still contains
126 // the old name)
127 alert('Image ' + this.nameInput.val() + ' already exists!');
128 this.nameInput.val(this.namediv.text());
129 // More fixes for avoiding #thresholdField resets
130 this.nameInput.val("10");
131 }
132 }
133 },
134 this));
135
136 // Adds the list element, calls setupUIElements to expand the
137 // list to match correct dimensions
138 this.listElement.hide().appendTo('#image_list').fadeIn(300);
139 setupUIElements();
140
141 // Activates this image
142 this.enable = function() {
143 // Disables the previous image
144 if (activeImage != this && activeImage != null) {
145 activeImage.disable();
146 activeImage = null;
147 }
148 // Sets this as the active image
149 if (activeImage == null) {
150 activeImage = this;
151
152 this.listElement.toggleClass('list_item_selected', true);
153
154 loadSelections();
155 imageListItemEnabled();
156 }
157 }
158
159 // Disables this image
160 this.disable = function() {
161 this.listElement.toggleClass('list_item_selected', false);
162 }
163
164 // Proxies the correct scope for the click-event
165 this.listElement.click($.proxy(this.enable, this));
166
167 // Destructor for the object - removes the list element as well as the
168 // canvas. Also removes the remaining JS objects.
169 // NOTE: This is not bound to any events and is only used for debugging
170 // purposes.
171 this.destruct = function() {
172 this.post_delete();
173 // Some candy for the list item removal!
174 this.listElement.fadeOut(300,
175 function() {
176 $(this).remove();
177 });
178 setupUIElements();
179 delete this;
180 }
181
182 // Function for debugging purposes, should not be used.
183 this.post_delete = function() {
184 $.post("/layered_images/destroy", {
185 id: this.id
186 },
187 // Executes this after the call returns.
188 function(data) {
189 //console.log('delete successful');
190 },
191 "json"
192 );
193 }
194
195 // Only updates the name of this this
196 this.update_name = function() {
197 $.post("/layered_images/update_name", {
198 layered_image: {
199 name: this.name,
200 },
201 id: this.id
202 },
203 // Executes this after the call returns.
204 function(data) {
205 //console.log('update successful');
206 },
207 "json"
208 );
209 }
210
211 // Gets an image at index imageNumber and draws it to the view.
212 // NOTE: Does not overwrite the preview image.
213 this.getImage = function(imageNumber) {
214 this.currentLayer = imageNumber;
215 $.ajax({
216 type: 'POST',
217 url: "/layered_images/get_image",
218 data: {
219 index: imageNumber,
220 id: this.id
221 },
222 success: $.proxy(function(data) {
223 this.img = new Image();
224 this.img.src = data.data.toString();
225 this.img.onload = function() {
226 updateCanvases();
227 }
228 }, this),
229 dataType: "json"
230 });
231 }
232
233 // Gets and draws the preview. Uses the image in index max/2.
234 // NOTE: Only a small difference to getImage function.
235 // TODO: Pass onload-function as a parameter? Problems with proxying
236 // "this" as well as a function.
237 this.getPreviewImage = function() {
238 this.currentLayer = Math.floor(this.layerCount/2);
239 $.ajax({
240 type: 'POST',
241 url: "/layered_images/get_image",
242 data: {
243 index: this.currentLayer,
244 id: this.id
245 },
246 success: $.proxy(function(data) {
247 this.img = new Image();
248 this.img.src = data.data.toString();
249 this.img.onload = $.proxy(function() {
250 this.drawPreview();
251 }, this)
252 }, this),
253 dataType: "json"
254 });
255 }
256
257 this.getPreviewImage();
258}
app/assets/javascripts/selections/selection_class.js.erb
(524 / 0)
  
1/* This content is released under the MIT License.
2 *
3 * Please see the full license in the attached LICENCE-file
4 * or at http://www.opensource.org/licenses/mit-license.php
5 *
6 * Copyright (c) 2012 Pekka Iso-Ahola
7 * Copyright (c) 2012 Jussi Perttola
8 * Copyright (c) 2012 Tommi Tuovinen
9 * ---------------------------------------------------------
10 * A Selection-object, represents a single selection group.
11 * It contains the related bit masks, canvases
12 * and other UI elements.
13*/
14function Selection(name, id, bitmask) {
15 // Check whether a selection with this name already exists.
16 if (selectionTable[name] == null) {
17 selectionTable[name] = this;
18 }
19 else {
20 alert('Selection ' + name + ' already exists!');
21 }
22
23 this.image = activeImage.id; // sets the selection under the active image
24
25 // Initialize an array for holding restore points to be used
26 // with the undo functionality
27 this.restorePoints = [];
28
29 // Initialize an array for holding the small undo canvases
30 this.historyItems = [];
31
32 this.fillStyle = 'rgb(0, 200, 0)';
33
34 if (id != null) {
35 this.id = id;
36 }
37
38 this.name = name;
39
40 // Bitmask is the bitmask that has been downloaded from the server
41 this.bitmask = null;
42 if (bitmask != null)
43 this.bitmask = bitmask;
44
45 /* Initializes the data array, containing the selections
46 * that are made locally.
47 * Data format is a following object:
48 {
49 selectionTool: Name of the tool used
50 x: x_min, Lowest x-coordinate
51 y: y_min, Lowest y-coordinate (origin is top-left)
52 h: y_max - y_min, Height of the selection
53 w: x_max - x_min, Width of the selection
54 bitString: bitMask Bitmask of the selection
55 -- string with a length of h*w
56 }
57 */
58 this.data = [];
59
60 // Helper function for storing a bitmask, called by the tools
61 this.storeBitmask = function(bitmask) {
62 this.data.push(bitmask);
63 }
64
65 // Create canvas-element and fetch it's context for the Selection object
66 this.canvas = jQuery('<canvas/>', {
67 id: this.name + "_canvas",
68 'class': "image_holder selection_canvas hide"
69 });
70 this.context = this.canvas.get(0).getContext('2d');
71
72 // Create list element div and the related UI objects
73 this.listElement = jQuery('<div/>', {
74 id: this.name + "_element",
75 'class': "selection_list_item"
76 });
77
78 // Container div for the namediv and nameInput </div>s
79 this.innerdiv = jQuery('<div/>', {
80 'class': "selection_innerdiv"
81 });
82
83 // Container for default name text display
84 this.namediv = jQuery('<div/>', {
85 text: this.name,
86 'class': "selection_list_name"
87 });
88
89 // Input field for renaming the selection
90 this.nameInput = jQuery('<input/>', {
91 'class': "selection_list_rename hide"
92 });
93
94 // Create jQuery canvas object for the selection preview
95 this.previewCanvasJQ = jQuery('<canvas/>', {
96 'class': "preview_canvas"
97 });
98
99 // Creates a kill button for the Selection-object
100 this.kill_button = jQuery('<img/>', {
101 // text: "x",
102 src: "<%= asset_path 'killbutton.png' %>",
103 'class': "killButton"
104 });
105
106 // Get the actual DOM object for setting height/width settings
107 this.previewCanvas = this.previewCanvasJQ.get(0);
108 this.previewCanvas.height = 50;
109 this.previewCanvas.width = 50;
110 this.previewContext = this.previewCanvas.getContext('2d');
111
112 // Draws the preview image from this.canvas => this.previewCanvas
113 this.drawPreview = function() {
114 this.previewContext.clearRect(0, 0,
115 this.previewCanvas.width, this.previewCanvas.height);
116 this.previewContext.drawImage(this.context.canvas,
117 0, 0, this.context.canvas.width, this.context.canvas.height,
118 0, 0, this.previewCanvas.width, this.previewCanvas.height);
119 }
120
121 // Bind doubleclick-event for renaming the selection
122 this.innerdiv.bind("dblclick",
123 $.proxy(function() {
124 this.namediv.toggleClass('hide', true);
125 this.nameInput.toggleClass('hide', false);
126 // Quick fix for avoiding #thresholdField getting reset on reload
127 this.nameInput.val(this.name);
128 this.nameInput.focus();
129 },
130 this)
131 );
132
133 // Binds the focusout-event for the nameInput field
134 // -- shows the default name field.
135 this.nameInput.bind('focusout', $.proxy(function() {
136 this.namediv.toggleClass('hide', false);
137 this.nameInput.toggleClass('hide', true);
138 },
139 this));
140
141 // Bind enter-up event for the renaming functionality
142 this.innerdiv.keyup($.proxy(function(event) {
143 if (event.keyCode == 13) {
144 this.nameInput.blur();
145 if (selectionTable[this.nameInput.val()] == null) {
146 // Renames this object and moves it to it's
147 // rightful place in selectionTable
148 this.namediv.text(this.nameInput.val());
149 selectionTable[this.nameInput.val()] = this;
150 delete selectionTable[this.name];
151 this.name = this.nameInput.val();
152 // Update the selection name to the server
153 this.update_name();
154 // More fixes for avoiding #thresholdField resets
155 this.nameInput.val("10");
156 }
157 else if (this.nameInput.val() == this.name) {
158 // Don't complain if name doesn't change.
159 return;
160 } else {
161 // Copy name from the namediv field (which still contains
162 // the old name)
163 alert('Selection ' + this.nameInput.val() + ' already exists!');
164 this.nameInput.val(this.namediv.text());
165 // More fixes for avoiding #thresholdField resets
166 this.nameInput.val("10");
167 }
168 }
169 },
170 this));
171
172 // Function for fixing canvas size to the size of the current image
173 this.setSize = function() {
174 var canv = this.canvas.get(0);
175 canv.width = $('#image_holder').width();
176 canv.height = $('#image_holder').height();
177 canv.globalAlpha = 1;
178 }
179
180 // Appends the canvas under it's parent HTML-container
181 this.positionCanvas = function() {
182 this.canvas.appendTo($('#image_container'));
183 }
184
185 // Updates the selection history to represent the current history
186 this.updateSelectionHistory = function() {
187 var historyContainer = $('#history_list').empty();
188 if (this.historyItems.length != 0) {
189 for (i = 0; i < this.historyItems.length; i++) {
190 this.historyItems[i].prependTo(historyContainer);
191 }
192 $('#selection_history').toggleClass('fade', false);
193 }
194 else $('#selection_history').toggleClass('fade', true);
195 }
196
197 // Removes a history item and updates the view accordingly
198 this.removeHistoryItem = function() {
199 this.historyItems.pop();
200 this.updateSelectionHistory();
201 }
202
203 // Adds a history item
204 // Creates a canvas, copies the contents of this.canvas to it
205 // and prepends it to the selection history list
206 this.addHistoryItem = function() {
207 $('#selection_history').toggleClass('fade', false);
208 var historyContainer = $('#history_list');
209 var image = new Image();
210 var historyElement = jQuery('<canvas/>', {
211 'class': "selection_history_item"
212 });
213 historyPreviewCanvas = historyElement.get(0);
214 historyPreviewCanvas.height = 90;
215 historyPreviewCanvas.width = 90;
216 historyPreviewContext = historyPreviewCanvas.getContext('2d');
217 historyPreviewContext.drawImage(this.context.canvas,
218 0, 0, this.context.canvas.width, this.context.canvas.height,
219 0, 0, historyPreviewCanvas.width, historyPreviewCanvas.height);
220 this.historyItems.push(historyElement);
221 historyElement.prependTo(historyContainer);
222 }
223
224 // Takes a snapshot (png) of the current canvas and pushes it
225 // to the restorePoints array
226 this.takeSnapshot = function() {
227 this.restorePoints.push(this.context.canvas.toDataURL("image/png"));
228 updateUndoButton();
229
230 // Some temporary code for a different kind of snapshot-implementation
231 // - don't remove yet.
232 // var restorePoints = document.createElement('canvas');
233 // restorePoints.width = this.context.canvas.width;
234 // restorePoints.height = this.context.canvas.height;
235 // this.restorePoints.push(restorePoints);
236 // this.restorePoints.getContext('2d')
237 // .putImageData(this.context.getImageData(0, 0,
238 // this.context.canvas.width, this.context.canvas.height), 0, 0);
239 }
240
241 // Undoes the previous selection by restoring a PNG stored in
242 // restorePoints-array,
243 // removes the local data entry from this.data and the remote entry
244 // by a POST ajax call
245 this.undo = function() {
246 if (isUndoUsable()) {
247 this.context.clearRect(0, 0,
248 this.context.canvas.width, this.context.canvas.height);
249 this.previewContext.clearRect(0, 0,
250 this.previewContext.canvas.width,
251 this.previewContext.canvas.height);
252
253 // Creates an image from the restorePoints
254 var img = new Image();
255 img.src = this.restorePoints.pop();
256
257 // Use late binding for making the actual changes to avoid
258 // messing the image up.
259 //We also have to redraw the preview.
260 img.onload = $.proxy(function() {
261 // Switching to source-over is necessary to have the
262 // image drawn correctly
263 this.context.globalCompositeOperation = 'source-over';
264 this.context.drawImage(img, 0, 0);
265 if (deleteMode)
266 this.context.globalCompositeOperation = 'destination-out';
267 this.drawPreview();
268 // This removes the local Selection data
269 this.data.pop();
270 // This removes the remote data
271 this.ajax_undo();
272 this.removeHistoryItem();
273 },
274 this);
275
276 updateUndoButton();
277
278 } else alert("Nothing to undo!"
279 + " You cannot undo selections loaded from the server.");
280 }
281
282 // Undoes the previous selection
283 this.ajax_undo = function() {
284 $.post("/selections/undo", {
285 id: this.id
286 },
287 // Callback function for the delete - implement if needed.
288 function(data) {
289 },
290 "json"
291 );
292 }
293
294 // Enables this selection
295 this.enable = function() {
296 // Disables the previous selection
297 if (activeSelection != this && activeSelection != null) {
298 activeSelection.disable();
299 }
300 // Sets this as the active selection
301 if (activeSelection == null) {
302 if (deleteMode) {
303 this.context.globalCompositeOperation = 'destination-out';
304 }
305 activeSelection = this;
306 this.listElement.toggleClass('list_item_selected', true);
307 this.canvas.toggleClass('hide', false);
308 unlockSelectionTools();
309 this.updateSelectionHistory();
310 updateUndoButton();
311 }
312 }
313
314 // Disables this as the active selection
315 this.disable = function() {
316 this.context.globalCompositeOperation = 'source-over';
317 this.listElement.toggleClass('list_item_selected', false);
318 activeSelection = null;
319 if (!showAllSelections)
320 this.canvas.toggleClass('hide', true);
321 }
322
323 // Handler for the mouseover-events for displaying the selection
324 this.mouseOver = function() {
325 if (activeSelection != this && !showAllSelections) {
326 this.canvas.toggleClass('hide', false);
327 }
328 }
329 this.mouseOut = function() {
330 if (activeSelection != this && !showAllSelections) {
331 this.canvas.toggleClass('hide', true);
332 }
333 }
334
335 // Draws the contents from drawLayer to this.context
336 this.draw = function(canvas) {
337 var tempCanvas = document.createElement('canvas');
338 var tempContext = tempCanvas.getContext('2d');
339 tempCanvas.width = drawLayer.height;
340 tempCanvas.height = drawLayer.width;
341 tempContext.drawImage(drawLayer, 0, 0);
342 this.takeSnapshot();
343 this.addHistoryItem();
344
345 if (canvas == null)
346 this.context.drawImage(drawLayer, 0, 0);
347 else
348 this.context.drawImage(canvas, 0, 0);
349
350 this.drawPreview();
351 }
352
353 // Draws the presentation of selections stored in this.data
354 this.present = function() {
355 if (this.bitmask != null) {
356 // Second argument is an array with the colors [R, G, B, A]
357 // ranging from 0 to 255
358 this.drawBits(this.bitmask, [0, 0, 220, 128]);
359 // [138, 159, 255, 128]);
360 }
361 this.takeSnapshot();
362 }
363
364 // Helper function for drawing to canvas from bitmask with given colors
365 this.drawBits = function(bitMask, color) {
366 // Initialize local variables for clarity
367 var bits = bitMask.bitString;
368 var x0 = bitMask.x;
369 var y0 = bitMask.y;
370 var h = bitMask.h;
371 var w = bitMask.w;
372
373 // Create an empty set of data
374 var imgData = drawCanvas.createImageData(w, h);
375 var dataArray = imgData.data;
376
377 // Loop for iterating through x-y values - forms the image data
378 for (var y = 0; y < h; ++y) {
379 for (var x = 0; x < w; ++x) {
380 var ind = y * w + x;
381 var index = ind * 4
382 if (bits[ind] == 1) {
383 dataArray[index] = color[0]; // red
384 dataArray[++index] = color[1]; // green
385 dataArray[++index] = color[2]; // blue
386 dataArray[++index] = color[3]; // alpha
387 }
388 }
389 }
390
391 // Create temporary canvas to be used with drawImage
392 // NOTE: Usage of putImageData directly to this.context would
393 // cause clipping of overlapping selections, thus the workaround
394 var tempCanvas = document.createElement('canvas');
395 var tempContext = tempCanvas.getContext('2d');
396 tempCanvas.width = w;
397 tempCanvas.height = h;
398 tempContext.putImageData(imgData, 0, 0);
399
400 // Draw the formed image into this selection's canvas at
401 // location (x0, y0)
402 this.context.drawImage(tempCanvas, x0, y0);
403 }
404
405 // Removes the local instances of this Selection
406 this.localDestruct = function() {
407 activeSelection = null;
408 updateUndoButton();
409 this.canvas.remove();
410 // Some candy for the list item removal!
411 this.listElement.remove();
412 delete selectionTable[this.name];
413 setupUIElements();
414 delete this;
415 }
416
417 // Removes this Selection from the server
418 this.destruct = function() {
419 this.localDestruct();
420 this.post_delete();
421 }
422
423 // Sends the selection to the server with a simple POST request when
424 // a fresh selection group is made.
425 // Proxy needed for passing correct scope to the callback function.
426 if (this.id == null) {
427 $.post("/selections/create", {
428 selection: {
429 name: this.name,
430 layered_image_id: this.image
431 }
432 },
433 // Gets the correct mongo ID from the server
434 $.proxy(function(data) {
435 this.id = data._id
436 },
437 this), "json");
438 }
439
440 // Deletes the selection from the server with a simple POST request.
441 this.post_delete = function() {
442 $.post("/selections/destroy", {
443 id: this.id
444 },
445 // Executes this after the call returns.
446 function(data) {
447 },
448 "json"
449 );
450 }
451
452 // Updates the selection with a simple POST request.
453 // Sends the previously made selection only, not all of current selections.
454 this.post_update = function() {
455 $.post("/selections/update", {
456 selection: {
457 name: this.name,
458 bits: this.data[this.data.length - 1]
459 // individual selection bitmasks
460 },
461 id: this.id
462 },
463 // Executes this after the call returns.
464 $.proxy(function(data) {
465 },
466 this),
467 "json"
468 );
469 }
470
471 // Only updates the name of this selection
472 this.update_name = function() {
473 $.post("/selections/update", {
474 selection: {
475 name: this.name,
476 },
477 id: this.id
478 },
479 // Executes this after the call returns.
480 function(data) {
481 },
482 "json"
483 );
484 }
485
486 this.getImages = function() {
487
488 }
489
490 // Locks the selected selection.
491 // Calls /selection/lock as jQuery POST with this.ID as the argument.
492 this.lock = function() {
493 // TODO: Functionality.
494 }
495
496 // Unlocks the selection.
497 // Calls /selection/unlock as jQuery POST with this.ID as the argument.
498 this.unlock = function() {
499 // TODO: Functionality
500 }
501
502 // Binds mouse-event handlers using proxy to pass the correct scope
503 this.listElement.mouseover($.proxy(this.mouseOver, this));
504 this.listElement.mouseout($.proxy(this.mouseOut, this));
505 this.kill_button.click($.proxy(this.destruct, this));
506 this.listElement.click($.proxy(this.enable, this));
507
508 // Combines the jQuery elements to form the final list element
509 this.namediv.appendTo(this.innerdiv);
510 this.nameInput.appendTo(this.innerdiv);
511 this.innerdiv.appendTo(this.listElement);
512 this.previewCanvasJQ.appendTo(this.listElement);
513 this.kill_button.appendTo(this.innerdiv);
514
515 // Adjusts the canvas to match the size of the image
516 this.setSize();
517
518 // Adds the list element to the list and expands the list to correct width
519 this.listElement.hide().appendTo('#selection_list').fadeIn(300);
520 setupUIElements();
521
522 // Positions the canvas after the object has been created.
523 this.positionCanvas();
524}
app/assets/javascripts/selections/selection_tools.js.erb
(622 / 0)
  
1/* This content is released under the MIT License.
2 *
3 * Please see the full license in the attached LICENCE-file
4 * or at http://www.opensource.org/licenses/mit-license.php
5 *
6 * Copyright (c) 2012 Pekka Iso-Ahola
7 * Copyright (c) 2012 Jussi Perttola
8 * Copyright (c) 2012 Tommi Tuovinen
9 * ---------------------------------------------------------
10 */
11
12// SelectionTool-object, contains the image info as well as the methods
13function SelectionTool(img) {
14 // Initialize variables for holding min- and max-values for x and y.
15 this.x_min = 10000;
16 this.y_min = 10000;
17 this.x_max = 0;
18 this.y_max = 0;
19
20 // Set selectionTool to either drawing (false) or deleting (true) mode
21 this.deleteMode = false;
22 this.locked = false;
23
24 this.img = img;
25 this.image = jQuery('<img/>', {
26 src: this.img,
27 'class': "selectiontool_button",
28 alt: "Alternative text",
29 title: "Image title"
30 });
31 this.image.appendTo($('#selection_tools'));
32
33 selectionTools.push(this);
34
35 // Enables this selectionTool
36 this.enable = function() {
37 if (this.locked) {
38 return;
39 }
40
41 $('.toolsettings').toggleClass('hide', true);
42
43 // Disables the previously used selectionTool
44 if (selectionTool != null && selectionTool != this)
45 selectionTool.disable();
46
47 // Enables the selectionTool
48 selectionTool = this;
49
50 // Toggles the DOM element as selected
51 this.image.toggleClass('list_item_selected', true);
52 }
53
54 // Disables this selectionTool
55 this.disable = function() {
56 this.image.toggleClass('list_item_selected', false);
57 selectionTool = null;
58 }
59
60 // Updates values of x_min, x_max, y_min, y_max
61 this.updateBoundaryPoints = function(ev) {
62 if (ev._x > this.x_max)
63 this.x_max = ev._x;
64 if (ev._x < this.x_min)
65 this.x_min = ev._x;
66 if (ev._y > this.y_max)
67 this.y_max = ev._y;
68 if (ev._y < this.y_min)
69 this.y_min = ev._y;
70 }
71
72 // Resets values of x_min, x_max, y_min, y_max points
73 this.resetBoundaryPoints = function() {
74 this.x_min = 10000;
75 this.y_min = 10000;
76 this.x_max = 0;
77 this.y_max = 0;
78 }
79
80 // Debugging..
81 this.debugpoints = function() {
82 console.log("(" + this.x_min + ", " + this.y_min
83 + ") => (" + this.x_max + ", " + this.y_max + ")");
84 }
85
86 // Bind the click event to selection of the selectionTool.
87 this.image.click($.proxy(this.enable, this));
88 this.enable();
89 // Enabled to avoid unnecessary "selectionTool is null" console output
90}
91
92// Initializes the rectangle tool
93function initRectangleTool() {
94 rectangle = new SelectionTool("<%= asset_path 'rectangle_tool_icon.png' %>");
95 rectangle.name = "rectangle";
96 var selectionTool = rectangle;
97 selectionTool.image.attr('alt', 'Rectangle tool');
98 selectionTool.image.attr('title', 'Rectangle');
99 selectionTool.started = false;
100
101 rectangle.mousedown = function(ev) {
102 startShape(ev);
103 };
104
105 rectangle.mousemove = function(ev) {
106 if (!selectionTool.started) {
107 return;
108 }
109
110 var x = Math.min(ev._x, selectionTool.x0),
111 y = Math.min(ev._y, selectionTool.y0),
112 w = Math.abs(ev._x - selectionTool.x0),
113 h = Math.abs(ev._y - selectionTool.y0);
114
115 // Clears the current rectangles from the view
116 drawCanvas.clearRect(0, 0, drawLayer.width, drawLayer.height);
117
118 if (!w) {
119 w = 1;
120 }
121
122 if (!h) {
123 h = 1;
124 }
125
126 drawCanvas.fillRect(x, y, w, h);
127 };
128
129 rectangle.mouseup = function(ev) {
130 endShape(ev);
131 getBitMask();
132 };
133}
134
135// Initializes the ellipse tool.
136function initEllipseTool() {
137 ellipse = new SelectionTool("<%= asset_path 'ellipse_tool_icon.png' %>");
138 ellipse.name = "ellipse";
139 var selectionTool = ellipse;
140 selectionTool.image.attr('alt', 'Ellipse tool');
141 selectionTool.image.attr('title', 'Ellipse');
142 selectionTool.started = false;
143
144 ellipse.mousedown = function(ev) {
145 startShape(ev);
146 };
147
148 ellipse.mousemove = function(ev) {
149 if (!selectionTool.started) {
150 return;
151 }
152
153 var x = Math.min(ev._x, selectionTool.x0),
154 y = Math.min(ev._y, selectionTool.y0),
155 w = Math.abs(ev._x - selectionTool.x0),
156 h = Math.abs(ev._y - selectionTool.y0);
157
158 // Clears the current rectangles from the view
159 drawCanvas.clearRect(0, 0, drawLayer.width, drawLayer.height);
160
161 if (!w)
162 w = 1;
163
164 if (!h)
165 h = 1;
166
167 drawEllipse(x, y, w, h);
168
169 };
170
171 ellipse.mouseup = function(ev) {
172 endShape(ev);
173 getBitMask();
174 };
175}
176
177function drawEllipse(x, y, width, height) {
178
179 var kappa = .5522848, //Curvature of a curve
180 offsetX = (width / 2) * kappa,
181 offsetY = (height / 2) * kappa,
182 xEnd = x + width,
183 yEnd = y + height,
184 xMiddle = x + width / 2,
185 yMiddle = y + height / 2;
186
187 drawCanvas.beginPath();
188 drawCanvas.moveTo(x, yMiddle);
189 drawCanvas.bezierCurveTo(x, yMiddle - offsetY, xMiddle - offsetX,
190 y, xMiddle, y);
191 drawCanvas.bezierCurveTo(xMiddle + offsetX, y, xEnd, yMiddle - offsetY,
192 xEnd, yMiddle);
193 drawCanvas.bezierCurveTo(xEnd, yMiddle + offsetY, xMiddle + offsetX, yEnd,
194 xMiddle, yEnd);
195 drawCanvas.bezierCurveTo(xMiddle - offsetX, yEnd, x, yMiddle + offsetY,
196 x, yMiddle);
197
198 drawCanvas.fill();
199 drawCanvas.closePath();
200
201}
202
203function startShape(ev) {
204 if (activeSelection != null) {
205 if (deleteMode)
206 // Deleting is marked with red color
207 drawCanvas.fillStyle = 'rgb(220, 0, 0)';
208 else
209 drawCanvas.fillStyle = activeSelection.fillStyle;
210 }
211
212 if (!selectionTool.started)
213 selectionTool.resetBoundaryPoints();
214
215 // Delete mode requires non-transparent color, otherwise the opacities will be
216 // added together, which equals to non-zero opacity.
217 if (deleteMode)
218 drawCanvas.globalAlpha = 1;
219 else
220 drawCanvas.globalAlpha = 0.75;
221
222 selectionTool.started = true;
223
224 // Store initial points
225 selectionTool.x0 = ev._x;
226 selectionTool.y0 = ev._y;
227
228 // Updates boundary points for the selectionTool
229 selectionTool.updateBoundaryPoints(ev);
230}
231
232function endShape(ev) {
233 if (selectionTool.started) {
234 selectionTool.mousemove(ev);
235 selectionTool.updateBoundaryPoints(ev);
236 selectionTool.started = false;
237 if (activeSelection != null)
238 activeSelection.draw();
239 drawCanvas.clearRect(0, 0, drawLayer.width, drawLayer.height);
240
241 // Set final points
242 selectionTool.x1 = ev._x;
243 selectionTool.y1 = ev._y;
244 }
245}
246
247
248// Initializes the polygon (alias point to point free hand selection) tool.
249function initPolygonTool() {
250 polygon = new SelectionTool("<%= asset_path 'polygon_tool_icon.png' %>");
251 polygon.name = "polygon";
252 var selectionTool = polygon;
253 selectionTool.image.attr('alt', 'Polygon tool');
254 selectionTool.image.attr('title', 'Polygon');
255 selectionTool.started = false;
256 var points = [];
257 var startRadius = 10;
258
259
260 line_canvas = $('#help_layer').get(0);
261 line_canvas.width = $('#draw_layer').width();
262 line_canvas.height = $('#draw_layer').height();
263 line_context = line_canvas.getContext("2d");
264
265 polygon.mousedown = function(ev) {
266
267 if (!selectionTool.started) {
268 selectionTool.resetBoundaryPoints();
269 selectionTool.startX = ev._x;
270 selectionTool.startY = ev._y;
271 selectionTool.started = true;
272 points.push([selectionTool.startX, selectionTool.startY]);
273 // adds starting point to array
274 startShape(ev);
275
276 line_context.beginPath();
277 line_context.arc(selectionTool.startX, selectionTool.startY,
278 startRadius/zoomScale+1, 0, 2 * Math.PI, false);
279
280 line_context.lineWidth = 1;
281 line_context.strokeStyle = "#0000ff";
282 line_context.stroke();
283
284 return;
285 }
286
287 var distanceToStart = Math.sqrt(Math.pow(ev._x - selectionTool.startX, 2)
288 + Math.pow(ev._y - selectionTool.startY, 2))
289 if (distanceToStart < startRadius/zoomScale+1 && points.length > 2) {
290 selectionTool.started = false;
291 selectionTool.updateBoundaryPoints(ev);
292 line_context.clearRect(0, 0, drawLayer.width, drawLayer.height);
293 drawFilledPolygon();
294 getBitMask();
295 points = [];
296
297 return;
298 }
299
300 line_context.drawImage(drawLayer, 0, 0);
301 drawCanvas.clearRect(0, 0, drawLayer.width, drawLayer.height);
302 points.push([ev._x, ev._y]);
303 startShape(ev);
304 };
305
306 polygon.mousemove = function(ev) {
307 if (!selectionTool.started) {
308 return;
309 }
310
311 // Clears the current rectangles from the view
312 drawCanvas.clearRect(0, 0, drawLayer.width, drawLayer.height);
313
314 drawCanvas.beginPath();
315 drawCanvas.moveTo(selectionTool.x0, selectionTool.y0);
316 drawCanvas.lineTo(ev._x, ev._y);
317 $('#image_holder').get(0).getContext('2d')
318 drawCanvas.strokeStyle = "#ff0000";
319 drawCanvas.stroke();
320 drawCanvas.closePath();
321 };
322
323 polygon.mouseup = function(ev) {
324 // Initialize function to avoid unnecessary errors
325 };
326
327 function drawFilledPolygon() {
328 drawCanvas.clearRect(0, 0, drawLayer.width, drawLayer.height);
329 drawCanvas.beginPath();
330 drawCanvas.moveTo(selectionTool.startX, selectionTool.startY);
331 for (var i = 1; i < points.length; i++) {
332 drawCanvas.lineTo(points[i][0], points[i][1]);
333 }
334 drawCanvas.closePath();
335
336 drawCanvas.fill();
337 if (activeSelection != null)
338 activeSelection.draw();
339 drawCanvas.clearRect(0, 0, drawLayer.width, drawLayer.height);
340 }
341}
342
343// Initializes the magic wand tool.
344function initMagicWandTool() {
345 wand = new SelectionTool("<%= asset_path 'magic_wand_tool_icon.png' %>");
346 wand.name = "wand";
347 var selectionTool = wand;
348 selectionTool.image.attr('alt', 'Area selection tool');
349 selectionTool.image.attr('title', 'Area selection');
350
351 wand.image.click(function() {
352 if (selectionTool === wand) {
353 $('.toolsettings').toggleClass('hide', false);
354
355 }
356 });
357
358 wand.mousedown = function(ev) {
359 var grayThreshold = $('#thresholdField').val();
360
361 if (activeSelection == null) {
362 return;
363 }
364
365 //gets color values that belong to current selection
366 var startIndex = activeSelection.fillStyle.indexOf('(', 0);
367 var endIndex = activeSelection.fillStyle.indexOf(')', 0);
368 var colors = activeSelection.fillStyle
369 .substring(startIndex + 1, endIndex)
370 .split(',');
371 var drawR = parseInt(colors[0]);
372 var drawG = parseInt(colors[1]);
373 var drawB = parseInt(colors[2]);
374
375 var drawContext = $('#image_holder').get(0).getContext('2d');
376 var canvasWidth = drawContext.canvas.width;
377 var canvasHeight = drawContext.canvas.height;
378
379 var canvasdata = drawContext.getImageData(
380 0, 0, drawContext.canvas.width, drawContext.canvas.height);
381 var startPos = (ev._y * canvasWidth + ev._x) * 4;
382
383 var startGraysum = 0.30 * canvasdata.data[startPos]
384 + 0.59 * canvasdata.data[startPos + 1]
385 + 0.11 * canvasdata.data[startPos + 2];
386
387 var imageData = drawCanvas.createImageData(canvasWidth, canvasHeight);
388
389 validateThreshold();
390
391 var unscaledThreshold = parseInt($('#thresholdField').val());
392
393 var grayThreshold = (unscaledThreshold - (unscaledThreshold) *0.3)
394 /100.0 * 255.0;
395
396 var pixelStack = [[ev._x, ev._y]];
397
398 //uses luma. Luma is weighted average of gamma-corrected R, G, and B
399 function matchStartColor(pixelPos)
400 {
401 if ( canvasdata.data[pixelPos] === imageData.data[pixelPos]
402 && canvasdata.data[pixelPos + 1] === imageData.data[pixelPos + 1]
403 && canvasdata.data[pixelPos + 2] === imageData.data[pixelPos + 2])
404 return false;
405
406 var endGraysum = 0.30 * canvasdata.data[pixelPos]
407 + 0.59 * canvasdata.data[pixelPos + 1]
408 + 0.11 * canvasdata.data[pixelPos + 2];
409
410 return (endGraysum < startGraysum + grayThreshold
411 && endGraysum > startGraysum - grayThreshold);
412 }
413
414 function colorPixel(i)
415 {
416 canvasdata.data[i] = drawR;
417 canvasdata.data[i + 1] = drawG;
418 canvasdata.data[i + 2] = drawB;
419 canvasdata.data[i + 3] = 180;//TODO
420
421 imageData.data[i] = drawR;
422 imageData.data[i + 1] = drawG;
423 imageData.data[i + 2] = drawB;
424 imageData.data[i + 3] = 180;//TODO
425 }
426
427
428 //This code is modified from this tutorial:
429 // http://www.williammalone.com/articles
430 // /html5-canvas-javascript-paint-bucket-tool/
431 // (c) William Malone
432 function executeMagicWand() {
433 while (pixelStack.length)
434 {
435 var newPos,
436 x,
437 y,
438 pixelPos,
439 reachLeft,
440 reachRight;
441
442 newPos = pixelStack.pop();
443 x = newPos[0];
444 y = newPos[1];
445
446 if (x < selectionTool.x_min) selectionTool.x_min = x;
447 if (x > selectionTool.x_max) selectionTool.x_max = x;
448
449 pixelPos = (y * canvasWidth + x) * 4;
450
451 if (y <= selectionTool.y_min)
452 selectionTool.y_min = y;
453
454 while (y-->=0 && matchStartColor(pixelPos))
455 {
456 pixelPos -= canvasWidth * 4;
457 if (y <= selectionTool.y_min)
458 selectionTool.y_min = y;
459 }
460 pixelPos += canvasWidth * 4;
461 ++y;
462
463 if (y > selectionTool.y_max)
464 selectionTool.y_max = y;
465
466 reachLeft = false;
467 reachRight = false;
468 while (y++<canvasHeight - 1 && matchStartColor(pixelPos))
469 {
470 if (y > selectionTool.y_max)
471 selectionTool.y_max = y;
472
473 colorPixel(pixelPos);
474
475 if (x > 0)
476 {
477 if (matchStartColor(pixelPos - 4))
478 {
479 if (!reachLeft) {
480 pixelStack.push([x - 1, y]);
481 reachLeft = true;
482 }
483 }
484 else if (reachLeft)
485 {
486 reachLeft = false;
487 }
488 }
489
490 if (x < canvasWidth - 1)
491 {
492 if (matchStartColor(pixelPos + 4))
493 {
494 if (!reachRight)
495 {
496 pixelStack.push([x + 1, y]);
497 reachRight = true;
498 }
499 }
500 else if (reachRight)
501 {
502 reachRight = false;
503 }
504 }
505
506 pixelPos += canvasWidth * 4;
507 }
508
509 }
510 }
511
512 selectionTool.resetBoundaryPoints();
513 executeMagicWand()
514
515 var tempCanvas = document.createElement('canvas');
516 var tempContext = tempCanvas.getContext('2d');
517 tempCanvas.width = canvasWidth;
518 tempCanvas.height = canvasHeight;
519
520 tempContext.putImageData(imageData, 0, 0);
521 activeSelection.draw(tempCanvas);
522
523 drawCanvas.clearRect(0, 0, drawLayer.width, drawLayer.height);
524 }
525
526 wand.mouseup = function(ev) {
527 if (activeSelection == null) {
528 return;
529 }
530
531 getBitMask();
532 }
533
534}
535
536// Locks selection tools
537function lockSelectionTools() {
538 $('.selectiontool_button').toggleClass('fade', true);
539 for (sTool in selectionTools) {
540 selectionTools[sTool].locked = true;
541 }
542
543 if (selectionTool != null)
544 selectionTool.disable();
545}
546
547// Unlocks selection tools
548function unlockSelectionTools() {
549
550 $('.selectiontool_button').toggleClass('fade', false);
551 for (sTool in selectionTools)
552 selectionTools[sTool].locked = false;
553
554 // Use rectangle as default selectionTool
555 if (selectionTool == null)
556 rectangle.enable();
557}
558
559
560/* Gets pixels from the current canvas limited by the
561 * rectangle (x_min, y_min), (x_max, y_max),
562 * forms the corresponding bitmask, pushes the data into the
563 * current selection's data table and
564 * syncs the selection to the server.
565 */
566function getBitMask() {
567 var x_max = selectionTool.x_max;
568 var y_max = selectionTool.y_max;
569 var x_min = selectionTool.x_min;
570 var y_min = selectionTool.y_min;
571
572 var w = Math.max(1, Math.abs(x_max - x_min));
573 var h = Math.max(1, Math.abs(y_max - y_min));
574
575 if (x_min < 0) x_min = 0;
576 if (y_min < 0) y_min = 0;
577
578 // getImageData returns an alpha-RBG array, ie. one pixel in the
579 // image is represented by 4 array elements (red, blue, green, alpha)
580 var pixelData = activeSelection.context
581 .getImageData(x_min, y_min, w, h).data;
582
583 // Form the bitmask as a string to avoid JSON overhead from using arrays
584 var bitString = checkPixels(pixelData, w, h);
585
586 // Form a container object for JSON serialization.
587 // Stores the bitMask as well as the bottom left coordinates,
588 // height and width of the bounding box.
589 activeSelection.storeBitmask({
590 selectionTool: selectionTool.name,
591 x: x_min,
592 y: y_min,
593 h: y_max - y_min,
594 w: x_max - x_min,
595 bitString: bitString
596 });
597
598 // Updates the selection
599 activeSelection.post_update();
600}
601
602 /* Check every 4th pixel starting from index 3 (being the alpha)
603 * and form the bitmask
604 * array element == 0 => pixel is not selected
605 * array element == 1 => pixel is selected
606 * array element == 2 => pixel is marked for removal
607 */
608function checkPixels(pixelData, width, height) {
609 var zerobit = deleteMode ? "2": "0";
610 var mask = "";
611 for (var y = 0; y < height; ++y) {
612 for (var x = 0; x < width; ++x) {
613 var ind = (y * width + x) * 4 + 3;
614 if (pixelData[ind] != 0) {
615 mask += "1";
616 } else {
617 mask += zerobit;
618 }
619 }
620 }
621 return mask;
622}
app/assets/javascripts/selections/selection_view.js.erb
(373 / 0)
  
1/* This content is released under the MIT License.
2 *
3 * Please see the full license in the attached LICENCE-file
4 * or at http://www.opensource.org/licenses/mit-license.php
5 *
6 * Copyright (c) 2012 Pekka Iso-Ahola
7 * Copyright (c) 2012 Jussi Perttola
8 * Copyright (c) 2012 Tommi Tuovinen
9 * ---------------------------------------------------------
10 * Functions for the selection view.
11*/
12
13// Init global variables
14selectionTable = {};
15selectionTools = [];
16selectionTool = null;
17layeredImageTable = {}
18showAllSelections = false;
19drawLayer = null;
20drawCanvas = null;
21activeSelection = null;
22activeImage = null;
23deleteMode = false;
24zoomScale = 1;
25SELECTION_WIDTH = 86;
26MIN_ZOOM = 0.125;
27MAX_ZOOM = 64;
28
29
30// Function for adding Selection-groups
31function addSelectionFromForm() {
32 var name = $('#selectionNameField').val();
33 if (selectionTable[name] != null) {
34 alert('Selection ' + name + ' already exists!');
35 } else {
36 var s = new Selection(name);
37 s.enable();
38 }
39 $('#selection_popup').toggle();
40
41 // FOR EASY TESTING PURPOSES
42 if (name.indexOf("-") > -1) {
43 var splitName = name.split("-");
44 $('#selectionNameField').val(splitName[0] + "-" + (++splitName[1]));
45 }
46}
47
48// The general-purpose event handler. This function determines the mouse
49// position relative to the canvas element.
50function evCanvas(ev) {
51 if (!ev) var ev = window.event;
52 ev._x = 0;
53 ev._y = 0;
54
55 if (ev.layerX || ev.layerY) { // layerX and layerY are soon depricated.
56 ev._x = ev.layerX;
57 ev._y = ev.layerY;
58 }
59 else if (ev.pageX || ev.pageY) { //TODO: Does not work when zooming
60 ev._x = ev.pageX - $('#image_container').get(0).offsetLeft
61 + $('#image_container').get(0).scrollLeft;
62 ev._y = ev.pageY - $('#image_container').get(0).offsetTop
63 + $('#image_container').get(0).scrollTop;
64 }
65 else if (ev.clientX || ev.clientY) { //TODO: Does not work when zooming
66 ev._x = ev.clientX - $('#image_container').get(0).offsetLeft
67 + window.pageXOffset + $('#image_container').get(0).scrollLeft;
68 ev._y = ev.clientY - $('#image_container').get(0).offsetTop
69 + window.pageYOffset + $('#image_container').get(0).scrollTop;
70 }
71
72 // Positions the coordinate floater and shows coordinates
73 $('#coordinate_floater').html("(" + ev._x + ", " + ev._y + ")").css({
74 left: ev.pageX + 10,
75 top: ev.pageY + 10
76 });
77
78 // If no selectionToolselected then just return.
79 if (selectionTool== null)
80 return;
81
82 if (activeSelection == null) {
83 if (ev.type == "mousedown")
84 alert("You must have a selection active first.")
85 } else {
86
87 // Call the event handler of the selectionTool.
88 var func = selectionTool[ev.type];
89 if (func) {
90 func(ev);
91 }
92 }
93}
94
95// Attach the mousedown, mousemove and mouseup event listeners
96function initCanvas() {
97 // Init canvas
98 drawLayer = $('#draw_layer').get(0);
99 drawLayer.addEventListener('mousedown', evCanvas, false);
100 drawLayer.addEventListener('mousemove', evCanvas, false);
101 drawLayer.addEventListener('mouseup', evCanvas, false);
102
103 // Init context settings
104 drawCanvas = drawLayer.getContext('2d');
105}
106
107// Expands the image and selection lists so that all the elements fit inside.
108function setupUIElements() {
109 $("#selection_list").width($("#selection_list > div").size() * SELECTION_WIDTH);
110 $("#image_list").width($("#image_list > div").size() * SELECTION_WIDTH);
111
112 // Positions the loading div
113 var lo = $('#loading').get(0);
114 lo.setAttribute('left', $('document').width() / 2 + 'px');
115 lo.setAttribute('top', $('document').height() / 2 + 'px');
116
117 //Changing imgContainer to fit better on the screen
118 var imgContainer = $('#image_container');
119 var oldWidth = drawLayer.width;
120 imgContainer.width("80%");
121 var ratio = imgContainer.width() / oldWidth;
122 var newHeight = drawLayer.height * ratio;
123 imgContainer.height(newHeight);
124 imgContainer.get(0).style.overflow = "auto";
125
126}
127
128// Updates the current viewed image and resizes canvases accordingly.
129// Called when activeImage changes.
130function updateCanvases() {
131 x = activeImage.img.width;
132 y = activeImage.img.height;
133 contexto = $('#image_holder').get(0).getContext('2d');
134 contexto.canvas.width = x;
135 contexto.canvas.height = y;
136 helpContext = $('#help_layer').get(0).getContext('2d');
137 helpContext.canvas.width = x;
138 helpContext.canvas.height = y;
139 drawLayer.width = x;
140 drawLayer.height = y;
141 drawCanvas = drawLayer.getContext('2d');
142 contexto.drawImage(activeImage.img, 0, 0);
143}
144
145// Updates the current viewed image but does not resize canvases.
146// Called when image number changes.
147function updateImage() {
148 contexto = $('#image_holder').get(0).getContext('2d');
149 contexto.drawImage(activeImage.img, 0, 0);
150}
151
152// Clears current selections
153function clearSelections() {
154 for (var key in selectionTable) {
155 selectionTable[key].localDestruct();
156 }
157}
158
159// Actions that are commited in UI when a hyperspectral image
160// is selected from the list.
161function imageListItemEnabled() {
162 updateCanvases();
163 initPartialImageSelector();
164}
165
166// Loads selections from the server and displays a loading animation
167function loadSelections() {
168 zoomScale = 1;
169 zoom(zoomScale);
170 clearSelections();
171 $.ajax({
172 type: "POST",
173 url: "/selections/index",
174 dataType: "json",
175 data: {
176 id: activeImage.id
177 },
178
179 // Display loading animation
180 beforeSend: function(xhr) {
181 $('#loading_text').html("Loading ...");
182 $('#loading_div').show();
183 },
184
185 success: function(data) {
186 for (i = 0; i < data.length; i++) {
187 sel = new Selection(data[i].name, data[i]._id,
188 data[i].merged_bitmask);
189 sel.present();
190 sel.drawPreview();
191 }
192 },
193
194 error: function(data) {
195 alert('Something went wrong! ' + data)
196 },
197
198 complete: function() {
199 // Hide loading animation
200 $('#loading_div').hide();
201 setupUIElements();
202 }
203 });
204}
205
206function loadImages() {
207 $.ajax({
208 type: "GET",
209 url: "/layered_images",
210 dataType: "json",
211
212 // Display loading animation
213 beforeSend: function(xhr) {
214 $('#loading_text').html("Loading Images ...");
215 $('#loading_div').show();
216 },
217
218 success: function(data) {
219 for (i = 0; i < data.length; i++) {
220 img = new LayeredImage(data[i]);
221 }
222 },
223
224 error: function(data) {
225 alert('Something went wrong! ' + data)
226 },
227
228 complete: function() {
229 // Hide loading animation
230 // fixCanvases();
231 $('#loading_div').hide();
232 setupUIElements();
233 }
234 });
235}
236
237// Define handler function for slide-event
238function partialSliderSlide(event, ui) {
239 $('#partialImageSelector > a').text(ui.value).css(
240 {'text-align' : 'center',
241 'color' : '#4B81BE',
242 'background-color': '#EBEBFF'});
243}
244
245// Initializes the slider for selecting current displayed PartialImage
246function initPartialImageSelector() {
247 $('#top_toolbar').show();
248 // Initialize slider twice to have the start value work correctly
249 $('#partialImageSelector').slider({
250 value: activeImage.currentLayer,
251 min: 0,
252 max: activeImage.layerCount-1,
253 step: 1,
254 stop: function( event, ui ) {
255 activeImage.getImage(ui.value);
256 },
257 slide: partialSliderSlide