From 4bbcd63826f36ebfcbca8b35be3c4b4c76549e94 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Thu, 26 Nov 2015 17:24:37 -0500 Subject: [PATCH 1/7] Fix formatting on netbeans. --- nbproject/project.properties | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nbproject/project.properties b/nbproject/project.properties index 422675b4..c57a3e0d 100644 --- a/nbproject/project.properties +++ b/nbproject/project.properties @@ -5,7 +5,9 @@ auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.tab-size=8 auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width=80 auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap=none auxiliary.org-netbeans-modules-editor-indent.CodeStyle.usedProfile=project +auxiliary.org-netbeans-modules-editor-indent.text.javascript.CodeStyle.project.continuationIndentSize=4 auxiliary.org-netbeans-modules-editor-indent.text.javascript.CodeStyle.project.indent-shift-width=4 +auxiliary.org-netbeans-modules-editor-indent.text.javascript.CodeStyle.project.spaceBeforeAnonMethodDeclParen=false auxiliary.org-netbeans-modules-editor-indent.text.x-json.CodeStyle.project.indent-shift-width=2 auxiliary.org-netbeans-modules-editor-indent.text.x-json.CodeStyle.project.spaces-per-tab=2 auxiliary.org-netbeans-modules-web-clientproject-api.js_2e_libs_2e_folder=src From 94186826af5e4c0edf3d25381314175c13282099 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Thu, 26 Nov 2015 17:25:50 -0500 Subject: [PATCH 2/7] Extend Rect class to support rotation. --- src/rectangle.js | 226 +++++++++++++++++++-------------- test/coverage.html | 1 + test/modules/rectangle.js | 260 ++++++++++++++++++++++++++++++++++++++ test/test.html | 1 + 4 files changed, 394 insertions(+), 94 deletions(-) create mode 100644 test/modules/rectangle.js diff --git a/src/rectangle.js b/src/rectangle.js index abf1c947..25817762 100644 --- a/src/rectangle.js +++ b/src/rectangle.js @@ -36,42 +36,63 @@ /** * @class Rect - * @classdesc A Rectangle really represents a 2x2 matrix where each row represents a - * 2 dimensional vector component, the first is (x,y) and the second is - * (width, height). The latter component implies the equation of a simple - * plane. + * @classdesc A Rectangle is described by it top left coordinates (x, y), width, + * height and degrees of rotation around (x, y). + * Note that the coordinate system used is the one commonly used with images: + * x increases when going to the right + * y increases when going to the bottom + * degrees increases clockwise with 0 being the horizontal * * @memberof OpenSeadragon - * @param {Number} x The vector component 'x'. - * @param {Number} y The vector component 'y'. - * @param {Number} width The vector component 'height'. - * @param {Number} height The vector component 'width'. + * @param {Object} options + * @param {Number} options.x X coordinate of the top left corner of the rectangle. + * @param {Number} options.y Y coordinate of the top left corner of the rectangle. + * @param {Number} options.width Width of the rectangle. + * @param {Number} options.height Height of the rectangle. + * @param {Number} options.degrees Rotation of the rectangle around (x,y) in degrees. + * @param {Number} [x] Deprecated: The vector component 'x'. + * @param {Number} [y] Deprecated: The vector component 'y'. + * @param {Number} [width] Deprecated: The vector component 'width'. + * @param {Number} [height] Deprecated: The vector component 'height'. */ -$.Rect = function( x, y, width, height ) { +$.Rect = function(x, y, width, height) { + + var options = x; + if (!$.isPlainObject(options)) { + options = { + x: x, + y: y, + width: width, + height: height + }; + } + /** * The vector component 'x'. * @member {Number} x * @memberof OpenSeadragon.Rect# */ - this.x = typeof ( x ) == "number" ? x : 0; + this.x = typeof(options.x) === "number" ? options.x : 0; /** * The vector component 'y'. * @member {Number} y * @memberof OpenSeadragon.Rect# */ - this.y = typeof ( y ) == "number" ? y : 0; + this.y = typeof(options.y) === "number" ? options.y : 0; /** * The vector component 'width'. * @member {Number} width * @memberof OpenSeadragon.Rect# */ - this.width = typeof ( width ) == "number" ? width : 0; + this.width = typeof(options.width) === "number" ? options.width : 0; /** * The vector component 'height'. * @member {Number} height * @memberof OpenSeadragon.Rect# */ - this.height = typeof ( height ) == "number" ? height : 0; + this.height = typeof(options.height) === "number" ? options.height : 0; + + this.degrees = typeof(options.degrees) === "number" ? options.degrees : 0; }; $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ @@ -80,7 +101,13 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ * @returns {OpenSeadragon.Rect} a duplicate of this Rect */ clone: function() { - return new $.Rect(this.x, this.y, this.width, this.height); + return new $.Rect({ + x: this.x, + y: this.y, + width: this.width, + height: this.height, + degrees: this.degrees + }); }, /** @@ -114,10 +141,8 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ * the rectangle. */ getBottomRight: function() { - return new $.Point( - this.x + this.width, - this.y + this.height - ); + return new $.Point(this.x + this.width, this.y + this.height) + .rotate(this.degrees, this.getTopLeft()); }, /** @@ -128,10 +153,8 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ * the rectangle. */ getTopRight: function() { - return new $.Point( - this.x + this.width, - this.y - ); + return new $.Point(this.x + this.width, this.y) + .rotate(this.degrees, this.getTopLeft()); }, /** @@ -142,10 +165,8 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ * the rectangle. */ getBottomLeft: function() { - return new $.Point( - this.x, - this.y + this.height - ); + return new $.Point(this.x, this.y + this.height) + .rotate(this.degrees, this.getTopLeft()); }, /** @@ -158,7 +179,7 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ return new $.Point( this.x + this.width / 2.0, this.y + this.height / 2.0 - ); + ).rotate(this.degrees, this.getTopLeft()); }, /** @@ -177,28 +198,31 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ * @param {OpenSeadragon.Rect} rectangle The Rectangle to compare to. * @return {Boolean} 'true' if all components are equal, otherwise 'false'. */ - equals: function( other ) { - return ( other instanceof $.Rect ) && - ( this.x === other.x ) && - ( this.y === other.y ) && - ( this.width === other.width ) && - ( this.height === other.height ); + equals: function(other) { + return (other instanceof $.Rect) && + this.x === other.x && + this.y === other.y && + this.width === other.width && + this.height === other.height && + this.degrees === other.degrees; }, /** - * Multiply all dimensions in this Rect by a factor and return a new Rect. + * Multiply all dimensions (except degrees) in this Rect by a factor and + * return a new Rect. * @function * @param {Number} factor The factor to multiply vector components. * @returns {OpenSeadragon.Rect} A new rect representing the multiplication * of the vector components by the factor */ - times: function( factor ) { - return new OpenSeadragon.Rect( - this.x * factor, - this.y * factor, - this.width * factor, - this.height * factor - ); + times: function(factor) { + return new $.Rect({ + x: this.x * factor, + y: this.y * factor, + width: this.width * factor, + height: this.height * factor, + degrees: this.degrees + }); }, /** @@ -207,13 +231,14 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ * @param {OpenSeadragon.Point} delta The translation vector. * @returns {OpenSeadragon.Rect} A new rect with altered position */ - translate: function( delta ) { - return new OpenSeadragon.Rect( - this.x + delta.x, - this.y + delta.y, - this.width, - this.height - ); + translate: function(delta) { + return new $.Rect({ + x: this.x + delta.x, + y: this.y + delta.y, + width: this.width, + height: this.height, + degrees: this.degrees + }); }, /** @@ -223,67 +248,79 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ */ // ---------- union: function(rect) { + if (this.degrees !== 0 || rect.degrees !== 0) { + throw new Error('Only union of non rotated rectangles are supported.'); + } var left = Math.min(this.x, rect.x); var top = Math.min(this.y, rect.y); var right = Math.max(this.x + this.width, rect.x + rect.width); var bottom = Math.max(this.y + this.height, rect.y + rect.height); - return new OpenSeadragon.Rect(left, top, right - left, bottom - top); + return new $.Rect({ + left: left, + top: top, + width: right - left, + height: bottom - top + }); }, /** - * Rotates a rectangle around a point. Currently only 90, 180, and 270 - * degrees are supported. + * Rotates a rectangle around a point. * @function * @param {Number} degrees The angle in degrees to rotate. * @param {OpenSeadragon.Point} pivot The point about which to rotate. * Defaults to the center of the rectangle. * @return {OpenSeadragon.Rect} */ - rotate: function( degrees, pivot ) { - // TODO support arbitrary rotation - var width = this.width, - height = this.height, - newTopLeft; - - degrees = ( degrees + 360 ) % 360; - if (degrees % 90 !== 0) { - throw new Error('Currently only 0, 90, 180, and 270 degrees are supported.'); - } - - if( degrees === 0 ){ - return new $.Rect( - this.x, - this.y, - this.width, - this.height - ); + rotate: function(degrees, pivot) { + degrees = (degrees + 360) % 360; + if (degrees === 0) { + return this.clone(); } pivot = pivot || this.getCenter(); + var newTopLeft = this.getTopLeft().rotate(degrees, pivot); + var newTopRight = this.getTopRight().rotate(degrees, pivot); - switch ( degrees ) { - case 90: - newTopLeft = this.getBottomLeft(); - width = this.height; - height = this.width; - break; - case 180: - newTopLeft = this.getBottomRight(); - break; - case 270: - newTopLeft = this.getTopRight(); - width = this.height; - height = this.width; - break; - default: - newTopLeft = this.getTopLeft(); - break; + var diff = newTopRight.minus(newTopLeft); + var radians = Math.atan(diff.y / diff.x); + if (diff.x < 0) { + radians += Math.PI; + } else if (diff.y < 0) { + radians += 2 * Math.PI; } + return new $.Rect({ + x: newTopLeft.x, + y: newTopLeft.y, + width: this.width, + height: this.height, + degrees: radians / Math.PI * 180 + }); + }, - newTopLeft = newTopLeft.rotate(degrees, pivot); - - return new $.Rect(newTopLeft.x, newTopLeft.y, width, height); + /** + * Retrieves the smallest horizontal (degrees=0) rectangle which contains + * this rectangle. + * @returns {OpenSeadrayon.Rect} + */ + getBoundingBox: function() { + if (this.degrees === 0) { + return this.clone(); + } + var topLeft = this.getTopLeft(); + var topRight = this.getTopRight(); + var bottomLeft = this.getBottomLeft(); + var bottomRight = this.getBottomRight(); + var minX = Math.min(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x); + var maxX = Math.max(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x); + var minY = Math.min(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y); + var maxY = Math.max(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y); + return new $.Rect({ + x: minX, + y: minY, + width: maxX - minX, + height: maxY - minY + }); }, /** @@ -294,11 +331,12 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ */ toString: function() { return "[" + - (Math.round(this.x*100) / 100) + "," + - (Math.round(this.y*100) / 100) + "," + - (Math.round(this.width*100) / 100) + "x" + - (Math.round(this.height*100) / 100) + - "]"; + (Math.round(this.x * 100) / 100) + "," + + (Math.round(this.y * 100) / 100) + "," + + (Math.round(this.width * 100) / 100) + "x" + + (Math.round(this.height * 100) / 100) + "," + + (Math.round(this.degrees * 100) / 100) + "°" + + "]"; } }; diff --git a/test/coverage.html b/test/coverage.html index dc857202..15d3d490 100644 --- a/test/coverage.html +++ b/test/coverage.html @@ -75,6 +75,7 @@ + diff --git a/test/modules/rectangle.js b/test/modules/rectangle.js new file mode 100644 index 00000000..ac6e76a9 --- /dev/null +++ b/test/modules/rectangle.js @@ -0,0 +1,260 @@ +/* global module, asyncTest, $, ok, equal, notEqual, start, test, Util, testLog */ + +(function() { + + module('Rectangle', {}); + + var precision = 0.000000001; + + function assertPointsEquals(pointA, pointB, message) { + Util.assessNumericValue(pointA.x, pointB.x, precision, message + " x: "); + Util.assessNumericValue(pointA.y, pointB.y, precision, message + " y: "); + } + + function assertRectangleEquals(rectA, rectB, message) { + Util.assessNumericValue(rectA.x, rectB.x, precision, message + " x: "); + Util.assessNumericValue(rectA.y, rectB.y, precision, message + " y: "); + Util.assessNumericValue(rectA.width, rectB.width, precision, + message + " width: "); + Util.assessNumericValue(rectA.height, rectB.height, precision, + message + " height: "); + Util.assessNumericValue(rectA.degrees, rectB.degrees, precision, + message + " degrees: "); + } + + test('Legacy constructor', function() { + var rect = new OpenSeadragon.Rect(1, 2, 3, 4); + strictEqual(rect.x, 1, 'rect.x should be 1'); + strictEqual(rect.y, 2, 'rect.y should be 2'); + strictEqual(rect.width, 3, 'rect.width should be 3'); + strictEqual(rect.height, 4, 'rect.height should be 4'); + strictEqual(rect.degrees, 0, 'rect.degrees should be 0'); + }); + + test('Constructor', function() { + var rect = new OpenSeadragon.Rect({ + x: 1, + y: 2, + width: 3, + height: 4, + degrees: 5 + }); + strictEqual(rect.x, 1, 'rect.x should be 1'); + strictEqual(rect.y, 2, 'rect.y should be 2'); + strictEqual(rect.width, 3, 'rect.width should be 3'); + strictEqual(rect.height, 4, 'rect.height should be 4'); + strictEqual(rect.degrees, 5, 'rect.degrees should be 5'); + + rect = new OpenSeadragon.Rect({}); + strictEqual(rect.x, 0, 'rect.x should be 0'); + strictEqual(rect.y, 0, 'rect.y should be 0'); + strictEqual(rect.width, 0, 'rect.width should be 0'); + strictEqual(rect.height, 0, 'rect.height should be 0'); + strictEqual(rect.degrees, 0, 'rect.degrees should be 0'); + }); + + test('getTopLeft', function() { + var rect = new OpenSeadragon.Rect({ + x: 1, + y: 2, + width: 3, + height: 4, + degrees: 5 + }); + var expected = new OpenSeadragon.Point(1, 2); + ok(expected.equals(rect.getTopLeft()), "Incorrect top left point."); + }); + + test('getTopRight', function() { + var rect = new OpenSeadragon.Rect({ + x: 0, + y: 0, + width: 1, + height: 3 + }); + var expected = new OpenSeadragon.Point(1, 0); + ok(expected.equals(rect.getTopRight()), "Incorrect top right point."); + + rect.degrees = 45; + expected = new OpenSeadragon.Point(1 / Math.sqrt(2), 1 / Math.sqrt(2)); + assertPointsEquals(expected, rect.getTopRight(), + "Incorrect top right point with rotation."); + }); + + test('getBottomLeft', function() { + var rect = new OpenSeadragon.Rect({ + x: 0, + y: 0, + width: 3, + height: 1 + }); + var expected = new OpenSeadragon.Point(0, 1); + ok(expected.equals(rect.getBottomLeft()), "Incorrect bottom left point."); + + rect.degrees = 45; + expected = new OpenSeadragon.Point(-1 / Math.sqrt(2), 1 / Math.sqrt(2)); + assertPointsEquals(expected, rect.getBottomLeft(), + "Incorrect bottom left point with rotation."); + }); + + test('getBottomRight', function() { + var rect = new OpenSeadragon.Rect({ + x: 0, + y: 0, + width: 1, + height: 1 + }); + var expected = new OpenSeadragon.Point(1, 1); + ok(expected.equals(rect.getBottomRight()), "Incorrect bottom right point."); + + rect.degrees = 45; + expected = new OpenSeadragon.Point(0, Math.sqrt(2)); + assertPointsEquals(expected, rect.getBottomRight(), + "Incorrect bottom right point with 45 rotation."); + + rect.degrees = 90; + expected = new OpenSeadragon.Point(-1, 1); + assertPointsEquals(expected, rect.getBottomRight(), + "Incorrect bottom right point with 90 rotation."); + + rect.degrees = 135; + expected = new OpenSeadragon.Point(-Math.sqrt(2), 0); + assertPointsEquals(expected, rect.getBottomRight(), + "Incorrect bottom right point with 135 rotation."); + }); + + test('getCenter', function() { + var rect = new OpenSeadragon.Rect({ + x: 0, + y: 0, + width: 1, + height: 1 + }); + var expected = new OpenSeadragon.Point(0.5, 0.5); + ok(expected.equals(rect.getCenter()), "Incorrect center point."); + + rect.degrees = 45; + expected = new OpenSeadragon.Point(0, 0.5 * Math.sqrt(2)); + assertPointsEquals(expected, rect.getCenter(), + "Incorrect bottom right point with 45 rotation."); + + rect.degrees = 90; + expected = new OpenSeadragon.Point(-0.5, 0.5); + assertPointsEquals(expected, rect.getCenter(), + "Incorrect bottom right point with 90 rotation."); + + rect.degrees = 135; + expected = new OpenSeadragon.Point(-0.5 * Math.sqrt(2), 0); + assertPointsEquals(expected, rect.getCenter(), + "Incorrect bottom right point with 135 rotation."); + }); + + test('rotate', function() { + var rect = new OpenSeadragon.Rect({ + x: 0, + y: 0, + width: 2, + height: 1 + }); + + // Rotate 45deg around center. + var expected = new OpenSeadragon.Rect({ + x: 1 - 1 / (2 * Math.sqrt(2)), + y: 0.5 - 3 / (2 * Math.sqrt(2)), + width: 2, + height: 1, + degrees: 45 + }); + var actual = rect.rotate(45); + assertRectangleEquals(expected, actual, + "Incorrect rectangle after rotation of 45deg around center."); + + expected = new OpenSeadragon.Rect({ + x: 0, + y: 0, + width: 2, + height: 1, + degrees: 33 + }); + actual = rect.rotate(33, rect.getTopLeft()); + assertRectangleEquals(expected, actual, + "Incorrect rectangle after rotation of 33deg around topLeft."); + + expected = new OpenSeadragon.Rect({ + x: 0, + y: 0, + width: 2, + height: 1, + degrees: 101 + }); + actual = rect.rotate(101, rect.getTopLeft()); + assertRectangleEquals(expected, actual, + "Incorrect rectangle after rotation of 187deg around topLeft."); + + expected = new OpenSeadragon.Rect({ + x: 0, + y: 0, + width: 2, + height: 1, + degrees: 187 + }); + actual = rect.rotate(187, rect.getTopLeft()); + assertRectangleEquals(expected, actual, + "Incorrect rectangle after rotation of 187deg around topLeft."); + + expected = new OpenSeadragon.Rect({ + x: 0, + y: 0, + width: 2, + height: 1, + degrees: 300 + }); + actual = rect.rotate(300, rect.getTopLeft()); + assertRectangleEquals(expected, actual, + "Incorrect rectangle after rotation of 300deg around topLeft."); + }); + + test('getBoundingBox', function() { + var rect = new OpenSeadragon.Rect({ + x: 0, + y: 0, + width: 2, + height: 3 + }); + + var bb = rect.getBoundingBox(); + ok(rect.equals(bb), "Bounding box of horizontal rectangle should be " + + "identical to rectangle."); + + rect.degrees = 90; + var expected = new OpenSeadragon.Rect({ + x: -3, + y: 0, + width: 3, + height: 2 + }); + assertRectangleEquals(expected, rect.getBoundingBox(), + "Bounding box of rect rotated 90deg."); + + rect.degrees = 180; + var expected = new OpenSeadragon.Rect({ + x: -2, + y: -3, + width: 2, + height: 3 + }); + assertRectangleEquals(expected, rect.getBoundingBox(), + "Bounding box of rect rotated 180deg."); + + rect.degrees = 270; + var expected = new OpenSeadragon.Rect({ + x: 0, + y: -2, + width: 3, + height: 2 + }); + assertRectangleEquals(expected, rect.getBoundingBox(), + "Bounding box of rect rotated 270deg."); + }); + +})(); diff --git a/test/test.html b/test/test.html index e52eb66a..d50e53eb 100644 --- a/test/test.html +++ b/test/test.html @@ -39,6 +39,7 @@ + From 2e26ae5ff13b3a2df112e14c39435d238af8c927 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Mon, 30 Nov 2015 19:11:41 -0500 Subject: [PATCH 3/7] Remove Rect options contructor. --- src/rectangle.js | 116 +++++++++++++------------------- test/modules/rectangle.js | 135 +++++++------------------------------- 2 files changed, 69 insertions(+), 182 deletions(-) diff --git a/src/rectangle.js b/src/rectangle.js index 25817762..5224d1d8 100644 --- a/src/rectangle.js +++ b/src/rectangle.js @@ -32,7 +32,7 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -(function( $ ){ +(function($) { /** * @class Rect @@ -44,55 +44,39 @@ * degrees increases clockwise with 0 being the horizontal * * @memberof OpenSeadragon - * @param {Object} options - * @param {Number} options.x X coordinate of the top left corner of the rectangle. - * @param {Number} options.y Y coordinate of the top left corner of the rectangle. - * @param {Number} options.width Width of the rectangle. - * @param {Number} options.height Height of the rectangle. - * @param {Number} options.degrees Rotation of the rectangle around (x,y) in degrees. - * @param {Number} [x] Deprecated: The vector component 'x'. - * @param {Number} [y] Deprecated: The vector component 'y'. - * @param {Number} [width] Deprecated: The vector component 'width'. - * @param {Number} [height] Deprecated: The vector component 'height'. + * @param {Number} [x=0] The vector component 'x'. + * @param {Number} [y=0] The vector component 'y'. + * @param {Number} [width=0] The vector component 'width'. + * @param {Number} [height=0] The vector component 'height'. + * @param {Number} [degrees=0] Rotation of the rectangle around (x,y) in degrees. */ -$.Rect = function(x, y, width, height) { - - var options = x; - if (!$.isPlainObject(options)) { - options = { - x: x, - y: y, - width: width, - height: height - }; - } - +$.Rect = function(x, y, width, height, degrees) { /** * The vector component 'x'. * @member {Number} x * @memberof OpenSeadragon.Rect# */ - this.x = typeof(options.x) === "number" ? options.x : 0; + this.x = typeof(x) === "number" ? x : 0; /** * The vector component 'y'. * @member {Number} y * @memberof OpenSeadragon.Rect# */ - this.y = typeof(options.y) === "number" ? options.y : 0; + this.y = typeof(y) === "number" ? y : 0; /** * The vector component 'width'. * @member {Number} width * @memberof OpenSeadragon.Rect# */ - this.width = typeof(options.width) === "number" ? options.width : 0; + this.width = typeof(width) === "number" ? width : 0; /** * The vector component 'height'. * @member {Number} height * @memberof OpenSeadragon.Rect# */ - this.height = typeof(options.height) === "number" ? options.height : 0; + this.height = typeof(height) === "number" ? height : 0; - this.degrees = typeof(options.degrees) === "number" ? options.degrees : 0; + this.degrees = typeof(degrees) === "number" ? degrees : 0; }; $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ @@ -101,13 +85,12 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ * @returns {OpenSeadragon.Rect} a duplicate of this Rect */ clone: function() { - return new $.Rect({ - x: this.x, - y: this.y, - width: this.width, - height: this.height, - degrees: this.degrees - }); + return new $.Rect( + this.x, + this.y, + this.width, + this.height, + this.degrees); }, /** @@ -216,13 +199,12 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ * of the vector components by the factor */ times: function(factor) { - return new $.Rect({ - x: this.x * factor, - y: this.y * factor, - width: this.width * factor, - height: this.height * factor, - degrees: this.degrees - }); + return new $.Rect( + this.x * factor, + this.y * factor, + this.width * factor, + this.height * factor, + this.degrees); }, /** @@ -232,13 +214,12 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ * @returns {OpenSeadragon.Rect} A new rect with altered position */ translate: function(delta) { - return new $.Rect({ - x: this.x + delta.x, - y: this.y + delta.y, - width: this.width, - height: this.height, - degrees: this.degrees - }); + return new $.Rect( + this.x + delta.x, + this.y + delta.y, + this.width, + this.height, + this.degrees); }, /** @@ -256,12 +237,11 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ var right = Math.max(this.x + this.width, rect.x + rect.width); var bottom = Math.max(this.y + this.height, rect.y + rect.height); - return new $.Rect({ - left: left, - top: top, - width: right - left, - height: bottom - top - }); + return new $.Rect( + left, + top, + right - left, + bottom - top); }, /** @@ -289,13 +269,12 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ } else if (diff.y < 0) { radians += 2 * Math.PI; } - return new $.Rect({ - x: newTopLeft.x, - y: newTopLeft.y, - width: this.width, - height: this.height, - degrees: radians / Math.PI * 180 - }); + return new $.Rect( + newTopLeft.x, + newTopLeft.y, + this.width, + this.height, + radians / Math.PI * 180); }, /** @@ -315,12 +294,11 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ var maxX = Math.max(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x); var minY = Math.min(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y); var maxY = Math.max(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y); - return new $.Rect({ - x: minX, - y: minY, - width: maxX - minX, - height: maxY - minY - }); + return new $.Rect( + minX, + minY, + maxX - minX, + maxY - minY); }, /** @@ -341,4 +319,4 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ }; -}( OpenSeadragon )); +}(OpenSeadragon)); diff --git a/test/modules/rectangle.js b/test/modules/rectangle.js index ac6e76a9..22a6c0ca 100644 --- a/test/modules/rectangle.js +++ b/test/modules/rectangle.js @@ -22,30 +22,15 @@ message + " degrees: "); } - test('Legacy constructor', function() { - var rect = new OpenSeadragon.Rect(1, 2, 3, 4); - strictEqual(rect.x, 1, 'rect.x should be 1'); - strictEqual(rect.y, 2, 'rect.y should be 2'); - strictEqual(rect.width, 3, 'rect.width should be 3'); - strictEqual(rect.height, 4, 'rect.height should be 4'); - strictEqual(rect.degrees, 0, 'rect.degrees should be 0'); - }); - test('Constructor', function() { - var rect = new OpenSeadragon.Rect({ - x: 1, - y: 2, - width: 3, - height: 4, - degrees: 5 - }); + var rect = new OpenSeadragon.Rect(1, 2, 3, 4, 5); strictEqual(rect.x, 1, 'rect.x should be 1'); strictEqual(rect.y, 2, 'rect.y should be 2'); strictEqual(rect.width, 3, 'rect.width should be 3'); strictEqual(rect.height, 4, 'rect.height should be 4'); strictEqual(rect.degrees, 5, 'rect.degrees should be 5'); - rect = new OpenSeadragon.Rect({}); + rect = new OpenSeadragon.Rect(); strictEqual(rect.x, 0, 'rect.x should be 0'); strictEqual(rect.y, 0, 'rect.y should be 0'); strictEqual(rect.width, 0, 'rect.width should be 0'); @@ -54,24 +39,13 @@ }); test('getTopLeft', function() { - var rect = new OpenSeadragon.Rect({ - x: 1, - y: 2, - width: 3, - height: 4, - degrees: 5 - }); + var rect = new OpenSeadragon.Rect(1, 2, 3, 4, 5); var expected = new OpenSeadragon.Point(1, 2); ok(expected.equals(rect.getTopLeft()), "Incorrect top left point."); }); test('getTopRight', function() { - var rect = new OpenSeadragon.Rect({ - x: 0, - y: 0, - width: 1, - height: 3 - }); + var rect = new OpenSeadragon.Rect(0, 0, 1, 3); var expected = new OpenSeadragon.Point(1, 0); ok(expected.equals(rect.getTopRight()), "Incorrect top right point."); @@ -82,12 +56,7 @@ }); test('getBottomLeft', function() { - var rect = new OpenSeadragon.Rect({ - x: 0, - y: 0, - width: 3, - height: 1 - }); + var rect = new OpenSeadragon.Rect(0, 0, 3, 1); var expected = new OpenSeadragon.Point(0, 1); ok(expected.equals(rect.getBottomLeft()), "Incorrect bottom left point."); @@ -98,12 +67,7 @@ }); test('getBottomRight', function() { - var rect = new OpenSeadragon.Rect({ - x: 0, - y: 0, - width: 1, - height: 1 - }); + var rect = new OpenSeadragon.Rect(0, 0, 1, 1); var expected = new OpenSeadragon.Point(1, 1); ok(expected.equals(rect.getBottomRight()), "Incorrect bottom right point."); @@ -124,12 +88,7 @@ }); test('getCenter', function() { - var rect = new OpenSeadragon.Rect({ - x: 0, - y: 0, - width: 1, - height: 1 - }); + var rect = new OpenSeadragon.Rect(0, 0, 1, 1); var expected = new OpenSeadragon.Point(0.5, 0.5); ok(expected.equals(rect.getCenter()), "Incorrect center point."); @@ -150,109 +109,59 @@ }); test('rotate', function() { - var rect = new OpenSeadragon.Rect({ - x: 0, - y: 0, - width: 2, - height: 1 - }); + var rect = new OpenSeadragon.Rect(0, 0, 2, 1); // Rotate 45deg around center. - var expected = new OpenSeadragon.Rect({ - x: 1 - 1 / (2 * Math.sqrt(2)), - y: 0.5 - 3 / (2 * Math.sqrt(2)), - width: 2, - height: 1, - degrees: 45 - }); + var expected = new OpenSeadragon.Rect( + 1 - 1 / (2 * Math.sqrt(2)), + 0.5 - 3 / (2 * Math.sqrt(2)), + 2, + 1, + 45); var actual = rect.rotate(45); assertRectangleEquals(expected, actual, "Incorrect rectangle after rotation of 45deg around center."); - expected = new OpenSeadragon.Rect({ - x: 0, - y: 0, - width: 2, - height: 1, - degrees: 33 - }); + expected = new OpenSeadragon.Rect(0, 0, 2, 1, 33); actual = rect.rotate(33, rect.getTopLeft()); assertRectangleEquals(expected, actual, "Incorrect rectangle after rotation of 33deg around topLeft."); - expected = new OpenSeadragon.Rect({ - x: 0, - y: 0, - width: 2, - height: 1, - degrees: 101 - }); + expected = new OpenSeadragon.Rect(0, 0, 2, 1, 101); actual = rect.rotate(101, rect.getTopLeft()); assertRectangleEquals(expected, actual, "Incorrect rectangle after rotation of 187deg around topLeft."); - expected = new OpenSeadragon.Rect({ - x: 0, - y: 0, - width: 2, - height: 1, - degrees: 187 - }); + expected = new OpenSeadragon.Rect(0, 0, 2, 1, 187); actual = rect.rotate(187, rect.getTopLeft()); assertRectangleEquals(expected, actual, "Incorrect rectangle after rotation of 187deg around topLeft."); - expected = new OpenSeadragon.Rect({ - x: 0, - y: 0, - width: 2, - height: 1, - degrees: 300 - }); + expected = new OpenSeadragon.Rect(0, 0, 2, 1, 300); actual = rect.rotate(300, rect.getTopLeft()); assertRectangleEquals(expected, actual, "Incorrect rectangle after rotation of 300deg around topLeft."); }); test('getBoundingBox', function() { - var rect = new OpenSeadragon.Rect({ - x: 0, - y: 0, - width: 2, - height: 3 - }); + var rect = new OpenSeadragon.Rect(0, 0, 2, 3); var bb = rect.getBoundingBox(); ok(rect.equals(bb), "Bounding box of horizontal rectangle should be " + "identical to rectangle."); rect.degrees = 90; - var expected = new OpenSeadragon.Rect({ - x: -3, - y: 0, - width: 3, - height: 2 - }); + var expected = new OpenSeadragon.Rect(-3, 0, 3, 2); assertRectangleEquals(expected, rect.getBoundingBox(), "Bounding box of rect rotated 90deg."); rect.degrees = 180; - var expected = new OpenSeadragon.Rect({ - x: -2, - y: -3, - width: 2, - height: 3 - }); + var expected = new OpenSeadragon.Rect(-2, -3, 2, 3); assertRectangleEquals(expected, rect.getBoundingBox(), "Bounding box of rect rotated 180deg."); rect.degrees = 270; - var expected = new OpenSeadragon.Rect({ - x: 0, - y: -2, - width: 3, - height: 2 - }); + var expected = new OpenSeadragon.Rect(0, -2, 3, 2); assertRectangleEquals(expected, rect.getBoundingBox(), "Bounding box of rect rotated 270deg."); }); From abc11685821f890ac007e4d0341f9268246edcfe Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Mon, 30 Nov 2015 20:18:05 -0500 Subject: [PATCH 4/7] Remove superfluous comment. --- test/modules/rectangle.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/modules/rectangle.js b/test/modules/rectangle.js index 22a6c0ca..833d5857 100644 --- a/test/modules/rectangle.js +++ b/test/modules/rectangle.js @@ -111,7 +111,6 @@ test('rotate', function() { var rect = new OpenSeadragon.Rect(0, 0, 2, 1); - // Rotate 45deg around center. var expected = new OpenSeadragon.Rect( 1 - 1 / (2 * Math.sqrt(2)), 0.5 - 3 / (2 * Math.sqrt(2)), From fc919ff56d36636225c80797382228954eaf530e Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Mon, 30 Nov 2015 20:44:06 -0500 Subject: [PATCH 5/7] Make union reasons on bounding boxes. --- src/rectangle.js | 24 ++++++++++++++---------- test/modules/rectangle.js | 20 ++++++++++++++++++++ 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/rectangle.js b/src/rectangle.js index 5224d1d8..57bd48f1 100644 --- a/src/rectangle.js +++ b/src/rectangle.js @@ -172,7 +172,7 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ * the width and height of the rectangle. */ getSize: function() { - return new $.Point( this.width, this.height ); + return new $.Point(this.width, this.height); }, /** @@ -223,19 +223,23 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ }, /** - * Returns the smallest rectangle that will contain this and the given rectangle. + * Returns the smallest rectangle that will contain this and the given + * rectangle bounding boxes. * @param {OpenSeadragon.Rect} rect * @return {OpenSeadragon.Rect} The new rectangle. */ - // ---------- union: function(rect) { - if (this.degrees !== 0 || rect.degrees !== 0) { - throw new Error('Only union of non rotated rectangles are supported.'); - } - var left = Math.min(this.x, rect.x); - var top = Math.min(this.y, rect.y); - var right = Math.max(this.x + this.width, rect.x + rect.width); - var bottom = Math.max(this.y + this.height, rect.y + rect.height); + var thisBoundingBox = this.getBoundingBox(); + var otherBoundingBox = rect.getBoundingBox(); + + var left = Math.min(thisBoundingBox.x, otherBoundingBox.x); + var top = Math.min(thisBoundingBox.y, otherBoundingBox.y); + var right = Math.max( + thisBoundingBox.x + thisBoundingBox.width, + otherBoundingBox.x + otherBoundingBox.width); + var bottom = Math.max( + thisBoundingBox.y + thisBoundingBox.height, + otherBoundingBox.y + otherBoundingBox.height); return new $.Rect( left, diff --git a/test/modules/rectangle.js b/test/modules/rectangle.js index 833d5857..09f5e467 100644 --- a/test/modules/rectangle.js +++ b/test/modules/rectangle.js @@ -108,6 +108,26 @@ "Incorrect bottom right point with 135 rotation."); }); + test('union', function() { + var rect1 = new OpenSeadragon.Rect(2, 2, 2, 3); + var rect2 = new OpenSeadragon.Rect(0, 1, 1, 1); + var expected = new OpenSeadragon.Rect(0, 1, 4, 4); + var actual = rect1.union(rect2); + assertRectangleEquals(expected, actual, + "Incorrect union with horizontal rectangles."); + + rect1 = new OpenSeadragon.Rect(0, -Math.sqrt(2), 2, 2, 45); + rect2 = new OpenSeadragon.Rect(1, 0, 2, 2, 0); + expected = new OpenSeadragon.Rect( + -Math.sqrt(2), + -Math.sqrt(2), + 3 + Math.sqrt(2), + 2 + Math.sqrt(2)); + actual = rect1.union(rect2); + assertRectangleEquals(expected, actual, + "Incorrect union with non horizontal rectangles."); + }); + test('rotate', function() { var rect = new OpenSeadragon.Rect(0, 0, 2, 1); From 5e362554e22d52b8573b9c4c94b96acef5089682 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Sat, 5 Dec 2015 17:51:21 -0500 Subject: [PATCH 6/7] Normalize the rectangles --- src/rectangle.js | 36 ++++++++++++++- test/modules/rectangle.js | 93 +++++++++++++++++++++++++++++---------- 2 files changed, 105 insertions(+), 24 deletions(-) diff --git a/src/rectangle.js b/src/rectangle.js index 57bd48f1..35544cdf 100644 --- a/src/rectangle.js +++ b/src/rectangle.js @@ -43,6 +43,8 @@ * y increases when going to the bottom * degrees increases clockwise with 0 being the horizontal * + * The constructor normalizes the rectangle to always have 0 <= degrees < 90 + * * @memberof OpenSeadragon * @param {Number} [x=0] The vector component 'x'. * @param {Number} [y=0] The vector component 'y'. @@ -77,6 +79,35 @@ $.Rect = function(x, y, width, height, degrees) { this.height = typeof(height) === "number" ? height : 0; this.degrees = typeof(degrees) === "number" ? degrees : 0; + + // Normalizes the rectangle. + this.degrees = this.degrees % 360; + if (this.degrees < 0) { + this.degrees += 360; + } + var newTopLeft, newWidth; + if (this.degrees >= 270) { + newTopLeft = this.getTopRight(); + this.x = newTopLeft.x; + this.y = newTopLeft.y; + newWidth = this.height; + this.height = this.width; + this.width = newWidth; + this.degrees -= 270; + } else if (this.degrees >= 180) { + newTopLeft = this.getBottomRight(); + this.x = newTopLeft.x; + this.y = newTopLeft.y; + this.degrees -= 180; + } else if (this.degrees >= 90) { + newTopLeft = this.getBottomLeft(); + this.x = newTopLeft.x; + this.y = newTopLeft.y; + newWidth = this.height; + this.height = this.width; + this.width = newWidth; + this.degrees -= 90; + } }; $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ @@ -257,10 +288,13 @@ $.Rect.prototype = /** @lends OpenSeadragon.Rect.prototype */{ * @return {OpenSeadragon.Rect} */ rotate: function(degrees, pivot) { - degrees = (degrees + 360) % 360; + degrees = degrees % 360; if (degrees === 0) { return this.clone(); } + if (degrees < 0) { + degrees += 360; + } pivot = pivot || this.getCenter(); var newTopLeft = this.getTopLeft().rotate(degrees, pivot); diff --git a/test/modules/rectangle.js b/test/modules/rectangle.js index 09f5e467..437f20b4 100644 --- a/test/modules/rectangle.js +++ b/test/modules/rectangle.js @@ -15,11 +15,11 @@ Util.assessNumericValue(rectA.x, rectB.x, precision, message + " x: "); Util.assessNumericValue(rectA.y, rectB.y, precision, message + " y: "); Util.assessNumericValue(rectA.width, rectB.width, precision, - message + " width: "); + message + " width: "); Util.assessNumericValue(rectA.height, rectB.height, precision, - message + " height: "); + message + " height: "); Util.assessNumericValue(rectA.degrees, rectB.degrees, precision, - message + " degrees: "); + message + " degrees: "); } test('Constructor', function() { @@ -36,6 +36,39 @@ strictEqual(rect.width, 0, 'rect.width should be 0'); strictEqual(rect.height, 0, 'rect.height should be 0'); strictEqual(rect.degrees, 0, 'rect.degrees should be 0'); + + rect = new OpenSeadragon.Rect(0, 0, 1, 2, -405); + Util.assessNumericValue(Math.sqrt(2) / 2, rect.x, precision, + 'rect.x should be sqrt(2)/2'); + Util.assessNumericValue(-Math.sqrt(2) / 2, rect.y, precision, + 'rect.y should be -sqrt(2)/2'); + Util.assessNumericValue(2, rect.width, precision, + 'rect.width should be 2'); + Util.assessNumericValue(1, rect.height, precision, + 'rect.height should be 1'); + strictEqual(45, rect.degrees, 'rect.degrees should be 45'); + + rect = new OpenSeadragon.Rect(0, 0, 1, 2, 135); + Util.assessNumericValue(-Math.sqrt(2), rect.x, precision, + 'rect.x should be -sqrt(2)'); + Util.assessNumericValue(-Math.sqrt(2), rect.y, precision, + 'rect.y should be -sqrt(2)'); + Util.assessNumericValue(2, rect.width, precision, + 'rect.width should be 2'); + Util.assessNumericValue(1, rect.height, precision, + 'rect.height should be 1'); + strictEqual(45, rect.degrees, 'rect.degrees should be 45'); + + rect = new OpenSeadragon.Rect(0, 0, 1, 1, 585); + Util.assessNumericValue(0, rect.x, precision, + 'rect.x should be 0'); + Util.assessNumericValue(-Math.sqrt(2), rect.y, precision, + 'rect.y should be -sqrt(2)'); + Util.assessNumericValue(1, rect.width, precision, + 'rect.width should be 1'); + Util.assessNumericValue(1, rect.height, precision, + 'rect.height should be 1'); + strictEqual(45, rect.degrees, 'rect.degrees should be 45'); }); test('getTopLeft', function() { @@ -52,7 +85,7 @@ rect.degrees = 45; expected = new OpenSeadragon.Point(1 / Math.sqrt(2), 1 / Math.sqrt(2)); assertPointsEquals(expected, rect.getTopRight(), - "Incorrect top right point with rotation."); + "Incorrect top right point with rotation."); }); test('getBottomLeft', function() { @@ -63,7 +96,7 @@ rect.degrees = 45; expected = new OpenSeadragon.Point(-1 / Math.sqrt(2), 1 / Math.sqrt(2)); assertPointsEquals(expected, rect.getBottomLeft(), - "Incorrect bottom left point with rotation."); + "Incorrect bottom left point with rotation."); }); test('getBottomRight', function() { @@ -74,17 +107,17 @@ rect.degrees = 45; expected = new OpenSeadragon.Point(0, Math.sqrt(2)); assertPointsEquals(expected, rect.getBottomRight(), - "Incorrect bottom right point with 45 rotation."); + "Incorrect bottom right point with 45 rotation."); rect.degrees = 90; expected = new OpenSeadragon.Point(-1, 1); assertPointsEquals(expected, rect.getBottomRight(), - "Incorrect bottom right point with 90 rotation."); + "Incorrect bottom right point with 90 rotation."); rect.degrees = 135; expected = new OpenSeadragon.Point(-Math.sqrt(2), 0); assertPointsEquals(expected, rect.getBottomRight(), - "Incorrect bottom right point with 135 rotation."); + "Incorrect bottom right point with 135 rotation."); }); test('getCenter', function() { @@ -95,17 +128,31 @@ rect.degrees = 45; expected = new OpenSeadragon.Point(0, 0.5 * Math.sqrt(2)); assertPointsEquals(expected, rect.getCenter(), - "Incorrect bottom right point with 45 rotation."); + "Incorrect bottom right point with 45 rotation."); rect.degrees = 90; expected = new OpenSeadragon.Point(-0.5, 0.5); assertPointsEquals(expected, rect.getCenter(), - "Incorrect bottom right point with 90 rotation."); + "Incorrect bottom right point with 90 rotation."); rect.degrees = 135; expected = new OpenSeadragon.Point(-0.5 * Math.sqrt(2), 0); assertPointsEquals(expected, rect.getCenter(), - "Incorrect bottom right point with 135 rotation."); + "Incorrect bottom right point with 135 rotation."); + }); + + test('times', function() { + var rect = new OpenSeadragon.Rect(1, 2, 3, 4, 45); + var expected = new OpenSeadragon.Rect(2, 4, 6, 8, 45); + var actual = rect.times(2); + assertRectangleEquals(expected, actual, "Incorrect x2 rectangles."); + }); + + test('translate', function() { + var rect = new OpenSeadragon.Rect(1, 2, 3, 4, 45); + var expected = new OpenSeadragon.Rect(2, 4, 3, 4, 45); + var actual = rect.translate(new OpenSeadragon.Point(1, 2)); + assertRectangleEquals(expected, actual, "Incorrect translation."); }); test('union', function() { @@ -114,7 +161,7 @@ var expected = new OpenSeadragon.Rect(0, 1, 4, 4); var actual = rect1.union(rect2); assertRectangleEquals(expected, actual, - "Incorrect union with horizontal rectangles."); + "Incorrect union with horizontal rectangles."); rect1 = new OpenSeadragon.Rect(0, -Math.sqrt(2), 2, 2, 45); rect2 = new OpenSeadragon.Rect(1, 0, 2, 2, 0); @@ -125,7 +172,7 @@ 2 + Math.sqrt(2)); actual = rect1.union(rect2); assertRectangleEquals(expected, actual, - "Incorrect union with non horizontal rectangles."); + "Incorrect union with non horizontal rectangles."); }); test('rotate', function() { @@ -137,29 +184,29 @@ 2, 1, 45); - var actual = rect.rotate(45); + var actual = rect.rotate(-675); assertRectangleEquals(expected, actual, - "Incorrect rectangle after rotation of 45deg around center."); + "Incorrect rectangle after rotation of -675deg around center."); expected = new OpenSeadragon.Rect(0, 0, 2, 1, 33); actual = rect.rotate(33, rect.getTopLeft()); assertRectangleEquals(expected, actual, - "Incorrect rectangle after rotation of 33deg around topLeft."); + "Incorrect rectangle after rotation of 33deg around topLeft."); expected = new OpenSeadragon.Rect(0, 0, 2, 1, 101); actual = rect.rotate(101, rect.getTopLeft()); assertRectangleEquals(expected, actual, - "Incorrect rectangle after rotation of 187deg around topLeft."); + "Incorrect rectangle after rotation of 187deg around topLeft."); expected = new OpenSeadragon.Rect(0, 0, 2, 1, 187); actual = rect.rotate(187, rect.getTopLeft()); assertRectangleEquals(expected, actual, - "Incorrect rectangle after rotation of 187deg around topLeft."); + "Incorrect rectangle after rotation of 187deg around topLeft."); expected = new OpenSeadragon.Rect(0, 0, 2, 1, 300); actual = rect.rotate(300, rect.getTopLeft()); assertRectangleEquals(expected, actual, - "Incorrect rectangle after rotation of 300deg around topLeft."); + "Incorrect rectangle after rotation of 300deg around topLeft."); }); test('getBoundingBox', function() { @@ -167,22 +214,22 @@ var bb = rect.getBoundingBox(); ok(rect.equals(bb), "Bounding box of horizontal rectangle should be " + - "identical to rectangle."); + "identical to rectangle."); rect.degrees = 90; var expected = new OpenSeadragon.Rect(-3, 0, 3, 2); assertRectangleEquals(expected, rect.getBoundingBox(), - "Bounding box of rect rotated 90deg."); + "Bounding box of rect rotated 90deg."); rect.degrees = 180; var expected = new OpenSeadragon.Rect(-2, -3, 2, 3); assertRectangleEquals(expected, rect.getBoundingBox(), - "Bounding box of rect rotated 180deg."); + "Bounding box of rect rotated 180deg."); rect.degrees = 270; var expected = new OpenSeadragon.Rect(0, -2, 3, 2); assertRectangleEquals(expected, rect.getBoundingBox(), - "Bounding box of rect rotated 270deg."); + "Bounding box of rect rotated 270deg."); }); })(); From 1d04ceadffc7213634e513f9fe4a4cf2dbc20547 Mon Sep 17 00:00:00 2001 From: Antoine Vandecreme Date: Sat, 5 Dec 2015 19:18:56 -0500 Subject: [PATCH 7/7] Fix home bounds with rotation. Fix #567 and #463 --- src/viewport.js | 23 +++++++++------- test/helpers/test.js | 18 ++++++++++++ test/modules/rectangle.js | 58 +++++++++++++++------------------------ test/modules/viewport.js | 20 ++++++++++++++ 4 files changed, 73 insertions(+), 46 deletions(-) diff --git a/src/viewport.js b/src/viewport.js index c619198d..b69ccbcb 100644 --- a/src/viewport.js +++ b/src/viewport.js @@ -175,12 +175,12 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{ $.console.assert(bounds.width > 0, "[Viewport.setHomeBounds] bounds.width must be greater than 0"); $.console.assert(bounds.height > 0, "[Viewport.setHomeBounds] bounds.height must be greater than 0"); - this.homeBounds = bounds.clone(); + this.homeBounds = bounds.clone().rotate(this.degrees).getBoundingBox(); this.contentSize = this.homeBounds.getSize().times(contentFactor); this.contentAspectX = this.contentSize.x / this.contentSize.y; this.contentAspectY = this.contentSize.y / this.contentSize.x; - if( this.viewer ){ + if (this.viewer) { /** * Raised when the viewer's content size or home bounds are reset * (see {@link OpenSeadragon.Viewport#resetContentSize}, @@ -195,7 +195,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{ * @property {Number} contentFactor * @property {?Object} userData - Arbitrary subscriber-defined object. */ - this.viewer.raiseEvent( 'reset-size', { + this.viewer.raiseEvent('reset-size', { contentSize: this.contentSize.clone(), contentFactor: contentFactor, homeBounds: this.homeBounds.clone() @@ -807,13 +807,19 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{ * @function * @return {OpenSeadragon.Viewport} Chainable. */ - setRotation: function( degrees ) { - if( !( this.viewer && this.viewer.drawer.canRotate() ) ) { + setRotation: function(degrees) { + if (!this.viewer || !this.viewer.drawer.canRotate()) { return this; } - degrees = ( degrees + 360 ) % 360; + degrees = degrees % 360; + if (degrees < 0) { + degrees += 360; + } this.degrees = degrees; + this.setHomeBounds( + this.viewer.world.getHomeBounds(), + this.viewer.world.getContentFactor()); this.viewer.forceRedraw(); /** @@ -826,10 +832,7 @@ $.Viewport.prototype = /** @lends OpenSeadragon.Viewport.prototype */{ * @property {Number} degrees - The number of degrees the rotation was set to. * @property {?Object} userData - Arbitrary subscriber-defined object. */ - if (this.viewer !== null) - { - this.viewer.raiseEvent('rotate', {"degrees": degrees}); - } + this.viewer.raiseEvent('rotate', {"degrees": degrees}); return this; }, diff --git a/test/helpers/test.js b/test/helpers/test.js index 54a28412..3dbb5468 100644 --- a/test/helpers/test.js +++ b/test/helpers/test.js @@ -68,6 +68,24 @@ ok( Util.equalsWithVariance( value1, value2, variance ), message + " Expected:" + value1 + " Found: " + value2 + " Variance: " + variance ); }, + // ---------- + assertPointsEquals: function (pointA, pointB, precision, message) { + Util.assessNumericValue(pointA.x, pointB.x, precision, message + " x: "); + Util.assessNumericValue(pointA.y, pointB.y, precision, message + " y: "); + }, + + // ---------- + assertRectangleEquals: function (rectA, rectB, precision, message) { + Util.assessNumericValue(rectA.x, rectB.x, precision, message + " x: "); + Util.assessNumericValue(rectA.y, rectB.y, precision, message + " y: "); + Util.assessNumericValue(rectA.width, rectB.width, precision, + message + " width: "); + Util.assessNumericValue(rectA.height, rectB.height, precision, + message + " height: "); + Util.assessNumericValue(rectA.degrees, rectB.degrees, precision, + message + " degrees: "); + }, + // ---------- timeWatcher: function ( time ) { time = time || 2000; diff --git a/test/modules/rectangle.js b/test/modules/rectangle.js index 437f20b4..7e905f58 100644 --- a/test/modules/rectangle.js +++ b/test/modules/rectangle.js @@ -6,22 +6,6 @@ var precision = 0.000000001; - function assertPointsEquals(pointA, pointB, message) { - Util.assessNumericValue(pointA.x, pointB.x, precision, message + " x: "); - Util.assessNumericValue(pointA.y, pointB.y, precision, message + " y: "); - } - - function assertRectangleEquals(rectA, rectB, message) { - Util.assessNumericValue(rectA.x, rectB.x, precision, message + " x: "); - Util.assessNumericValue(rectA.y, rectB.y, precision, message + " y: "); - Util.assessNumericValue(rectA.width, rectB.width, precision, - message + " width: "); - Util.assessNumericValue(rectA.height, rectB.height, precision, - message + " height: "); - Util.assessNumericValue(rectA.degrees, rectB.degrees, precision, - message + " degrees: "); - } - test('Constructor', function() { var rect = new OpenSeadragon.Rect(1, 2, 3, 4, 5); strictEqual(rect.x, 1, 'rect.x should be 1'); @@ -84,7 +68,7 @@ rect.degrees = 45; expected = new OpenSeadragon.Point(1 / Math.sqrt(2), 1 / Math.sqrt(2)); - assertPointsEquals(expected, rect.getTopRight(), + Util.assertPointsEquals(expected, rect.getTopRight(), precision, "Incorrect top right point with rotation."); }); @@ -95,7 +79,7 @@ rect.degrees = 45; expected = new OpenSeadragon.Point(-1 / Math.sqrt(2), 1 / Math.sqrt(2)); - assertPointsEquals(expected, rect.getBottomLeft(), + Util.assertPointsEquals(expected, rect.getBottomLeft(), precision, "Incorrect bottom left point with rotation."); }); @@ -106,17 +90,17 @@ rect.degrees = 45; expected = new OpenSeadragon.Point(0, Math.sqrt(2)); - assertPointsEquals(expected, rect.getBottomRight(), + Util.assertPointsEquals(expected, rect.getBottomRight(), precision, "Incorrect bottom right point with 45 rotation."); rect.degrees = 90; expected = new OpenSeadragon.Point(-1, 1); - assertPointsEquals(expected, rect.getBottomRight(), + Util.assertPointsEquals(expected, rect.getBottomRight(), precision, "Incorrect bottom right point with 90 rotation."); rect.degrees = 135; expected = new OpenSeadragon.Point(-Math.sqrt(2), 0); - assertPointsEquals(expected, rect.getBottomRight(), + Util.assertPointsEquals(expected, rect.getBottomRight(), precision, "Incorrect bottom right point with 135 rotation."); }); @@ -127,17 +111,17 @@ rect.degrees = 45; expected = new OpenSeadragon.Point(0, 0.5 * Math.sqrt(2)); - assertPointsEquals(expected, rect.getCenter(), + Util.assertPointsEquals(expected, rect.getCenter(), precision, "Incorrect bottom right point with 45 rotation."); rect.degrees = 90; expected = new OpenSeadragon.Point(-0.5, 0.5); - assertPointsEquals(expected, rect.getCenter(), + Util.assertPointsEquals(expected, rect.getCenter(), precision, "Incorrect bottom right point with 90 rotation."); rect.degrees = 135; expected = new OpenSeadragon.Point(-0.5 * Math.sqrt(2), 0); - assertPointsEquals(expected, rect.getCenter(), + Util.assertPointsEquals(expected, rect.getCenter(), precision, "Incorrect bottom right point with 135 rotation."); }); @@ -145,14 +129,16 @@ var rect = new OpenSeadragon.Rect(1, 2, 3, 4, 45); var expected = new OpenSeadragon.Rect(2, 4, 6, 8, 45); var actual = rect.times(2); - assertRectangleEquals(expected, actual, "Incorrect x2 rectangles."); + Util.assertRectangleEquals(expected, actual, precision, + "Incorrect x2 rectangles."); }); test('translate', function() { var rect = new OpenSeadragon.Rect(1, 2, 3, 4, 45); var expected = new OpenSeadragon.Rect(2, 4, 3, 4, 45); var actual = rect.translate(new OpenSeadragon.Point(1, 2)); - assertRectangleEquals(expected, actual, "Incorrect translation."); + Util.assertRectangleEquals(expected, actual, precision, + "Incorrect translation."); }); test('union', function() { @@ -160,7 +146,7 @@ var rect2 = new OpenSeadragon.Rect(0, 1, 1, 1); var expected = new OpenSeadragon.Rect(0, 1, 4, 4); var actual = rect1.union(rect2); - assertRectangleEquals(expected, actual, + Util.assertRectangleEquals(expected, actual, precision, "Incorrect union with horizontal rectangles."); rect1 = new OpenSeadragon.Rect(0, -Math.sqrt(2), 2, 2, 45); @@ -171,7 +157,7 @@ 3 + Math.sqrt(2), 2 + Math.sqrt(2)); actual = rect1.union(rect2); - assertRectangleEquals(expected, actual, + Util.assertRectangleEquals(expected, actual, precision, "Incorrect union with non horizontal rectangles."); }); @@ -185,27 +171,27 @@ 1, 45); var actual = rect.rotate(-675); - assertRectangleEquals(expected, actual, + Util.assertRectangleEquals(expected, actual, precision, "Incorrect rectangle after rotation of -675deg around center."); expected = new OpenSeadragon.Rect(0, 0, 2, 1, 33); actual = rect.rotate(33, rect.getTopLeft()); - assertRectangleEquals(expected, actual, + Util.assertRectangleEquals(expected, actual, precision, "Incorrect rectangle after rotation of 33deg around topLeft."); expected = new OpenSeadragon.Rect(0, 0, 2, 1, 101); actual = rect.rotate(101, rect.getTopLeft()); - assertRectangleEquals(expected, actual, + Util.assertRectangleEquals(expected, actual, precision, "Incorrect rectangle after rotation of 187deg around topLeft."); expected = new OpenSeadragon.Rect(0, 0, 2, 1, 187); actual = rect.rotate(187, rect.getTopLeft()); - assertRectangleEquals(expected, actual, + Util.assertRectangleEquals(expected, actual, precision, "Incorrect rectangle after rotation of 187deg around topLeft."); expected = new OpenSeadragon.Rect(0, 0, 2, 1, 300); actual = rect.rotate(300, rect.getTopLeft()); - assertRectangleEquals(expected, actual, + Util.assertRectangleEquals(expected, actual, precision, "Incorrect rectangle after rotation of 300deg around topLeft."); }); @@ -218,17 +204,17 @@ rect.degrees = 90; var expected = new OpenSeadragon.Rect(-3, 0, 3, 2); - assertRectangleEquals(expected, rect.getBoundingBox(), + Util.assertRectangleEquals(expected, rect.getBoundingBox(), precision, "Bounding box of rect rotated 90deg."); rect.degrees = 180; var expected = new OpenSeadragon.Rect(-2, -3, 2, 3); - assertRectangleEquals(expected, rect.getBoundingBox(), + Util.assertRectangleEquals(expected, rect.getBoundingBox(), precision, "Bounding box of rect rotated 180deg."); rect.degrees = 270; var expected = new OpenSeadragon.Rect(0, -2, 3, 2); - assertRectangleEquals(expected, rect.getBoundingBox(), + Util.assertRectangleEquals(expected, rect.getBoundingBox(), precision, "Bounding box of rect rotated 270deg."); }); diff --git a/test/modules/viewport.js b/test/modules/viewport.js index f480a6cd..ff485395 100644 --- a/test/modules/viewport.js +++ b/test/modules/viewport.js @@ -218,6 +218,26 @@ }); }); + asyncTest('getHomeBoundsWithRotation', function() { + function openHandler() { + viewer.removeHandler('open', openHandler); + var viewport = viewer.viewport; + viewport.setRotation(-675); + Util.assertRectangleEquals( + viewport.getHomeBounds(), + new OpenSeadragon.Rect( + (1 - Math.sqrt(2)) / 2, + (1 - Math.sqrt(2)) / 2, + Math.sqrt(2), + Math.sqrt(2)), + 0.00000001, + "Test getHomeBounds with degrees = -675"); + start(); + } + viewer.addHandler('open', openHandler); + viewer.open(DZI_PATH); + }); + asyncTest('getHomeZoom', function() { reopenViewerHelper({ property: 'defaultZoomLevel',