UNPKG

area-selection-js

Version:

Simple and easy area selection library for image/video cropping

246 lines (219 loc) 7.04 kB
/** * Box component */ export default class Box { /** * Creates a new Box instance. * @constructor * @param {Number} x1 * @param {Number} y1 * @param {Number} x2 * @param {Number} y2 */ constructor(x1, y1, x2, y2) { this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; } /** * Sets the new dimensions of the box. * @param {Number} x1 * @param {Number} y1 * @param {Number} x2 * @param {Number} y2 */ set(x1 = null, y1 = null, x2 = null, y2 = null) { this.x1 = x1 == null ? this.x1 : x1; this.y1 = y1 == null ? this.y1 : y1; this.x2 = x2 == null ? this.x2 : x2; this.y2 = y2 == null ? this.y2 : y2; return this; } /** * Calculates the width of the box. * @returns {Number} */ width() { return Math.abs(this.x2 - this.x1); } /** * Calculates the height of the box. * @returns {Number} */ height() { return Math.abs(this.y2 - this.y1); } /** * Resizes the box to a new size. * @param {Number} newWidth * @param {Number} newHeight * @param {Array} [origin] The origin point to resize from. * Defaults to [0, 0] (top left). */ resize(newWidth, newHeight, origin = [0, 0]) { const fromX = this.x1 + (this.width() * origin[0]); const fromY = this.y1 + (this.height() * origin[1]); this.x1 = fromX - (newWidth * origin[0]); this.y1 = fromY - (newHeight * origin[1]); this.x2 = this.x1 + newWidth; this.y2 = this.y1 + newHeight; return this; } /** * Scale the box by a factor. * @param {Number} factor * @param {Array} [origin] The origin point to resize from. * Defaults to [0, 0] (top left). */ scale(factor, origin = [0, 0]) { const newWidth = this.width() * factor; const newHeight = this.height() * factor; this.resize(newWidth, newHeight, origin); return this; } /** * Move the box to the specified coordinates. */ move(x = null, y = null) { let width = this.width(); let height = this.height(); x = x === null ? this.x1 : x; y = y === null ? this.y1 : y; this.x1 = x; this.y1 = y; this.x2 = x + width; this.y2 = y + height; return this; } /** * Get relative x and y coordinates of a given point within the box. * @param {Array} point The x and y ratio position within the box. * @returns {Array} The x and y coordinates [x, y]. */ getRelativePoint(point = [0, 0]) { const x = this.width() * point[0]; const y = this.height() * point[1]; return [x, y]; } /** * Get absolute x and y coordinates of a given point within the box. * @param {Array} point The x and y ratio position within the box. * @returns {Array} The x and y coordinates [x, y]. */ getAbsolutePoint(point = [0, 0]) { const x = this.x1 + this.width() * point[0]; const y = this.y1 + this.height() * point[1]; return [x, y]; } /** * Constrain the box to a fixed ratio. * @param {Number} ratio * @param {Array} [origin] The origin point to resize from. * Defaults to [0, 0] (top left). * @param {String} [grow] The axis to grow to maintain the ratio. * Defaults to 'height'. */ constrainToRatio(ratio, origin = [0, 0], grow = 'height') { if (ratio === null) { return; } const width = this.width(); const height = this.height(); switch (grow) { case 'height': // Grow height only this.resize(this.width(), this.width() / ratio, origin); break; case 'width': // Grow width only this.resize(this.height() * ratio, this.height(), origin); break; default: // Default: Grow height only this.resize(this.width(), this.width() / ratio, origin); } return this; } /** * Constrain the box within a boundary. * @param {Number} boundaryWidth * @param {Number} boundaryHeight * @param {Array} [origin] The origin point to resize from. * Defaults to [0, 0] (top left). */ constrainToBoundary(boundaryWidth, boundaryHeight, origin = [0, 0]) { // Calculate the maximum sizes for each direction of growth const [originX, originY] = this.getAbsolutePoint(origin); const maxIfLeft = originX const maxIfTop = originY const maxIfRight = boundaryWidth - originX const maxIfBottom = boundaryHeight - originY // Express the direction of growth in terms of left, both, // and right as -1, 0, and 1 respectively. Ditto for top/both/down. const directionX = -2 * origin[0] + 1; const directionY = -2 * origin[1] + 1; // Determine the max size to use according to the direction of growth. let [maxWidth, maxHeight] = [null, null]; switch (directionX) { case -1: maxWidth = maxIfLeft; break; case 0: maxWidth = Math.min(maxIfLeft, maxIfRight) * 2; break; case +1: maxWidth = maxIfRight; break; } switch (directionY) { case -1: maxHeight = maxIfTop; break; case 0: maxHeight = Math.min(maxIfTop, maxIfBottom) * 2; break; case +1: maxHeight = maxIfBottom; break; } // Resize if the box exceeds the calculated max width/height. if (this.width() > maxWidth) { const factor = maxWidth / this.width(); this.scale(factor, origin); } if (this.height() > maxHeight) { const factor = maxHeight / this.height(); this.scale(factor, origin); } return this; } /** * Constrain the box to a maximum/minimum size. * @param {Number} [maxWidth] * @param {Number} [maxHeight] * @param {Number} [minWidth] * @param {Number} [minHeight] * @param {Array} [origin] The origin point to resize from. * Defaults to [0, 0] (top left). * @param {Number} [ratio] Ratio to maintain. */ constrainToSize(maxWidth = null, maxHeight = null, minWidth = null, minHeight = null, origin = [0, 0], ratio = null) { // Calculate new max/min widths & heights that constrains to the ratio if (ratio) { if (ratio > 1) { maxWidth = maxHeight * 1 / ratio; minHeight = minHeight * ratio; } else if (ratio < 1) { maxHeight = maxWidth * ratio; minWidth = minHeight * 1 / ratio; } } if (maxWidth && this.width() > maxWidth) { const newWidth = maxWidth, newHeight = ratio === null ? this.height() : maxHeight; this.resize(newWidth, newHeight, origin); } if (maxHeight && this.height() > maxHeight) { const newWidth = ratio === null ? this.width() : maxWidth, newHeight = maxHeight; this.resize(newWidth, newHeight, origin); } if (minWidth && this.width() < minWidth) { const newWidth = minWidth, newHeight = ratio === null ? this.height() : minHeight; this.resize(newWidth, newHeight, origin); } if (minHeight && this.height() < minHeight) { const newWidth = ratio === null ? this.width() : minWidth, newHeight = minHeight; this.resize(newWidth, newHeight, origin); } return this; } }