Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Crop rotation fixes #410

Merged
merged 8 commits into from
Feb 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 16 additions & 5 deletions src/js/models/panel_model.js
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@

// resize, zoom and pan to show the specified region.
// new panel will fit inside existing panel
// coords is {x:x, y:y, width:w, height:h, rotation?:r}
cropToRoi: function(coords) {
var targetWH = coords.width/coords.height,
currentWH = this.get('width')/this.get('height'),
Expand All @@ -473,14 +474,20 @@
yPercent = this.get('orig_height') / coords.height,
zoom = Math.min(xPercent, yPercent) * 100;

this.set({'width': newW, 'height': newH, 'dx': dx, 'dy': dy, 'zoom': zoom});
var toSet = { 'width': newW, 'height': newH, 'dx': dx, 'dy': dy, 'zoom': zoom };
var rotation = coords.rotation || 0;
if (!isNaN(rotation)) {
toSet.rotation = rotation;
}
this.save(toSet);
},

// returns the current viewport as a Rect {x, y, width, height}
getViewportAsRect: function(zoom, dx, dy) {
zoom = zoom !== undefined ? zoom : this.get('zoom');
dx = dx !== undefined ? dx : this.get('dx');
dy = dy !== undefined ? dy : this.get('dy');
var rotation = this.get('rotation');

var width = this.get('width'),
height = this.get('height'),
Expand All @@ -497,7 +504,8 @@
view_wh = width / height;
if (dx === 0 && dy === 0 && zoom == 100 && Math.abs(orig_wh - view_wh) < 0.01) {
// ...ROI is whole image
return {'x': 0, 'y': 0, 'width': orig_width, 'height': orig_height}
return {'x': 0, 'y': 0, 'width': orig_width,
'height': orig_height, 'rotation': rotation}
}

// Factor in the applied zoom...
Expand All @@ -512,7 +520,8 @@
roiX = cX - (roiW / 2),
roiY = cY - (roiH / 2);

return {'x': roiX, 'y': roiY, 'width': roiW, 'height': roiH};
return {'x': roiX, 'y': roiY, 'width': roiW,
'height': roiH, 'rotation': rotation};
},

// Drag resizing - notify the PanelView without saving
Expand Down Expand Up @@ -598,10 +607,12 @@
},

// Turn coordinates into css object with rotation transform
_viewport_css: function(img_x, img_y, img_w, img_h, frame_w, frame_h) {
_viewport_css: function(img_x, img_y, img_w, img_h, frame_w, frame_h, rotation) {
var transform_x = 100 * (frame_w/2 - img_x) / img_w,
transform_y = 100 * (frame_h/2 - img_y) / img_h,
transform_y = 100 * (frame_h/2 - img_y) / img_h;
if (rotation == undefined) {
rotation = this.get('rotation') || 0;
}

var css = {'left':img_x,
'top':img_y,
Expand Down
42 changes: 36 additions & 6 deletions src/js/views/crop_modal_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ var CropModalView = Backbone.View.extend({

// get selected area...
var roi = self.m.getViewportAsRect();
self.applyRotation(roi);

// Show as ROI *if* it isn't the whole image
if (roi.x !== 0 || roi.y !== 0
Expand Down Expand Up @@ -130,14 +131,21 @@ var CropModalView = Backbone.View.extend({
y = parseInt($roi.attr('data-y'), 10),
width = parseInt($roi.attr('data-width'), 10),
height = parseInt($roi.attr('data-height'), 10),
rotation = $roi.attr('data-rotation') || 0,
theT = parseInt($roi.attr('data-theT'), 10),
theZ = parseInt($roi.attr('data-theZ'), 10);

// Rectangle ROIs have NO rotation. Copy of crop might have rotation
rotation = parseInt(rotation);
this.m.set('rotation', rotation);

this.m.set({'theT': theT, 'theZ': theZ});

this.currentROI = {
'x':x, 'y':y, 'width':width, 'height':height
}
// Update coords based on any rotation (if coords come from rotated crop region)
this.applyRotation(this.currentROI, 1, rotation);

this.render();

Expand All @@ -149,13 +157,29 @@ var CropModalView = Backbone.View.extend({
this.currentRoiId = $roi.attr('data-roiId');
},

applyRotation: function(rect, factor=1, rotation) {
// Update the x and y coordinates of a Rectangle ROI to take account of rotation of the
// underlying image around it's centre point. The image is rotated on the canvas, so any
// Rectangle not at the centre will need to be rotated around the centre, updating rect.x and rect.y.
if (rotation === undefined) {
rotation = this.m.get('rotation');
}
if (rotation != 0) {
var img_cx = this.m.get('orig_width') / 2;
var img_cy = this.m.get('orig_height') / 2;
var rect_cx = rect.x + (rect.width / 2);
var rect_cy = rect.y + (rect.height / 2);
var new_c = rotatePoint(rect_cx, rect_cy, img_cx, img_cy, rotation * factor);
rect.x = new_c.x - (rect.width / 2);
rect.y = new_c.y - (rect.height / 2);
}
},

handleRoiForm: function(event) {
event.preventDefault();
// var json = this.processForm();
var self = this,
r = this.currentROI,
theZ = this.m.get('theZ'),
theT = this.m.get('theT'),
sel = this.model.getSelected(),
sameT = sel.allEqual('theT');
// sameZT = sel.allEqual('theT') && sel.allEqual('theT');
Expand All @@ -167,6 +191,9 @@ var CropModalView = Backbone.View.extend({
if (sameT) {
t = self.m.get('theT');
}

self.applyRotation(r, -1);

var rv = {'x': r.x,
'y': r.y,
'width': r.width,
Expand Down Expand Up @@ -230,7 +257,7 @@ var CropModalView = Backbone.View.extend({
m.unset('shapes');
}
// 'save' to trigger 'unsaved': true
m.save({'theZ': newZ, 'theT': newT});
m.save({ 'theZ': newZ, 'theT': newT, 'rotation': self.m.get('rotation')});
});
}

Expand Down Expand Up @@ -301,13 +328,13 @@ var CropModalView = Backbone.View.extend({

showClipboardFigureRois: function() {
// Show Rectangles from clipboard
var imageRects = [],
clipboardRects = [],
var clipboardRects = [],
clipboard = this.model.get('clipboard');
if (clipboard && clipboard.CROP) {
roi = clipboard.CROP;
clipboardRects.push({
x: roi.x, y: roi.y, width: roi.width, height: roi.height
x: roi.x, y: roi.y, width: roi.width, height: roi.height,
rotation: roi.rotation
});
} else if (clipboard && clipboard.SHAPES) {
clipboard.SHAPES.forEach(function(roi){
Expand Down Expand Up @@ -448,6 +475,7 @@ var CropModalView = Backbone.View.extend({

for (var r=0; r<rects.length; r++) {
rect = rects[r];
let rotation = rect.rotation || 0;
if (rect.theT > -1) this.m.set('theT', rect.theT, {'silent': true});
if (rect.theZ > -1) this.m.set('theZ', rect.theZ, {'silent': true});
src = this.m.get_img_src(true);
Expand All @@ -465,13 +493,15 @@ var CropModalView = Backbone.View.extend({
left = -(zoom * rect.x);
rect.theT = rect.theT !== undefined ? rect.theT : origT;
rect.theZ = rect.theZ !== undefined ? rect.theZ : origZ;
let css = this.m._viewport_css(left, top, img_w, img_h, size, size, rotation);

var json = {
'msg': msg,
'src': src,
'rect': rect,
'w': div_w,
'h': div_h,
'css': css,
'top': top,
'left': left,
'img_w': img_w,
Expand Down
35 changes: 33 additions & 2 deletions src/js/views/right_panel_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,21 @@
this.render();
},

rectToPolygon: function(rect, rotation) {
// rotate Rect around centre point - return points "x,y, x,y, x,y, x,y"
let cx = rect.x + (rect.width / 2);
let cy = rect.y + (rect.height / 2);
// topleft
let tl = rotatePoint(rect.x, rect.y, cx, cy, rotation);
// topright
let tr = rotatePoint(rect.x + rect.width, rect.y, cx, cy, rotation);
// bottomright
let br = rotatePoint(rect.x + rect.width, rect.y + rect.height, cx, cy, rotation);
// bottomleft
let bl = rotatePoint(rect.x, rect.y + rect.height, cx, cy, rotation);
return [tl, tr, br, bl].map(point => point.x + ',' + point.y).join(', ');
},

pasteROIs: function(event) {
event.preventDefault();
var sel = this.model.getSelected(),
Expand All @@ -140,13 +155,25 @@
var color = $('button.shape-color span:first', this.$el).attr('data-color'),
width = $('button.line-width span:first', this.$el).attr('data-line-width'),
rect = roiJson.CROP;
roiJson = [{type: "Rectangle",
// If rotated, need to create a Polygon since Rectangle doesn't support rotation
if (rect.rotation && !isNaN(rect.rotation)) {
// rotate around centre point
var points = this.rectToPolygon(rect, -rect.rotation);
roiJson = [{
type: "Polygon",
strokeColor: "#" + color,
points: points,
strokeWidth: width
}]
} else {
roiJson = [{type: "Rectangle",
x: rect.x,
y: rect.y,
width: rect.width,
height: rect.height,
strokeColor: "#" + color,
strokeWidth: width}];
}
} else {
return;
}
Expand Down Expand Up @@ -1116,26 +1143,30 @@

getXYWH: function(zoom, dx, dy) {

var x, y, w, h;
var x, y, w, h, rotation;
this.models.forEach(function(m, i){
var r = m.getViewportAsRect(zoom, dx, dy);
if (i === 0) {
x = r.x;
y = r.y;
w = r.width;
h = r.height;
rotation = r.rotation;
} else {
// if any models have different values, return '-'
if (x !== r.x) x = "-";
if (y !== r.y) y = "-";
if (w !== r.width) w = "-";
if (h !== r.height) h = "-";
if (rotation !== r.rotation) rotation = "-";
}
});
var json = {
x: (x !== "-" ? parseInt(x, 10) : x),
y: (y !== "-" ? parseInt(y, 10) : y),
width: (w !== "-" ? parseInt(w, 10) : w),
height: (h !== "-" ? parseInt(h, 10) : h),
rotation: (rotation !== "-" ? parseInt(rotation, 10) : "-"),
}
return json;
},
Expand Down
10 changes: 10 additions & 0 deletions src/js/views/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,16 @@ $.prototype.slider = function() {
}


// Get coordinates for point x, y rotated around cx, cy, by rotation degrees
var rotatePoint = function (x, y, cx, cy, rotation) {
let length = Math.sqrt(Math.pow((x - cx), 2) + Math.pow((y - cy), 2));
let rot = Math.atan2((y - cy), (x - cx));
rot = rot + (rotation * (Math.PI / 180)); // degrees to rad
let dx = Math.cos(rot) * length;
let dy = Math.sin(rot) * length;
return { x: cx + dx, y: cy + dy };
}

$(function(){


Expand Down
6 changes: 5 additions & 1 deletion src/templates/modal_dialogs/crop_modal_roi.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
<div class="roi_wrapper" style="position: relative; overflow: hidden; margin: 5px; height: <%= h %>px; width: <%= w %>px">
<img class="roi_content"
data-roiId="<%= roiId %>"
data-rotation="<%= rect.rotation %>"
data-x="<%= rect.x %>" data-y="<%= rect.y %>"
data-width="<%= rect.width %>" data-height="<%= rect.height %>"
data-theT="<%= rect.theT %>" data-theZ="<%= rect.theZ %>"
style="position: absolute; top: <%= top %>px; left: <%= left %>px; width: <%= img_w %>px; height: <%= img_h %>px" src='<%= src %>' />
style="position: absolute; top: <%= top %>px; left: <%= left %>px; width: <%= img_w %>px; height: <%= img_h %>px;
transform: <%= css.transform %>;
transform-origin: <%= css['transform-origin'] %>;"
src='<%= src %>' />
</div>
</td>
<% if (zStart) { %>
Expand Down