diff --git a/src/drawer.js b/src/drawer.js index ff658aaf..18a63ece 100644 --- a/src/drawer.js +++ b/src/drawer.js @@ -166,6 +166,41 @@ $.Drawer.prototype = { return this; }, + /** + * This function converts the given point from to the drawer coordinate by + * multiplying it with the pixel density. + * This function does not take rotation into account, thus assuming provided + * point is at 0 degree. + * @param {OpenSeadragon.Point} point - the pixel point to convert + */ + viewportCoordToDrawerCoord: function(point) { + var vpPoint = this.viewport.pixelFromPointNoRotate(point, true); + return new $.Point( + vpPoint.x * $.pixelDensityRatio, + vpPoint.y * $.pixelDensityRatio + ); + }, + + /** + * This function will create multiple polygon paths on the drawing context by provided polygons, + * then clip the context to the paths. + * @param {(OpenSeadragon.Point[])[]} polygons - an array of polygons. A polygon is an array of OpenSeadragon.Point + * @param {Boolean} useSketch - Whether to use the sketch canvas or not. + */ + clipWithPolygons: function (polygons, useSketch) { + if (!this.useCanvas) { + return; + } + var context = this._getContext(useSketch); + context.beginPath(); + polygons.forEach(function (polygon) { + polygon.forEach(function (coord, i) { + context[i === 0 ? 'moveTo' : 'lineTo'](coord.x, coord.y); + }); + }); + context.clip(); + }, + /** * Set the opacity of the drawer. * @param {Number} opacity diff --git a/src/tiledimage.js b/src/tiledimage.js index 54cc84fe..9c2fc4ed 100644 --- a/src/tiledimage.js +++ b/src/tiledimage.js @@ -674,6 +674,58 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag this._setScale(height / this.normHeight, immediately); }, + /** + * Sets an array of polygons to crop the TiledImage during draw tiles. + * The render function will use the default non-zero winding rule. + * @param Polygons represented in an array of point object in image coordinates. + * Example format: [ + * [{x: 197, y:172}, {x: 226, y:172}, {x: 226, y:198}, {x: 197, y:198}], // First polygon + * [{x: 328, y:200}, {x: 330, y:199}, {x: 332, y:201}, {x: 329, y:202}] // Second polygon + * [{x: 321, y:201}, {x: 356, y:205}, {x: 341, y:250}] // Third polygon + * ] + */ + setCroppingPolygons: function( polygons ) { + + var isXYObject = function(obj) { + return obj instanceof $.Point || (typeof obj.x === 'number' && typeof obj.y === 'number'); + }; + + var objectToSimpleXYObject = function(objs) { + return objs.map(function(obj) { + try { + if (isXYObject(obj)) { + return { x: obj.x, y: obj.y }; + } else { + throw new Error(); + } + } catch(e) { + throw new Error('A Provided cropping polygon point is not supported'); + } + }); + }; + + try { + if (!$.isArray(polygons)) { + throw new Error('Provided cropping polygon is not an array'); + } + this._croppingPolygons = polygons.map(function(polygon){ + return objectToSimpleXYObject(polygon); + }); + } catch (e) { + $.console.error('[TiledImage.setCroppingPolygons] Cropping polygon format not supported'); + $.console.error(e); + this._croppingPolygons = null; + } + }, + + /** + * Resets the cropping polygons, thus next render will remove all cropping + * polygon effects. + */ + resetCroppingPolygons: function() { + this._croppingPolygons = null; + }, + /** * Positions and scales the TiledImage to fit in the specified bounds. * Note: this method fires OpenSeadragon.TiledImage.event:bounds-change @@ -1932,6 +1984,28 @@ function drawTiles( tiledImage, lastDrawn ) { usedClip = true; } + if (tiledImage._croppingPolygons) { + tiledImage._drawer.saveContext(useSketch); + try { + var polygons = tiledImage._croppingPolygons.map(function (polygon) { + return polygon.map(function (coord) { + var point = tiledImage + .imageToViewportCoordinates(coord.x, coord.y, true) + .rotate(-tiledImage.getRotation(true), tiledImage._getRotationPoint(true)); + var clipPoint = tiledImage._drawer.viewportCoordToDrawerCoord(point); + if (sketchScale) { + clipPoint = clipPoint.times(sketchScale); + } + return clipPoint; + }); + }); + tiledImage._drawer.clipWithPolygons(polygons, useSketch); + } catch (e) { + $.console.error(e); + } + usedClip = true; + } + if ( tiledImage.placeholderFillStyle && tiledImage._hasOpaqueTile === false ) { var placeholderRect = tiledImage._drawer.viewportToDrawerRectangle(tiledImage.getBounds(true)); if (sketchScale) { diff --git a/test/demo/cropping-polygons.html b/test/demo/cropping-polygons.html new file mode 100644 index 00000000..8f69d98d --- /dev/null +++ b/test/demo/cropping-polygons.html @@ -0,0 +1,165 @@ + + +
+