eyes.utils
Advanced tools
Comparing version 0.0.17 to 0.0.18
20
index.js
exports.ArgumentGuard = require('./src/ArgumentGuard'); | ||
exports.BrowserUtils = require('./src/BrowserUtils'); | ||
exports.CoordinatesType = require('./src/CoordinatesType'); | ||
exports.GeneralUtils = require('./src/GeneralUtils'); | ||
exports.GeometryUtils = require('./src/GeometryUtils'); | ||
exports.ImageDeltaCompressor = require('./src/ImageDeltaCompressor'); | ||
exports.ImageUtils = require('./src/ImageUtils'); | ||
exports.GeometryUtils = require('./src/GeometryUtils'); | ||
exports.MutableImage = require('./src/MutableImage'); | ||
exports.PositionProvider = require('./src/PositionProvider'); | ||
exports.PromiseFactory = require('./src/PromiseFactory'); | ||
exports.PropertyHandler = require('./src/PropertyHandler'); | ||
exports.ReadOnlyPropertyHandler = require('./src/ReadOnlyPropertyHandler'); | ||
exports.RegionProvider = require('./src/RegionProvider'); | ||
exports.ScaleMethod = require('./src/ScaleMethod'); | ||
exports.ScaleProvider = require('./src/ScaleProvider'); | ||
exports.ScaleProviderFactory = require('./src/ScaleProviderFactory'); | ||
exports.ScaleProviderIdentityFactory = require('./src/ScaleProviderIdentityFactory'); | ||
exports.SimplePropertyHandler = require('./src/SimplePropertyHandler'); | ||
exports.StreamUtils = require('./src/StreamUtils'); | ||
exports.PromiseFactory = require('./src/PromiseFactory'); | ||
exports.BrowserUtils = require('./src/BrowserUtils'); | ||
exports.MutableImage = require('./src/MutableImage'); | ||
exports.TestResultsFormatter = require('./src/TestResultsFormatter'); | ||
exports.ImageDeltaCompressor = require('./src/ImageDeltaCompressor'); |
{ | ||
"name": "eyes.utils", | ||
"version": "0.0.17", | ||
"version": "0.0.18", | ||
"description": "General purpose Javascript utilities.", | ||
@@ -27,4 +27,12 @@ "main": "index.js", | ||
"dependencies": { | ||
"node-png": "0.4.3" | ||
"pngjs": "^3.0.0" | ||
}, | ||
"devDependencies": {}, | ||
"browser": { | ||
"fs": "browserify-fs", | ||
"zlib": "browserify-zlib" | ||
}, | ||
"engines": { | ||
"node": ">=6.9.0" | ||
} | ||
} |
@@ -17,5 +17,19 @@ /* | ||
/** | ||
* Fails if the input parameter equals the input value. | ||
* | ||
* @param {Object} param The input parameter. | ||
* @param {Object} value The input value. | ||
* @param {string} paramName The input parameter name. | ||
*/ | ||
ArgumentGuard.notEqual = function (param, value, paramName) { | ||
if (param === value) { | ||
throw new Error("IllegalArgument: " + paramName + " == " + value); | ||
} | ||
}; | ||
/** | ||
* Fails if the input parameter is null. | ||
* @param param The input parameter. | ||
* @param paramName The input parameter name. | ||
* | ||
* @param {Object} param The input parameter. | ||
* @param {string} paramName The input parameter name. | ||
*/ | ||
@@ -29,10 +43,10 @@ ArgumentGuard.notNull = function (param, paramName) { | ||
/** | ||
* Fails if the input parameter equals the input value. | ||
* @param param The input parameter. | ||
* @param value The input value. | ||
* @param paramName The input parameter name. | ||
* Fails if the input parameter is not null. | ||
* | ||
* @param {Object} param The input parameter. | ||
* @param {string} paramName The input parameter name. | ||
*/ | ||
ArgumentGuard.notEqual = function (param, value, paramName) { | ||
if (param === value) { | ||
throw new Error("IllegalArgument: " + paramName + " == " + value); | ||
ArgumentGuard.isNull = function (param, paramName) { | ||
if (null != param) { | ||
throw new Error("IllegalArgument: " + paramName + " is not null"); | ||
} | ||
@@ -43,4 +57,5 @@ }; | ||
* Fails if the input parameter string is null or empty. | ||
* @param param The input parameter. | ||
* @param paramName The input parameter name. | ||
* | ||
* @param {Object} param The input parameter. | ||
* @param {string} paramName The input parameter name. | ||
*/ | ||
@@ -55,16 +70,6 @@ ArgumentGuard.notNullOrEmpty = function (param, paramName) { | ||
/** | ||
* Fails if the input parameter is not null. | ||
* @param param The input parameter. | ||
* @param paramName The input parameter name. | ||
*/ | ||
ArgumentGuard.isNull = function (param, paramName) { | ||
if (null != param) { | ||
throw new Error("IllegalArgument: " + paramName + " is not null"); | ||
} | ||
}; | ||
/** | ||
* Fails if the input integer parameter is negative. | ||
* @param param The input parameter. | ||
* @param paramName The input parameter name. | ||
* | ||
* @param {number} param The input parameter. | ||
* @param {string} paramName The input parameter name. | ||
*/ | ||
@@ -79,4 +84,5 @@ ArgumentGuard.greaterThanOrEqualToZero = function (param, paramName) { | ||
* Fails if the input integer parameter is smaller than 1. | ||
* @param param The input parameter. | ||
* @param paramName The input parameter name. | ||
* | ||
* @param {number} param The input parameter. | ||
* @param {string} paramName The input parameter name. | ||
*/ | ||
@@ -102,4 +108,5 @@ ArgumentGuard.greaterThanZero = function (param, paramName) { | ||
* Fails if isValid is false. | ||
* @param isValid Whether the current state is valid. | ||
* @param errMsg A description of the error. | ||
* | ||
* @param {boolean} isValid Whether the current state is valid. | ||
* @param {string} errMsg A description of the error. | ||
*/ | ||
@@ -114,4 +121,5 @@ ArgumentGuard.isValidState = function (isValid, errMsg) { | ||
* Fails if isValid is false. | ||
* @param param The input parameter. | ||
* @param type The expected param type | ||
* | ||
* @param {Object} param The input parameter. | ||
* @param {Object} type The expected param type | ||
*/ | ||
@@ -118,0 +126,0 @@ ArgumentGuard.isValidType = function (param, type) { |
@@ -14,5 +14,6 @@ /* | ||
var MutableImage = require('./MutableImage'); | ||
var GeometryUtils = require('./GeometryUtils'); | ||
var ImageUtils = require('./ImageUtils'); | ||
var MutableImage = require('./MutableImage'), | ||
GeometryUtils = require('./GeometryUtils'), | ||
CoordinatesType = require('./CoordinatesType'), | ||
ImageUtils = require('./ImageUtils'); | ||
@@ -22,9 +23,73 @@ var BrowserUtils = {}; | ||
/** | ||
* @private | ||
* @type {string} | ||
*/ | ||
var JS_GET_VIEWPORT_SIZE = | ||
"var height = undefined; " + | ||
"var width = undefined; " + | ||
"if (window.innerHeight) { height = window.innerHeight; } " + | ||
"else if (document.documentElement && document.documentElement.clientHeight) { height = document.documentElement.clientHeight; } " + | ||
"else { var b = document.getElementsByTagName('body')[0]; if (b.clientHeight) {height = b.clientHeight;} }; " + | ||
"if (window.innerWidth) { width = window.innerWidth; } " + | ||
"else if (document.documentElement && document.documentElement.clientWidth) { width = document.documentElement.clientWidth; } " + | ||
"else { var b = document.getElementsByTagName('body')[0]; if (b.clientWidth) { width = b.clientWidth;} }; " + | ||
"return {width: width, height: height};"; | ||
/** | ||
* @private | ||
* @type {string} | ||
*/ | ||
var JS_GET_CURRENT_SCROLL_POSITION = | ||
"var doc = document.documentElement; " + | ||
"var x = window.scrollX || ((window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0)); " + | ||
"var y = window.scrollY || ((window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0)); " + | ||
"return [x, y];"; | ||
/** | ||
* @private | ||
* @type {string} | ||
*/ | ||
var JS_GET_CONTENT_ENTIRE_SIZE = | ||
"var scrollWidth = document.documentElement.scrollWidth; " + | ||
"var bodyScrollWidth = document.body.scrollWidth; " + | ||
"var totalWidth = Math.max(scrollWidth, bodyScrollWidth); " + | ||
"var clientHeight = document.documentElement.clientHeight; " + | ||
"var bodyClientHeight = document.body.clientHeight; " + | ||
"var scrollHeight = document.documentElement.scrollHeight; " + | ||
"var bodyScrollHeight = document.body.scrollHeight; " + | ||
"var maxDocElementHeight = Math.max(clientHeight, scrollHeight); " + | ||
"var maxBodyHeight = Math.max(bodyClientHeight, bodyScrollHeight); " + | ||
"var totalHeight = Math.max(maxDocElementHeight, maxBodyHeight); " + | ||
"return [totalWidth, totalHeight];"; | ||
/** | ||
* @return {string} | ||
*/ | ||
var JS_GET_COMPUTED_STYLE_FORMATTED_STR = function (propStyle) { | ||
return "var elem = arguments[0]; var styleProp = '" + propStyle + "'; " + | ||
"if (window.getComputedStyle) { " + | ||
"return window.getComputedStyle(elem, null).getPropertyValue(styleProp);" + | ||
"} else if (elem.currentStyle) { " + | ||
"return elem.currentStyle[styleProp];" + | ||
"} else { " + | ||
"return null;" + | ||
"}"; | ||
}; | ||
/** | ||
* @private | ||
* @type {string[]} | ||
*/ | ||
var JS_TRANSFORM_KEYS = ["transform", "-webkit-transform"]; | ||
/** | ||
* Waits a specified amount of time before resolving the returned promise. | ||
* | ||
* @param {int} ms The amount of time to sleep in milliseconds. | ||
* @return {Promise} A promise which is resolved when sleep is done. | ||
* @param {PromiseFactory} promiseFactory | ||
* @return {Promise<void>} A promise which is resolved when sleep is done. | ||
*/ | ||
BrowserUtils.sleep = function sleep(ms, promiseFactory) { | ||
return promiseFactory.makePromise(function(resolve) { | ||
setTimeout(function() { | ||
return promiseFactory.makePromise(function (resolve) { | ||
setTimeout(function () { | ||
resolve(); | ||
@@ -37,59 +102,136 @@ }, ms); | ||
* Executes a script using the browser's executeScript function - and optionally waits a timeout. | ||
* @param {Object} browser - The driver using which to execute the script. | ||
* @param {string} script - The code to execute on the given driver. | ||
* @param {Object} promiseFactory | ||
* @param {number|undefined} stabilizationTimeMs (optional) The amount of time to wait after script execution to | ||
* let the browser a chance to stabilize (e.g., finish rendering). | ||
* | ||
* @return {Promise} A promise which resolves to the result of the script's execution on the tab. | ||
* @param {WebDriver} browser The driver using which to execute the script. | ||
* @param {string} script The code to execute on the given driver. | ||
* @param {PromiseFactory} promiseFactory | ||
* @param {number|undefined} [stabilizationTimeMs] The amount of time to wait after script execution to | ||
* let the browser a chance to stabilize (e.g., finish rendering). | ||
* @return {Promise<void>} A promise which resolves to the result of the script's execution on the tab. | ||
*/ | ||
BrowserUtils.executeScript = function executeScript(browser, script, promiseFactory, stabilizationTimeMs) { | ||
return browser.executeScript(script) | ||
.then(function(result) { | ||
if (stabilizationTimeMs) { | ||
return BrowserUtils.sleep(stabilizationTimeMs, promiseFactory) | ||
.then(function() { | ||
return result; | ||
}); | ||
} | ||
return browser.executeScript(script).then(function (result) { | ||
if (stabilizationTimeMs) { | ||
return BrowserUtils.sleep(stabilizationTimeMs, promiseFactory) | ||
.then(function () { | ||
return result; | ||
}); | ||
} | ||
return result; | ||
}); | ||
return result; | ||
}); | ||
}; | ||
/** | ||
* Gets the device pixel ratio. | ||
* Returns the computed value of the style property for the current element. | ||
* | ||
* @param {WebDriver} browser The driver which will execute the script to get the ratio. | ||
* @param {Object} promiseFactory | ||
* @return {Promise} A promise which resolves to the device pixel ratio (float type). | ||
* @param {WebDriver} browser The driver which will execute the script to get computed style. | ||
* @param {WebElement} element | ||
* @param {string} propStyle The style property which value we would like to extract. | ||
* @return {Promise<string>} The value of the style property of the element, or {@code null}. | ||
*/ | ||
BrowserUtils.getDevicePixelRatio = function getDevicePixelRatio(browser, promiseFactory) { | ||
//noinspection JSUnresolvedVariable | ||
return BrowserUtils.executeScript(browser, 'return window.devicePixelRatio', promiseFactory, undefined) | ||
.then(function (results) { | ||
return parseFloat(results); | ||
}); | ||
BrowserUtils.getComputedStyle = function (browser, element, propStyle) { | ||
var scriptToExec = JS_GET_COMPUTED_STYLE_FORMATTED_STR(propStyle); | ||
return browser.executeScript(scriptToExec, element).then(function(computedStyle) { | ||
return computedStyle; | ||
}); | ||
}; | ||
/** | ||
* Gets the current scroll position. | ||
* Returns a location based on the given location. | ||
* | ||
* @param {WebDriver} browser The driver which will execute the script to get the scroll position. | ||
* @param {Object} promiseFactory | ||
* @return {Promise} A promise which resolves to the current scroll position ({top: *, left: *}). | ||
* @param {Logger} logger The logger to use. | ||
* @param {WebElement|EyesRemoteWebElement} element The element for which we want to find the content's location. | ||
* @param {{x: number, y: number}} location The location of the element. | ||
* @param {PromiseFactory} promiseFactory | ||
* @return {Promise<{x: number, y: number}>} The location of the content of the element. | ||
*/ | ||
BrowserUtils.getCurrentScrollPosition = function getCurrentScrollPosition(browser, promiseFactory) { | ||
//noinspection JSUnresolvedVariable | ||
return BrowserUtils.executeScript(browser, | ||
'var doc = document.documentElement; ' + | ||
'var x = (window.scrollX || window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0); ' + | ||
'var y = (window.scrollY || window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0); ' + | ||
'return [x,y]', promiseFactory, undefined) | ||
.then(function (results) { | ||
// If we can't find the current scroll position, we use 0 as default. | ||
var x = parseInt(results[0], 10) || 0; | ||
var y = parseInt(results[1], 10) || 0; | ||
return {left: x, top: y}; | ||
BrowserUtils.getLocationWithBordersAddition = function (logger, element, location, promiseFactory) { | ||
logger.verbose("BordersAdditionFrameLocationProvider(logger, element, [" + location.x + "," + location.y + "])"); | ||
if (element.getRemoteWebElement !== undefined) { | ||
element = element.getRemoteWebElement(); | ||
} | ||
var leftBorderWidth, topBorderWidth; | ||
return _getLeftBorderWidth(logger, promiseFactory, element).then(function (val) { | ||
leftBorderWidth = val; | ||
return _getTopBorderWidth(logger, promiseFactory, element); | ||
}).then(function (val) { | ||
topBorderWidth = val; | ||
logger.verbose("Done!"); | ||
// Frame borders also have effect on the frame's location. | ||
return GeometryUtils.locationOffset(location, {x: leftBorderWidth, y: topBorderWidth}); | ||
}); | ||
}; | ||
/** | ||
* Return width of left border | ||
* | ||
* @private | ||
* @param {Logger} logger | ||
* @param {PromiseFactory} promiseFactory | ||
* @param {WebElement} element | ||
* @return {Promise<number>} | ||
*/ | ||
function _getLeftBorderWidth(logger, promiseFactory, element) { | ||
return promiseFactory.makePromise(function (resolve) { | ||
logger.verbose("Get element border left width..."); | ||
return BrowserUtils.getComputedStyle(element.getDriver(), element, "border-left-width").then(function (styleValue) { | ||
return styleValue; | ||
}, function (err) { | ||
logger.verbose("Using getComputedStyle failed: ", err); | ||
logger.verbose("Using getCssValue..."); | ||
return element.getCssValue("border-left-width"); | ||
}).then(function (propValue) { | ||
logger.verbose("Done!"); | ||
var leftBorderWidth = Math.round(parseFloat(propValue.trim().replace("px", ""))); | ||
logger.verbose("border-left-width: ", leftBorderWidth); | ||
resolve(leftBorderWidth); | ||
}, function (err) { | ||
logger.verbose("Couldn't get the element's border-left-width: ", err, ". Falling back to default"); | ||
resolve(0); | ||
}); | ||
}); | ||
} | ||
/** | ||
* Return width of top border | ||
* | ||
* @private | ||
* @param {Logger} logger | ||
* @param {PromiseFactory} promiseFactory | ||
* @param {WebElement} element | ||
* @return {Promise<number>} | ||
*/ | ||
function _getTopBorderWidth(logger, promiseFactory, element) { | ||
return promiseFactory.makePromise(function (resolve) { | ||
logger.verbose("Get element's border top width..."); | ||
return BrowserUtils.getComputedStyle(element.getDriver(), element, "border-top-width").then(function (styleValue) { | ||
return styleValue; | ||
}, function (err) { | ||
logger.verbose("Using getComputedStyle failed: ", err); | ||
logger.verbose("Using getCssValue..."); | ||
return element.getCssValue("border-top-width"); | ||
}).then(function (propValue) { | ||
logger.verbose("Done!"); | ||
var topBorderWidth = Math.round(parseFloat(propValue.trim().replace("px", ""))); | ||
logger.verbose("border-top-width: ", topBorderWidth); | ||
resolve(topBorderWidth); | ||
}, function (err) { | ||
logger.verbose("Couldn't get the element's border-top-width: ", err, ". Falling back to default"); | ||
resolve(0); | ||
}); | ||
}); | ||
} | ||
/** | ||
* Gets the device pixel ratio. | ||
* | ||
* @param {WebDriver} browser The driver which will execute the script to get the ratio. | ||
* @param {PromiseFactory} promiseFactory | ||
* @return {Promise<number>} A promise which resolves to the device pixel ratio (float type). | ||
*/ | ||
BrowserUtils.getDevicePixelRatio = function getDevicePixelRatio(browser, promiseFactory) { | ||
return BrowserUtils.executeScript(browser, 'return window.devicePixelRatio', promiseFactory, undefined).then(function (results) { | ||
return parseFloat(results); | ||
}); | ||
}; | ||
@@ -99,27 +241,55 @@ | ||
* Get the current transform of page. | ||
* | ||
* @param {WebDriver} browser The driver which will execute the script to get the scroll position. | ||
* @param {Object} promiseFactory | ||
* @return {Promise} A promise which resolves to the current transform value. | ||
* @param {PromiseFactory} promiseFactory | ||
* @return {Promise<object.<string, string>>} A promise which resolves to the current transform value. | ||
*/ | ||
BrowserUtils.getCurrentTransform = function getCurrentTransform(browser, promiseFactory) { | ||
return BrowserUtils.executeScript(browser, "return document.body.style.transform", promiseFactory); | ||
var script = "return { "; | ||
for (var i = 0, l = JS_TRANSFORM_KEYS.length; i < l; i++) { | ||
script += "'" + JS_TRANSFORM_KEYS[i] + "': document.documentElement.style['" + JS_TRANSFORM_KEYS[i] + "'],"; | ||
} | ||
script += " }"; | ||
return BrowserUtils.executeScript(browser, script, promiseFactory, undefined); | ||
}; | ||
/** | ||
* Set the current transform of the current page. | ||
* Sets transforms for document.documentElement according to the given map of style keys and values. | ||
* | ||
* @param {WebDriver} browser The browser to use. | ||
* @param {object.<string, string>} transforms The transforms to set. Keys are used as style keys and values are the values for those styles. | ||
* @param {PromiseFactory} promiseFactory | ||
* @returns {Promise<void>} | ||
*/ | ||
BrowserUtils.setTransforms = function (browser, transforms, promiseFactory) { | ||
var script = ""; | ||
for (var key in transforms) { | ||
if (transforms.hasOwnProperty(key)) { | ||
script += "document.documentElement.style['" + key + "'] = '" + transforms[key] + "';"; | ||
} | ||
} | ||
return BrowserUtils.executeScript(browser, script, promiseFactory, 250); | ||
}; | ||
/** | ||
* Set the given transform to document.documentElement for all style keys defined in {@link JS_TRANSFORM_KEYS} | ||
* | ||
* @param {WebDriver} browser The driver which will execute the script to set the transform. | ||
* @param {string} transformToSet The transform to set. | ||
* @param {Object} promiseFactory | ||
* | ||
* @return {Promise} A promise which resolves to the previous transform once the updated transform is set. | ||
* @param {PromiseFactory} promiseFactory | ||
* @return {Promise<void>} A promise which resolves to the previous transform once the updated transform is set. | ||
*/ | ||
BrowserUtils.setTransform = function setTransform(browser, transformToSet, promiseFactory) { | ||
var transforms = {}; | ||
if (!transformToSet) { | ||
transformToSet = ''; | ||
} | ||
return BrowserUtils.executeScript(browser, | ||
"var originalTransform = document.body.style.transform; " + | ||
"document.body.style.transform = '" + transformToSet + "'; " + | ||
"originalTransform", | ||
promiseFactory, 250); | ||
for (var i = 0, l = JS_TRANSFORM_KEYS.length; i < l; i++) { | ||
transforms[JS_TRANSFORM_KEYS[i]] = transformToSet; | ||
} | ||
return BrowserUtils.setTransforms(browser, transforms, promiseFactory); | ||
}; | ||
@@ -129,9 +299,10 @@ | ||
* CSS translate the document to a given location. | ||
* | ||
* @param {WebDriver} browser The driver which will execute the script to set the transform. | ||
* @param {Object} Point left; top;. | ||
* @param {Object} promiseFactory | ||
* @return {Promise} A promise which resolves to the previous transfrom when the scroll is executed. | ||
* @param {{x: number, y: number}} point | ||
* @param {PromiseFactory} promiseFactory | ||
* @return {Promise<void>} A promise which resolves to the previous transform when the scroll is executed. | ||
*/ | ||
BrowserUtils.translateTo = function translateTo(browser, point, promiseFactory) { | ||
return BrowserUtils.setTransform(browser, 'translate(-' + point.left + 'px, -' + point.top + 'px)', promiseFactory); | ||
return BrowserUtils.setTransform(browser, 'translate(-' + point.x + 'px, -' + point.y + 'px)', promiseFactory); | ||
}; | ||
@@ -143,9 +314,9 @@ | ||
* @param {WebDriver} browser - The driver which will execute the script to set the scroll position. | ||
* @param {Object} point Point to scroll to | ||
* @param {Object} promiseFactory | ||
* @return {Promise} A promise which resolves after the action is perfromed and timeout passed. | ||
* @param {{x: number, y: number}} point Point to scroll to | ||
* @param {PromiseFactory} promiseFactory | ||
* @return {Promise<void>} A promise which resolves after the action is performed and timeout passed. | ||
*/ | ||
BrowserUtils.scrollTo = function scrollTo(browser, point, promiseFactory) { | ||
return BrowserUtils.executeScript(browser, | ||
'window.scrollTo(' + parseInt(point.left, 10) + ', ' + parseInt(point.top, 10) + ');', | ||
'window.scrollTo(' + parseInt(point.x, 10) + ', ' + parseInt(point.y, 10) + ');', | ||
promiseFactory, 250); | ||
@@ -155,7 +326,23 @@ }; | ||
/** | ||
* Gets the current scroll position. | ||
* | ||
* @param {WebDriver} browser The driver which will execute the script to get the scroll position. | ||
* @param {PromiseFactory} promiseFactory | ||
* @return {Promise<{x: number, y: number}>} A promise which resolves to the current scroll position. | ||
*/ | ||
BrowserUtils.getCurrentScrollPosition = function getCurrentScrollPosition(browser, promiseFactory) { | ||
return BrowserUtils.executeScript(browser, JS_GET_CURRENT_SCROLL_POSITION, promiseFactory, undefined).then(function (results) { | ||
// If we can't find the current scroll position, we use 0 as default. | ||
var x = parseInt(results[0], 10) || 0; | ||
var y = parseInt(results[1], 10) || 0; | ||
return {x: x, y: y}; | ||
}); | ||
}; | ||
/** | ||
* Get the entire page size. | ||
* | ||
* @param {WebDriver} browser The driver used to query the web page. | ||
* @param {Object} promiseFactory | ||
* @return {Promise} A promise which resolves to an object containing the width/height of the page. | ||
* @param {PromiseFactory} promiseFactory | ||
* @return {Promise<{width: number, height: number}>} A promise which resolves to an object containing the width/height of the page. | ||
*/ | ||
@@ -168,24 +355,7 @@ BrowserUtils.getEntirePageSize = function getEntirePageSize(browser, promiseFactory) { | ||
// maximum between them. | ||
return BrowserUtils.executeScript(browser, | ||
'return [document.documentElement.scrollWidth, document.body.scrollWidth, ' | ||
+ 'document.documentElement.clientHeight, document.body.clientHeight, ' | ||
+ 'document.documentElement.scrollHeight, document.body.scrollHeight];', | ||
promiseFactory) | ||
.then(function (results) { | ||
// Notice that each result is itself actually an array (since executeScript returns an Array). | ||
var scrollWidth = parseInt(results[0], 10); | ||
var bodyScrollWidth = parseInt(results[1], 10); | ||
var totalWidth = Math.max(scrollWidth, bodyScrollWidth); | ||
var clientHeight = parseInt(results[2], 10); | ||
var bodyClientHeight = parseInt(results[3], 10); | ||
var scrollHeight = parseInt(results[4], 10); | ||
var bodyScrollHeight = parseInt(results[5], 10); | ||
var maxDocumentElementHeight = Math.max(clientHeight, scrollHeight); | ||
var maxBodyHeight = Math.max(bodyClientHeight, bodyScrollHeight); | ||
var totalHeight = Math.max(maxDocumentElementHeight, maxBodyHeight); | ||
return {width: totalWidth, height: totalHeight}; | ||
}); | ||
return BrowserUtils.executeScript(browser, JS_GET_CONTENT_ENTIRE_SIZE, promiseFactory).then(function (results) { | ||
var totalWidth = parseInt(results[0], 10) || 0; | ||
var totalHeight = parseInt(results[1], 10) || 0; | ||
return {width: totalWidth, height: totalHeight}; | ||
}); | ||
}; | ||
@@ -198,42 +368,205 @@ | ||
* @param {string} overflowValue The values of the overflow to set. | ||
* @return {Promise|*} A promise which resolves to the original overflow of the document. | ||
* @param {PromiseFactory} promiseFactory | ||
* @return {Promise<string>} A promise which resolves to the original overflow of the document. | ||
*/ | ||
BrowserUtils.setOverflow = function setOverflow(browser, overflowValue, promiseFactory) { | ||
return BrowserUtils.executeScript(browser, | ||
'var origOF = document.documentElement.style.overflow; document.documentElement.style.overflow = "' | ||
+ overflowValue + '"; origOF', promiseFactory, 100); | ||
var script; | ||
if (overflowValue == null) { | ||
script = | ||
"var origOverflow = document.documentElement.style.overflow; " + | ||
"document.documentElement.style.overflow = undefined; " + | ||
"return origOverflow"; | ||
} else { | ||
script = | ||
"var origOverflow = document.documentElement.style.overflow; " + | ||
"document.documentElement.style.overflow = \"" + overflowValue + "\"; " + | ||
"return origOverflow"; | ||
} | ||
return BrowserUtils.executeScript(browser, script, promiseFactory, 100); | ||
}; | ||
/** | ||
* Queries the current page's size and pixel ratio to figure out what is the normalization factor of a screenshot | ||
* image. Even if there's a pixel ration > 1, it doesn't necessarily mean that the image requires rescaling | ||
* (e.g., when the screenshot is a full page screenshot). | ||
* Hides the scrollbars of the current context's document element. | ||
* | ||
* @param {WebDriver} browser The driver used to update the web page. | ||
* @param {Object} imageSize The width and height. | ||
* @param {Object} viewportSize | ||
* @param {Object} promiseFactory | ||
* @return {Promise} A promise which resolves to the normalization factor (float). | ||
* @param {WebDriver} browser The browser to use for hiding the scrollbars. | ||
* @param {PromiseFactory} promiseFactory | ||
* @return {Promise<string>} The previous value of the overflow property (could be {@code null}). | ||
*/ | ||
BrowserUtils.findImageNormalizationFactor = function findImageNormalizationFactor(browser, imageSize, viewportSize, promiseFactory) { | ||
return BrowserUtils.getEntirePageSize(browser, promiseFactory) | ||
.then(function (entirePageSize) { | ||
return BrowserUtils.getDevicePixelRatio(browser, promiseFactory) | ||
.then(function (ratio) { | ||
return _calcImageNormalizationFactor(imageSize, viewportSize, entirePageSize, ratio); | ||
BrowserUtils.hideScrollbars = function (browser, promiseFactory) { | ||
return BrowserUtils.setOverflow(browser, "hidden", promiseFactory); | ||
}; | ||
/** | ||
* Tries to get the viewport size using Javascript. If fails, gets the entire browser window size! | ||
* | ||
* @param {WebDriver} browser The browser to use. | ||
* @param {PromiseFactory} promiseFactory | ||
* @return {Promise<{width: number, height: number}>} The viewport size. | ||
*/ | ||
BrowserUtils.getViewportSize = function (browser, promiseFactory) { | ||
return promiseFactory.makePromise(function (resolve) { | ||
try { | ||
return BrowserUtils.executeScript(browser, JS_GET_VIEWPORT_SIZE, promiseFactory, undefined).then(function (size) { | ||
resolve(size); | ||
}, function () { | ||
browser.manage().window().getSize().then(function (size) { | ||
resolve(size); | ||
}); | ||
}); | ||
}); | ||
} catch (err) { | ||
browser.manage().window().getSize().then(function (size) { | ||
resolve(size); | ||
}); | ||
} | ||
}.bind(this)); | ||
}; | ||
var _calcImageNormalizationFactor = function (imageSize, viewportSize, entirePageSize, pixelRatio) { | ||
if (imageSize.width === viewportSize.width || imageSize.width === entirePageSize.width) { | ||
return 1; | ||
} | ||
/** | ||
* Tries to set the viewport | ||
* | ||
* @param {WebDriver} browser The browser to use. | ||
* @param {{width: number, height: number}} size The viewport size. | ||
* @param {PromiseFactory} promiseFactory | ||
* @param {Logger} logger | ||
* @param {boolean|undefined} lastRetry | ||
* @returns {Promise<void>} | ||
*/ | ||
BrowserUtils.setViewportSize = function (browser, size, promiseFactory, logger, lastRetry) { | ||
// First we will set the window size to the required size. | ||
// Then we'll check the viewport size and increase the window size accordingly. | ||
return promiseFactory.makePromise(function (resolve, reject) { | ||
try { | ||
BrowserUtils.getViewportSize(browser, promiseFactory).then(function (actualViewportSize) { | ||
if (actualViewportSize.width === size.width && actualViewportSize.height === size.height) { | ||
resolve(); | ||
return; | ||
} | ||
return 1 / pixelRatio; | ||
browser.manage().window().getSize().then(function (browserSize) { | ||
// Edge case. | ||
if (browserSize.height < actualViewportSize.height || browserSize.width < actualViewportSize.width) { | ||
logger.log("Browser window size is smaller than the viewport! Using current viewport size as is."); | ||
resolve(); | ||
return; | ||
} | ||
var requiredBrowserSize = { | ||
height: browserSize.height + (size.height - actualViewportSize.height), | ||
width: browserSize.width + (size.width - actualViewportSize.width) | ||
}; | ||
logger.log("Trying to set browser size to: " + requiredBrowserSize.width + "x" + requiredBrowserSize.height); | ||
_setWindowSize(browser, requiredBrowserSize, 3, promiseFactory, logger).then(function () { | ||
BrowserUtils.getViewportSize(browser, promiseFactory).then(function (updatedViewportSize) { | ||
if (updatedViewportSize.width === size.width && | ||
updatedViewportSize.height === size.height) { | ||
resolve(); | ||
return; | ||
} | ||
if (lastRetry) { | ||
reject(new Error("Failed to set viewport size! " + | ||
"(Got " + updatedViewportSize.width + "x" + updatedViewportSize.height + ") " + | ||
"Please try using a smaller viewport size.")); | ||
} else { | ||
BrowserUtils.setViewportSize(browser, size, promiseFactory, logger, true).then(function () { | ||
resolve(); | ||
}, function (err) { | ||
reject(err); | ||
}); | ||
} | ||
}); | ||
}, function () { | ||
reject(new Error("Failed to set browser size! Please try using a smaller viewport size.")); | ||
}); | ||
}); | ||
}); | ||
} catch (err) { | ||
reject(new Error(err)); | ||
} | ||
}.bind(this)); | ||
}; | ||
var _processPart = function (part, parts, imageObj, browser, promise, promiseFactory, | ||
useCssTransition, viewportSize, entirePageSize, pixelRatio, rotationDegrees, | ||
automaticRotation, automaticRotationDegrees, isLandscape, waitBeforeScreenshots) { | ||
/** | ||
* @private | ||
* @param {WebDriver} driver | ||
* @param {{width: number, height: number}} size | ||
* @param {number} retries | ||
* @param {PromiseFactory} promiseFactory | ||
* @param {Logger} logger | ||
* @return {Promise<void>} | ||
*/ | ||
function _setWindowSize(driver, size, retries, promiseFactory, logger) { | ||
return promiseFactory.makePromise(function (resolve, reject) { | ||
return driver.manage().window().setSize(size.width, size.height).then(function () { | ||
return BrowserUtils.sleep(1000, promiseFactory); | ||
}).then(function () { | ||
return driver.manage().window().getSize(); | ||
}).then(function (browserSize) { | ||
logger.log("Current browser size: " + browserSize.width + "x" + browserSize.height); | ||
if (browserSize.width === size.width && browserSize.height === size.height) { | ||
resolve(); | ||
return; | ||
} | ||
if (retries === 0) { | ||
reject(); | ||
return; | ||
} | ||
_setWindowSize(driver, size, retries - 1, promiseFactory, logger).then(function () { | ||
resolve(); | ||
}, function () { | ||
reject(); | ||
}); | ||
}); | ||
}); | ||
} | ||
/** | ||
* @private | ||
* @param {{top: number, left: number, width: number, height: number}} part | ||
* @param {Array<{position: {x: number, y: number}, size: {width: number, height: number}, image: Buffer}>} parts | ||
* @param {{imageBuffer: Buffer, width: number, height: number}} imageObj | ||
* @param {WebDriver} browser | ||
* @param {Promise<void>} promise | ||
* @param {PromiseFactory} promiseFactory | ||
* @param {{width: number, height: number}} viewportSize | ||
* @param {PositionProvider} positionProvider | ||
* @param {ScaleProviderFactory} scaleProviderFactory | ||
* @param {{width: number, height: number}} entirePageSize | ||
* @param {number} pixelRatio | ||
* @param {number} rotationDegrees | ||
* @param {boolean} automaticRotation | ||
* @param {number} automaticRotationDegrees | ||
* @param {boolean} isLandscape | ||
* @param {int} waitBeforeScreenshots | ||
* @param {{left: number, top: number, width: number, height: number}} regionInScreenshot | ||
* @param {boolean} [saveDebugScreenshots=false] | ||
* @param {string} [tag=screenshot] | ||
* @return {Promise<void>} | ||
*/ | ||
var _processPart = function ( | ||
part, | ||
parts, | ||
imageObj, | ||
browser, | ||
promise, | ||
promiseFactory, | ||
viewportSize, | ||
positionProvider, | ||
scaleProviderFactory, | ||
entirePageSize, | ||
pixelRatio, | ||
rotationDegrees, | ||
automaticRotation, | ||
automaticRotationDegrees, | ||
isLandscape, | ||
waitBeforeScreenshots, | ||
regionInScreenshot, | ||
saveDebugScreenshots, | ||
tag | ||
) { | ||
return promise.then(function () { | ||
@@ -246,3 +579,3 @@ return promiseFactory.makePromise(function (resolve) { | ||
size: {width: imageObj.width, height: imageObj.height}, | ||
position: {left: 0, top: 0} | ||
position: {x: 0, y: 0} | ||
}); | ||
@@ -255,19 +588,12 @@ | ||
var currentPosition; | ||
var partCoords = {left: part.left, top: part.top}; | ||
var promise = useCssTransition ? | ||
BrowserUtils.translateTo(browser, partCoords, promiseFactory).then(function () { | ||
currentPosition = partCoords; | ||
}) : | ||
BrowserUtils.scrollTo(browser, partCoords, promiseFactory).then(function () { | ||
return BrowserUtils.getCurrentScrollPosition(browser, promiseFactory).then(function (position) { | ||
currentPosition = {left: position.left, top: position.top}; | ||
}); | ||
var partCoords = {x: part.left, y: part.top}; | ||
return positionProvider.setPosition(partCoords).then(function () { | ||
return positionProvider.getCurrentPosition().then(function (position) { | ||
currentPosition = position; | ||
}); | ||
return promise.then(function () { | ||
return _captureViewport(browser, promiseFactory, viewportSize, entirePageSize, | ||
}).then(function () { | ||
return _captureViewport(browser, promiseFactory, viewportSize, scaleProviderFactory, entirePageSize, | ||
pixelRatio, rotationDegrees, automaticRotation, automaticRotationDegrees, isLandscape, | ||
waitBeforeScreenshots); | ||
}) | ||
.then(function (partImage) { | ||
waitBeforeScreenshots, regionInScreenshot, saveDebugScreenshots, tag); | ||
}).then(function (partImage) { | ||
return partImage.asObject().then(function (partObj) { | ||
@@ -277,3 +603,3 @@ parts.push({ | ||
size: {width: partObj.width, height: partObj.height}, | ||
position: {left: currentPosition.left, top: currentPosition.top} | ||
position: {x: currentPosition.x, y: currentPosition.y} | ||
}); | ||
@@ -288,75 +614,139 @@ | ||
var _captureViewport = function _captureViewport(browser, | ||
promiseFactory, | ||
viewportSize, | ||
entirePageSize, | ||
pixelRatio, | ||
rotationDegrees, | ||
automaticRotation, | ||
automaticRotationDegrees, | ||
isLandscape, | ||
waitBeforeScreenshots) { | ||
var parsedImage; | ||
return BrowserUtils.sleep(waitBeforeScreenshots, promiseFactory).then(function() { return browser.takeScreenshot().then(function(screenshot64) { return new MutableImage(new Buffer(screenshot64, 'base64'), promiseFactory); | ||
}) | ||
.then(function(screenshot) { | ||
parsedImage = screenshot; | ||
return parsedImage.getSize(); | ||
}) | ||
.then(function(size) { | ||
var sizeFactor; | ||
if (isLandscape && automaticRotation && size.height > size.width) { | ||
rotationDegrees = automaticRotationDegrees; | ||
} | ||
sizeFactor = _calcImageNormalizationFactor(size, viewportSize, entirePageSize, pixelRatio); | ||
if (sizeFactor === 0.5) { | ||
return parsedImage.scaleImage(sizeFactor); | ||
} | ||
return parsedImage; | ||
}) | ||
.then(function(parsedImage) { | ||
if (rotationDegrees !== 0) { | ||
return parsedImage.rotateImage(rotationDegrees); | ||
} | ||
}) | ||
.then(function () { | ||
return parsedImage.getSize(); | ||
}) | ||
.then(function (imageSize) { | ||
// If the image is a viewport screenshot, we want to save the current scroll position (we'll need it | ||
// for check region). | ||
var isViewportScreenshot = imageSize.width <= viewportSize.width | ||
&& imageSize.height <= viewportSize.height; | ||
if (isViewportScreenshot) { | ||
return BrowserUtils.getCurrentScrollPosition(browser).then(function (scrollPosition) { | ||
return parsedImage.setCoordinates(scrollPosition); | ||
}, function () { | ||
// Failed to get Scroll position, setting coordinates to default. | ||
return parsedImage.setCoordinates({left: 0, top: 0}); | ||
}); | ||
} | ||
}) | ||
.then(function () { | ||
return parsedImage; | ||
/** | ||
* @private | ||
* @param {WebDriver} browser | ||
* @param {PromiseFactory} promiseFactory | ||
* @param {{width: number, height: number}} viewportSize | ||
* @param {ScaleProviderFactory} scaleProviderFactory | ||
* @param {{width: number, height: number}} entirePageSize | ||
* @param {number} pixelRatio | ||
* @param {number} rotationDegrees | ||
* @param {boolean} automaticRotation | ||
* @param {number} automaticRotationDegrees | ||
* @param {boolean} isLandscape | ||
* @param {int} waitBeforeScreenshots | ||
* @param {{left: number, top: number, width: number, height: number}} [regionInScreenshot] | ||
* @param {boolean} [saveDebugScreenshots=false] | ||
* @param {string} [tag=screenshot] | ||
* @return {Promise<MutableImage>} | ||
*/ | ||
var _captureViewport = function ( | ||
browser, | ||
promiseFactory, | ||
viewportSize, | ||
scaleProviderFactory, | ||
entirePageSize, | ||
pixelRatio, | ||
rotationDegrees, | ||
automaticRotation, | ||
automaticRotationDegrees, | ||
isLandscape, | ||
waitBeforeScreenshots, | ||
regionInScreenshot, | ||
saveDebugScreenshots, | ||
tag | ||
) { | ||
var parsedImage, imageSize, scaleProvider; | ||
return BrowserUtils.sleep(waitBeforeScreenshots, promiseFactory).then(function () { | ||
return browser.takeScreenshot().then(function (screenshot64) { | ||
return new MutableImage(new Buffer(screenshot64, 'base64'), promiseFactory); | ||
}) | ||
.then(function (image) { | ||
parsedImage = image; | ||
if (saveDebugScreenshots) { | ||
return parsedImage.saveImage((tag || "screenshot") + "-" + new Date().getTime() + ".png"); | ||
} | ||
}) | ||
.then(function () { | ||
return parsedImage.getSize(); | ||
}) | ||
.then(function (imgSize) { | ||
imageSize = imgSize; | ||
scaleProvider = scaleProviderFactory.getScaleProvider(imageSize.width); | ||
if (regionInScreenshot) { | ||
var scaledRegion = GeometryUtils.scaleRegion(regionInScreenshot, 1 / scaleProvider.getScaleRatio()); | ||
return parsedImage.cropImage(scaledRegion); | ||
} | ||
}) | ||
.then(function () { | ||
if (isLandscape && automaticRotation && imageSize.height > imageSize.width) { | ||
rotationDegrees = automaticRotationDegrees; | ||
} | ||
if (scaleProvider && scaleProvider.getScaleRatio() !== 1) { | ||
return parsedImage.scaleImage(scaleProvider); | ||
} | ||
}) | ||
.then(function () { | ||
if (rotationDegrees !== 0) { | ||
return parsedImage.rotateImage(rotationDegrees); | ||
} | ||
}) | ||
.then(function () { | ||
return parsedImage.getSize(); | ||
}) | ||
.then(function (imgSize) { | ||
// If the image is a viewport screenshot, we want to save the current scroll position (we'll need it for check region). | ||
if (imgSize.width <= viewportSize.width && imgSize.height <= viewportSize.height) { | ||
return BrowserUtils.getCurrentScrollPosition(browser).then(function (scrollPosition) { | ||
return parsedImage.setCoordinates(scrollPosition); | ||
}, function () { | ||
// Failed to get Scroll position, setting coordinates to default. | ||
return parsedImage.setCoordinates({x: 0, y: 0}); | ||
}); | ||
} | ||
}) | ||
.then(function () { | ||
return parsedImage; | ||
}); | ||
}); | ||
}); | ||
}; | ||
BrowserUtils.getScreenshot = function getScreenshot(browser, | ||
promiseFactory, | ||
viewportSize, | ||
fullpage, | ||
hideScrollbars, | ||
useCssTransition, | ||
rotationDegrees, | ||
automaticRotation, | ||
automaticRotationDegrees, | ||
isLandscape, | ||
waitBeforeScreenshots) { | ||
var MIN_SCREENSHOT_PART_HEIGHT = 10; | ||
var maxScrollbarSize = useCssTransition ? 0 : 50; // This should cover all scroll bars (and some fixed position footer elements :). | ||
var originalScrollPosition, | ||
/** | ||
* Capture screenshot from given driver | ||
* | ||
* @param {WebDriver} browser | ||
* @param {PromiseFactory} promiseFactory | ||
* @param {{width: number, height: number}} viewportSize | ||
* @param {PositionProvider} positionProvider | ||
* @param {ScaleProviderFactory} scaleProviderFactory | ||
* @param {boolean} fullPage | ||
* @param {boolean} hideScrollbars | ||
* @param {boolean} useCssTransition | ||
* @param {number} rotationDegrees | ||
* @param {boolean} automaticRotation | ||
* @param {number} automaticRotationDegrees | ||
* @param {boolean} isLandscape | ||
* @param {int} waitBeforeScreenshots | ||
* @param {boolean} checkFrameOrElement | ||
* @param {RegionProvider} [regionProvider] | ||
* @param {boolean} [saveDebugScreenshots=false] | ||
* @param {string} [tag=screenshot] | ||
* @returns {Promise<MutableImage>} | ||
*/ | ||
BrowserUtils.getScreenshot = function getScreenshot( | ||
browser, | ||
promiseFactory, | ||
viewportSize, | ||
positionProvider, | ||
scaleProviderFactory, | ||
fullPage, | ||
hideScrollbars, | ||
useCssTransition, | ||
rotationDegrees, | ||
automaticRotation, | ||
automaticRotationDegrees, | ||
isLandscape, | ||
waitBeforeScreenshots, | ||
checkFrameOrElement, | ||
regionProvider, | ||
saveDebugScreenshots, | ||
tag | ||
) { | ||
var MIN_SCREENSHOT_PART_HEIGHT = 10, | ||
MAX_SCROLLBAR_SIZE = 50; | ||
var originalPosition, | ||
originalOverflow, | ||
originalTransform, | ||
entirePageSize, | ||
regionInScreenshot, | ||
pixelRatio, | ||
@@ -366,4 +756,6 @@ imageObject, | ||
hideScrollbars = hideScrollbars === null ? useCssTransition : hideScrollbars; | ||
// step #1 - get entire page size for future use (scaling and stitching) | ||
return BrowserUtils.getEntirePageSize(browser, promiseFactory).then(function(pageSize) { | ||
return positionProvider.getEntireSize().then(function (pageSize) { | ||
entirePageSize = pageSize; | ||
@@ -374,58 +766,60 @@ }, function () { | ||
}) | ||
.then(function() { | ||
// step #2 - get the device pixel ratio (scaling) | ||
return BrowserUtils.getDevicePixelRatio(browser, promiseFactory) | ||
.then(function (ratio) { | ||
pixelRatio = ratio; | ||
}, function (err) { | ||
// Couldn't get pixel ratio, using 1 as default. | ||
pixelRatio = 1; | ||
}); | ||
}) | ||
.then(function() { | ||
// step #3 - hide the scrollbars if instructed | ||
if (hideScrollbars) { | ||
return BrowserUtils.setOverflow(browser, "hidden", promiseFactory).then(function(originalVal) { | ||
originalOverflow = originalVal; | ||
}); | ||
} | ||
}) | ||
.then(function() { | ||
// step #4 - if this is a full page screenshot we need to scroll to position 0,0 before taking the first | ||
if(fullpage) { | ||
return BrowserUtils.getCurrentScrollPosition(browser, promiseFactory).then(function (point) { | ||
originalScrollPosition = point; | ||
return BrowserUtils.scrollTo(browser, {left: 0, top: 0}, promiseFactory).then(function () { | ||
return BrowserUtils.getCurrentScrollPosition(browser, promiseFactory).then(function (point) { | ||
if (point.left != 0 || point.top != 0) { | ||
throw new Error("Could not scroll to the top/left corner of the screen"); | ||
} | ||
.then(function () { | ||
// step #2 - get the device pixel ratio (scaling) | ||
return BrowserUtils.getDevicePixelRatio(browser, promiseFactory) | ||
.then(function (ratio) { | ||
pixelRatio = ratio; | ||
}, function () { | ||
// Couldn't get pixel ratio, using 1 as default. | ||
pixelRatio = 1; | ||
}); | ||
}) | ||
.then(function () { | ||
// step #3 - hide the scrollbars if instructed | ||
if (hideScrollbars) { | ||
return BrowserUtils.setOverflow(browser, "hidden", promiseFactory).then(function (originalVal) { | ||
originalOverflow = originalVal; | ||
}); | ||
} | ||
}) | ||
.then(function () { | ||
// step #4 - if this is a full page screenshot we need to scroll to position 0,0 before taking the first | ||
if (fullPage) { | ||
return positionProvider.getState().then(function (state) { | ||
originalPosition = state; | ||
return positionProvider.setPosition({x: 0, y: 0}).then(function () { | ||
return positionProvider.getCurrentPosition().then(function (location) { | ||
if (location.x != 0 || location.y != 0) { | ||
throw new Error("Could not scroll to the x/y corner of the screen"); | ||
} | ||
}); | ||
}); | ||
}); | ||
}) | ||
.then(function () { | ||
if (useCssTransition) { | ||
return BrowserUtils.getCurrentTransform(browser, promiseFactory).then(function (transform) { | ||
originalTransform = transform; | ||
// Translating to "top/left" of the page (notice this is different from Javascript scrolling). | ||
return BrowserUtils.translateTo(browser, {left: 0, top: 0}, promiseFactory); | ||
} | ||
}) | ||
.then(function () { | ||
if (regionProvider) { | ||
return _captureViewport(browser, promiseFactory, viewportSize, scaleProviderFactory, entirePageSize, pixelRatio, | ||
rotationDegrees, automaticRotation, automaticRotationDegrees, isLandscape, waitBeforeScreenshots).then(function (image) { | ||
return regionProvider.getRegionInLocation(image, CoordinatesType.SCREENSHOT_AS_IS, promiseFactory); | ||
}).then(function (region) { | ||
regionInScreenshot = region; | ||
}); | ||
} | ||
}) | ||
} | ||
}) | ||
.then(function() { | ||
// step #5 - Take screenshot of the 0,0 tile / current viewport | ||
return _captureViewport(browser, promiseFactory, viewportSize, entirePageSize, pixelRatio, rotationDegrees, | ||
automaticRotation, automaticRotationDegrees, isLandscape, waitBeforeScreenshots) | ||
.then(function(image) { | ||
screenshot = image; | ||
return screenshot.asObject().then(function(imageObj) { | ||
imageObject = imageObj; | ||
} | ||
}) | ||
.then(function () { | ||
// step #5 - Take screenshot of the 0,0 tile / current viewport | ||
return _captureViewport(browser, promiseFactory, viewportSize, scaleProviderFactory, entirePageSize, pixelRatio, rotationDegrees, | ||
automaticRotation, automaticRotationDegrees, isLandscape, waitBeforeScreenshots, | ||
checkFrameOrElement ? regionInScreenshot : null, saveDebugScreenshots, tag) | ||
.then(function (image) { | ||
screenshot = image; | ||
return screenshot.asObject().then(function (imageObj) { | ||
imageObject = imageObj; | ||
}); | ||
}); | ||
}); | ||
}) | ||
.then(function() { | ||
.then(function () { | ||
return promiseFactory.makePromise(function (resolve) { | ||
if (!fullpage) { | ||
if (!fullPage && !checkFrameOrElement) { | ||
resolve(); | ||
@@ -445,3 +839,3 @@ return; | ||
width: imageObject.width, | ||
height: Math.max(imageObject.height - maxScrollbarSize, MIN_SCREENSHOT_PART_HEIGHT) | ||
height: Math.max(imageObject.height - MAX_SCROLLBAR_SIZE, MIN_SCREENSHOT_PART_HEIGHT) | ||
}; | ||
@@ -452,11 +846,13 @@ | ||
height: entirePageSize.height | ||
}, screenshotPartSize); | ||
}, screenshotPartSize, false); | ||
var parts = []; | ||
var promise = promiseFactory.makePromise(function (resolve) { resolve();}); | ||
var promise = promiseFactory.makePromise(function (resolve) { | ||
resolve(); | ||
}); | ||
screenshotParts.forEach(function (part) { | ||
promise = _processPart(part, parts, imageObject, browser, promise, | ||
promiseFactory, useCssTransition, viewportSize, entirePageSize, pixelRatio, rotationDegrees, | ||
automaticRotation, automaticRotationDegrees, isLandscape, waitBeforeScreenshots); | ||
promise = _processPart(part, parts, imageObject, browser, promise, promiseFactory, | ||
viewportSize, positionProvider, scaleProviderFactory, entirePageSize, pixelRatio, rotationDegrees, automaticRotation, | ||
automaticRotationDegrees, isLandscape, waitBeforeScreenshots, checkFrameOrElement ? regionInScreenshot : null, saveDebugScreenshots, tag); | ||
}); | ||
@@ -471,3 +867,3 @@ promise.then(function () { | ||
}) | ||
.then(function() { | ||
.then(function () { | ||
if (hideScrollbars) { | ||
@@ -478,13 +874,12 @@ return BrowserUtils.setOverflow(browser, originalOverflow, promiseFactory); | ||
.then(function () { | ||
if (fullpage) { | ||
if (useCssTransition) { | ||
return BrowserUtils.setTransform(browser, originalTransform, promiseFactory).then(function () { | ||
return BrowserUtils.scrollTo(browser, originalScrollPosition, promiseFactory); | ||
}); | ||
} else { | ||
return BrowserUtils.scrollTo(browser, originalScrollPosition, promiseFactory); | ||
} | ||
if (fullPage) { | ||
return positionProvider.restoreState(originalPosition); | ||
} | ||
}) | ||
.then(function() { | ||
.then(function () { | ||
if (!checkFrameOrElement && regionInScreenshot) { | ||
return screenshot.cropImage(regionInScreenshot); | ||
} | ||
}) | ||
.then(function () { | ||
return screenshot; | ||
@@ -491,0 +886,0 @@ }); |
@@ -14,4 +14,13 @@ /* | ||
var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], | ||
days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; | ||
var GeneralUtils = {}; | ||
/** | ||
* @private | ||
* @param {Object} to | ||
* @param {Object} from | ||
* @param {string} fnName | ||
*/ | ||
function _mixin(to, from, fnName) { | ||
@@ -23,7 +32,6 @@ to[fnName] = function () { | ||
//noinspection JSUnusedGlobalSymbols | ||
/** | ||
* Concatenate the url to the suffix - making sure there are no double slashes | ||
* | ||
* concatenate the url to the suffix - making sure there are no double slashes | ||
* | ||
* @method urlConcat | ||
* @param {String} url - The left side of the URL. | ||
@@ -33,3 +41,2 @@ * @param {String} suffix - the right side. | ||
* @return {String} the URL | ||
* | ||
**/ | ||
@@ -49,2 +56,9 @@ GeneralUtils.urlConcat = function (url, suffix) { | ||
//noinspection JSUnusedGlobalSymbols | ||
/** | ||
* Convert object into json string | ||
* | ||
* @param {Object} o | ||
* @returns {String} | ||
*/ | ||
GeneralUtils.toJson = function (o) { | ||
@@ -54,3 +68,10 @@ return JSON.stringify(o); | ||
// follow the prototype chain and apply form root to current - but skip the top (Object) | ||
//noinspection JSUnusedGlobalSymbols | ||
/** | ||
* Mixin methods from one object into another. | ||
* Follow the prototype chain and apply form root to current - but skip the top (Object) | ||
* | ||
* @param {Object} to The object to which methods will be added | ||
* @param {Object} from The object from which methods will be copied | ||
*/ | ||
GeneralUtils.mixin = function (to, from) { | ||
@@ -74,2 +95,8 @@ var index, protos = [], proto = from; | ||
//noinspection JSUnusedGlobalSymbols | ||
/** | ||
* Generate GUID | ||
* | ||
* @return {string} | ||
*/ | ||
GeneralUtils.guid = function () { | ||
@@ -83,2 +110,43 @@ return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { | ||
//noinspection JSUnusedGlobalSymbols | ||
/** | ||
* Clone object | ||
* | ||
* @param {Date|Array|Object} obj | ||
* @return {*} | ||
*/ | ||
GeneralUtils.clone = function (obj) { | ||
var copy; | ||
if (null == obj || "object" != typeof obj) { | ||
return obj; | ||
} | ||
if (obj instanceof Date) { | ||
copy = new Date(); | ||
copy.setTime(obj.getTime()); | ||
return copy; | ||
} | ||
if (obj instanceof Array) { | ||
copy = []; | ||
for (var i = 0, len = obj.length; i < len; i++) { | ||
copy[i] = GeneralUtils.clone(obj[i]); | ||
} | ||
return copy; | ||
} | ||
if (obj instanceof Object) { | ||
copy = obj.constructor(); | ||
for (var attr in obj) { | ||
if (obj.hasOwnProperty(attr)) { | ||
copy[attr] = GeneralUtils.clone(obj[attr]); | ||
} | ||
} | ||
return copy; | ||
} | ||
throw new Error("Unable to copy obj! Its type isn't supported."); | ||
}; | ||
//noinspection JSUnusedGlobalSymbols | ||
/** | ||
@@ -99,4 +167,5 @@ * Creates a property with default configuration (writable, enumerable, configurable). | ||
}); | ||
} | ||
}; | ||
//noinspection JSUnusedGlobalSymbols | ||
/** | ||
@@ -110,9 +179,49 @@ * Creates a property with default configuration (writable, enumerable, configurable) and default getter/setter. | ||
var getFunc = function () { return this["_" + name]; } | ||
var setFunc = function (v) { this["_" + name] = v; } | ||
var getFunc = function () { return this["_" + name]; }; | ||
var setFunc = function (v) { this["_" + name] = v; }; | ||
GeneralUtils.definePropertyWithDefaultConfig(obj, name, getFunc, setFunc); | ||
} | ||
}; | ||
//noinspection JSUnusedGlobalSymbols | ||
/** | ||
* Convert a Date object to a RFC-1123 date string | ||
* | ||
* @param {Date} date Date which will be converted | ||
* @return {string} String formatted as RFC-1123 (ddd, dd MMM yyyy HH:mm:ss GMT) | ||
*/ | ||
GeneralUtils.getRfc1123Date = function (date) { | ||
if (date.toUTCString) { | ||
return date.toUTCString() | ||
} else { | ||
return days[date.getUTCDay()] + ", " | ||
+ _numpad(date.getUTCDate(), 2) + " " | ||
+ months[date.getUTCMonth()] + " " | ||
+ date.getUTCFullYear() + " " | ||
+ _numpad(date.getUTCHours(), 2) + ":" | ||
+ _numpad(date.getUTCMinutes(), 2) + ":" | ||
+ _numpad(date.getUTCSeconds(), 2) + " " | ||
+ "GMT"; | ||
} | ||
}; | ||
function _numpad(x, digits) { | ||
var result = x.toString(); | ||
while (result.length < digits) { | ||
result = '0' + result; | ||
} | ||
return result; | ||
} | ||
function _getTZOString(timezoneOffset){ | ||
var prefix = timezoneOffset > 0 ? '-' : '+'; | ||
var offsetHours = Math.abs(Math.floor(timezoneOffset/60)); | ||
var offsetMinutes = Math.abs(timezoneOffset%60); | ||
return prefix + _numpad(offsetHours, 2) + _numpad(offsetMinutes, 2); | ||
} | ||
module.exports = GeneralUtils; | ||
}()); |
@@ -14,66 +14,271 @@ /* | ||
var ArgumentGuard = require('./ArgumentGuard'); | ||
var GeometryUtils = {}; | ||
GeometryUtils.intersect = function (rect1, rect2) { | ||
var top = Math.max(rect1.top, rect2.top); | ||
var left = Math.max(rect1.left, rect2.left); | ||
var bottom = Math.min(rect1.top + rect1.height, rect2.top + rect2.height); | ||
var right = Math.min(rect1.left + rect1.width, rect2.left + rect2.width); | ||
var height = bottom - top; | ||
var width = right - left; | ||
if (height > 0 && width > 0) { | ||
return { | ||
top: top, | ||
left: left, | ||
width: width, | ||
height: height | ||
}; | ||
} | ||
/** | ||
* Crate new simple region object from values | ||
* | ||
* @param {number} top | ||
* @param {number} left | ||
* @param {number} width | ||
* @param {number} height | ||
* @returns {{top: number, left: number, width: number, height: number}} New region object | ||
*/ | ||
GeometryUtils.createRegion = function(top, left, width, height) { | ||
return { | ||
top: Math.ceil(top) || 0, | ||
left: Math.ceil(left) || 0, | ||
width: Math.ceil(width) || 0, | ||
height: Math.ceil(height) || 0 | ||
}; | ||
}; | ||
/** | ||
* Crate new simple region object from location and size objects | ||
* | ||
* @param {{x: number, y: number}} location | ||
* @param {{width: number, height: number}} size | ||
* @returns {{top: number, left: number, width: number, height: number}} New region object | ||
*/ | ||
GeometryUtils.createRegionFromLocationAndSize = function(location, size) { | ||
return GeometryUtils.createRegion(location.y, location.x, size.width, size.height); | ||
}; | ||
/** | ||
* Crate new simple region object from location and size objects | ||
* | ||
* @param {{top: number, left: number, width: number, height: number}} region | ||
* @return {boolean} true if the region is empty, false otherwise. | ||
*/ | ||
GeometryUtils.isRegionEmpty = function(region) { | ||
return region.left == 0 && region.top == 0 && region.width == 0 && region.height == 0; | ||
}; | ||
/** | ||
* Crete new simple location object from values | ||
* | ||
* @param {number} left | ||
* @param {number} top | ||
* @returns {{x: number, y: number}} New location object | ||
*/ | ||
GeometryUtils.createLocation = function(left, top) { | ||
return { | ||
top: 0, | ||
left: 0, | ||
width: 0, | ||
height: 0 | ||
x: Math.ceil(left) || 0, | ||
y: Math.ceil(top) || 0 | ||
}; | ||
}; | ||
GeometryUtils.contains = function (rect, point) { | ||
return (rect.left <= point.x | ||
&& (rect.left + rect.width) > point.x | ||
&& rect.top <= point.y | ||
&& (rect.top + rect.height) > point.y); | ||
/** | ||
* Crete new simple location object from region | ||
* | ||
* @param {{top: number, left: number, width: number, height: number}} region | ||
* @returns {{x: number, y: number}} New location object | ||
*/ | ||
GeometryUtils.createLocationFromRegion = function(region) { | ||
return GeometryUtils.createLocation(region.left, region.top); | ||
}; | ||
/** | ||
* Crete new simple location object from location | ||
* | ||
* @param region The region from which we want to extract the sub regions. {left, top, width, height} | ||
* @param subRegionSize The max size of a sub region {width, height} | ||
* @param {{x: number, y: number}} location | ||
* @returns {{x: number, y: number}} New location object | ||
*/ | ||
GeometryUtils.getSubRegions = function (region, subRegionSize) { | ||
GeometryUtils.createLocationFromLocation = function(location) { | ||
return GeometryUtils.createLocation(location.x, location.y); | ||
}; | ||
/** | ||
* Crete new simple size object from values | ||
* | ||
* @param {number} width | ||
* @param {number} height | ||
* @returns {{width: number, height: number}} New size object | ||
*/ | ||
GeometryUtils.createSize = function(width, height) { | ||
return { | ||
width: Math.ceil(width) || 0, | ||
height: Math.ceil(height) || 0 | ||
}; | ||
}; | ||
/** | ||
* Get a scaled location. | ||
* | ||
* @param {{x: number, y: number}} location | ||
* @param {number} scaleRatio The ratio by which to scale the results. | ||
* @returns {{x: number, y: number}} A scaled copy of the current location. | ||
*/ | ||
GeometryUtils.scaleLocation = function(location, scaleRatio) { | ||
return { | ||
x: Math.ceil(location.x * scaleRatio), | ||
y: Math.ceil(location.y * scaleRatio) | ||
}; | ||
}; | ||
/** | ||
* Get a scaled version of the current size. | ||
* | ||
* @param {{width: number, height: number}} size | ||
* @param {number} scaleRatio The ratio by which to scale the results. | ||
* @returns {{width: number, height: number}} A scaled version of the current size. | ||
*/ | ||
GeometryUtils.scaleSize = function(size, scaleRatio) { | ||
return { | ||
width: Math.ceil(size.width * scaleRatio), | ||
height: Math.ceil(size.height * scaleRatio) | ||
}; | ||
}; | ||
/** | ||
* Get a region which is a scaled version of the current region. | ||
* IMPORTANT: This also scales the LOCATION(!!) of the region (not just its size). | ||
* | ||
* @param {{top: number, left: number, width: number, height: number}} region | ||
* @param {number} scaleRatio The ratio by which to scale the results. | ||
* @returns {{top: number, left: number, width: number, height: number}} A new region which is a scaled version of the current region. | ||
*/ | ||
GeometryUtils.scaleRegion = function(region, scaleRatio) { | ||
return { | ||
width: Math.ceil(region.width * scaleRatio), | ||
height: Math.ceil(region.height * scaleRatio), | ||
top: Math.ceil(region.top * scaleRatio), | ||
left: Math.ceil(region.left * scaleRatio) | ||
}; | ||
}; | ||
/** | ||
* Crete new simple size object from region | ||
* | ||
* @param {{top: number, left: number, width: number, height: number}} region | ||
* @returns {{width: number, height: number}} New size object | ||
*/ | ||
GeometryUtils.createSizeFromRegion = function(region) { | ||
return GeometryUtils.createSize(region.width, region.height); | ||
}; | ||
/** | ||
* Check if a region is intersected with the current region. | ||
* | ||
* @param {{top: number, left: number, width: number, height: number}} region1 First region | ||
* @param {{top: number, left: number, width: number, height: number}} region2 Second region | ||
* @return {boolean} True if the regions are intersected, false otherwise. | ||
*/ | ||
GeometryUtils.isRegionsIntersected = function (region1, region2) { | ||
var aRight = region1.left + region1.width; | ||
var aBottom = region1.top + region1.height; | ||
var bRight = region2.left + region2.width; | ||
var bBottom = region2.top + region2.height; | ||
return (((region1.left <= region2.left && region2.left <= aRight) || (region2.left <= region1.left && region1.left <= bRight)) | ||
&& ((region1.top <= region2.top && region2.top <= aBottom) || (region2.top <= region1.top && region1.top <= bBottom))); | ||
}; | ||
/** | ||
* Get the intersection of two regions | ||
* | ||
* @param {{top: number, left: number, width: number, height: number}} region1 The first region object | ||
* @param {{top: number, left: number, width: number, height: number}} region2 The second region object | ||
* @returns {{top: number, left: number, width: number, height: number}} | ||
*/ | ||
GeometryUtils.intersect = function (region1, region2) { | ||
if (!GeometryUtils.isRegionsIntersected(region1, region2)) { | ||
return GeometryUtils.createRegion(0, 0, 0, 0); | ||
} | ||
var top = Math.max(region1.top, region2.top); | ||
var left = Math.max(region1.left, region2.left); | ||
var bottom = Math.min(region1.top + region1.height, region2.top + region2.height); | ||
var right = Math.min(region1.left + region1.width, region2.left + region2.width); | ||
return GeometryUtils.createRegion(top, left, right - left, bottom - top); | ||
}; | ||
/** | ||
*Check if a specified location is contained within this region. | ||
* | ||
* @param {{top: number, left: number, width: number, height: number}} region | ||
* @param {{x: number, y: number}} location | ||
* @returns {boolean} rue if the location is contained within this region, false otherwise. | ||
*/ | ||
GeometryUtils.isRegionContainsLocation = function (region, location) { | ||
return (region.left <= location.x | ||
&& (region.left + region.width) > location.x | ||
&& region.top <= location.y | ||
&& (region.top + region.height) > location.y); | ||
}; | ||
/** | ||
* Check if a specified region is contained within the another region. | ||
* | ||
* @param {{top: number, left: number, width: number, height: number}} region1 | ||
* @param {{top: number, left: number, width: number, height: number}} region2 | ||
* @return {boolean} True if the region is contained within the another region, false otherwise. | ||
*/ | ||
GeometryUtils.isRegionContainsRegion = function (region1, region2) { | ||
var right = this.left + this.width; | ||
var otherRight = region2.left + region2.width; | ||
var bottom = this.top + this.height; | ||
var otherBottom = region2.top + region2.height; | ||
return this.top <= region2.top && this.left <= region2.left && bottom >= otherBottom && right >= otherRight; | ||
}; | ||
/** | ||
* Returns a list of sub-regions which compose the current region. | ||
* | ||
* @param {{top: number, left: number, width: number, height: number}} region The region from which we want to extract the sub regions. | ||
* @param {{width: number, height: number}} subRegionSize The default sub-region size to use. | ||
* @param isFixedSize If {@code false}, then sub-regions might have a size which is smaller then {@code subRegionSize} | ||
* (thus there will be no overlap of regions). Otherwise, all sub-regions will have the same size, but sub-regions might overlap. | ||
* @return {Array.<{top: number, left: number, width: number, height: number}>} The sub-regions composing the current region. | ||
* If {@code subRegionSize} is equal or greater than the current region, only a single region is returned. | ||
*/ | ||
GeometryUtils.getSubRegions = function (region, subRegionSize, isFixedSize) { | ||
if (isFixedSize) { | ||
return GeometryUtils.getSubRegionsWithFixedSize(region, subRegionSize); | ||
} | ||
return GeometryUtils.getSubRegionsWithVaryingSize(region, subRegionSize); | ||
}; | ||
/** | ||
* @param {{top: number, left: number, width: number, height: number}} region The region to divide into sub-regions. | ||
* @param {{width: number, height: number}} subRegionSize The maximum size of each sub-region. | ||
* @return {Array.<{top: number, left: number, width: number, height: number}>} The sub-regions composing the current region. | ||
* If subRegionSize is equal or greater than the current region, only a single region is returned. | ||
*/ | ||
GeometryUtils.getSubRegionsWithFixedSize = function (region, subRegionSize) { | ||
ArgumentGuard.notNull(region, "containerRegion"); | ||
ArgumentGuard.notNull(subRegionSize, "subRegionSize"); | ||
var subRegions = []; | ||
var currentTop = region.top; | ||
var bottom = region.top + region.height; | ||
var right = region.left + region.width; | ||
// Normalizing. | ||
var subRegionWidth = Math.min(region.width, subRegionSize.width); | ||
var subRegionHeight = Math.min(region.height, subRegionSize.height); | ||
var currentBottom, currentLeft, currentRight; | ||
while (currentTop < bottom) { | ||
currentBottom = currentTop + subRegionHeight; | ||
if (currentBottom > bottom) { | ||
currentBottom = bottom; | ||
currentTop = currentBottom - subRegionHeight; | ||
// If the requested size is greater or equal to the entire region size, we return a copy of the region. | ||
if (subRegionWidth == region.width && subRegionHeight == region.height) { | ||
subRegions.push({left: region.left, top: region.top, width: region.width, height: region.height}); | ||
return subRegions; | ||
} | ||
var currentTop = region.top; | ||
var bottom = region.top + region.height - 1; | ||
var right = region.left + region.width - 1; | ||
while (currentTop <= bottom) { | ||
if (currentTop + subRegionHeight > bottom) { | ||
currentTop = (bottom - subRegionHeight) + 1; | ||
} | ||
currentLeft = region.left; | ||
while (currentLeft < right) { | ||
currentRight = currentLeft + subRegionWidth; | ||
if (currentRight > right) { | ||
currentRight = right; | ||
currentLeft = currentRight - subRegionWidth; | ||
var currentLeft = region.left; | ||
while (currentLeft <= right) { | ||
if (currentLeft + subRegionWidth > right) { | ||
currentLeft = (right - subRegionWidth) + 1; | ||
} | ||
subRegions.push({left: currentLeft, top: currentTop, width: subRegionWidth, height: subRegionHeight}); | ||
currentLeft += subRegionWidth; | ||
@@ -83,8 +288,86 @@ } | ||
} | ||
return subRegions; | ||
}; | ||
/** | ||
* @param {{top: number, left: number, width: number, height: number}} region The region to divide into sub-regions. | ||
* @param {{width: number, height: number}} maxSubRegionSize The maximum size of each sub-region (some regions might be smaller). | ||
* @return {Array.<{top: number, left: number, width: number, height: number}>} The sub-regions composing the current region. | ||
* If maxSubRegionSize is equal or greater than the current region, only a single region is returned. | ||
*/ | ||
GeometryUtils.getSubRegionsWithVaryingSize = function (region, maxSubRegionSize) { | ||
ArgumentGuard.notNull(region, "containerRegion"); | ||
ArgumentGuard.notNull(maxSubRegionSize, "maxSubRegionSize"); | ||
ArgumentGuard.greaterThanZero(maxSubRegionSize.width, "maxSubRegionSize.width"); | ||
ArgumentGuard.greaterThanZero(maxSubRegionSize.height, "maxSubRegionSize.height"); | ||
var subRegions = []; | ||
var currentTop = region.top; | ||
var bottom = region.top + region.height; | ||
var right = region.left + region.width; | ||
while (currentTop < bottom) { | ||
var currentBottom = currentTop + maxSubRegionSize.height; | ||
if (currentBottom > bottom) { currentBottom = bottom; } | ||
var currentLeft = region.left; | ||
while (currentLeft < right) { | ||
var currentRight = currentLeft + maxSubRegionSize.width; | ||
if (currentRight > right) { currentRight = right; } | ||
var currentHeight = currentBottom - currentTop; | ||
var currentWidth = currentRight - currentLeft; | ||
subRegions.push({left: currentLeft, top: currentTop, width: currentWidth, height: currentHeight}); | ||
currentLeft += maxSubRegionSize.width; | ||
} | ||
currentTop += maxSubRegionSize.height; | ||
} | ||
return subRegions; | ||
}; | ||
//noinspection JSUnresolvedVariable | ||
/** | ||
* Translates this location by the specified amount (in place!). | ||
* | ||
* @param {{x: number, y: number}} location The original location | ||
* @param {{x: number, y: number}} offset The amount of the offset | ||
* @returns {{x: number, y: number}} | ||
*/ | ||
GeometryUtils.locationOffset = function (location, offset) { | ||
return { | ||
x: location.x + offset.x, | ||
y: location.y + offset.y | ||
}; | ||
}; | ||
/** | ||
* Translates this region by the specified amount (in place!). | ||
* | ||
* @param {{top: number, left: number, width: number, height: number}} region The original region | ||
* @param {{x: number, y: number}} offset The amount of the offset | ||
* @returns {{top: number, left: number, width: number, height: number}} | ||
*/ | ||
GeometryUtils.regionOffset = function (region, offset) { | ||
return { | ||
top: region.top + offset.x, | ||
left: region.left + offset.y, | ||
width: region.width, | ||
height: region.height | ||
}; | ||
}; | ||
/** | ||
* @param {{top: number, left: number, width: number, height: number}} region The region | ||
* @returns {{x: number, y: number}} | ||
*/ | ||
GeometryUtils.getMiddleOffsetOfRegion = function (region) { | ||
return { | ||
x: region.width / 2, | ||
y: region.height / 2 | ||
}; | ||
}; | ||
module.exports = GeometryUtils; | ||
}()); |
@@ -15,18 +15,19 @@ /* | ||
var StreamUtils = require('./StreamUtils'), | ||
PNG = require('node-png').PNG, | ||
fs = require('fs'); | ||
var ReadableBufferStream = StreamUtils.ReadableBufferStream; | ||
var WritableBufferStream = StreamUtils.WritableBufferStream; | ||
ScaleMethod = require('./ScaleMethod'), | ||
fs = require('fs'), | ||
/** @type {PNG} */ | ||
PNG = require('pngjs').PNG; | ||
var ReadableBufferStream = StreamUtils.ReadableBufferStream, | ||
WritableBufferStream = StreamUtils.WritableBufferStream; | ||
var ImageUtils = {}; | ||
/** | ||
* Processes a PNG buffer - returns it as BMP. | ||
* | ||
* parseImage - processes a PNG buffer - returns it as BMP. | ||
* | ||
* @param {Buffer} image | ||
* @param {Object} promiseFactory | ||
* @param {PromiseFactory} promiseFactory | ||
* @returns {Promise<PNG>} imageBmp object | ||
* | ||
* @returns {Object} - Promise | ||
* | ||
**/ | ||
@@ -51,17 +52,22 @@ ImageUtils.parseImage = function parseImage(image, promiseFactory) { | ||
/** | ||
* Repacks a parsed image to a PNG buffer. | ||
* | ||
* packImage - repacks a parsed image to a PNG buffer. | ||
* | ||
* @param {Object} imageBmp - parsed image as returned from parseImage | ||
* @param {Object} promiseFactory | ||
* | ||
* @returns {Object} - Promise - when resolved contains a buffer | ||
* | ||
* @param {PNG} imageBmp Parsed image as returned from parseImage | ||
* @param {PromiseFactory} promiseFactory | ||
* @returns {Promise<Buffer>} when resolved contains a buffer | ||
**/ | ||
ImageUtils.packImage = function packImage(imageBmp, promiseFactory) { | ||
return promiseFactory.makePromise(function (resolve, reject) { | ||
return promiseFactory.makePromise(function (resolve) { | ||
var png = new PNG({ | ||
width: imageBmp.width, | ||
height: imageBmp.height, | ||
bitDepth: 8, | ||
filterType: 4 | ||
}); | ||
png.data = new Buffer(imageBmp.data); | ||
// Write back to a temp png file | ||
var croppedImageStream = new WritableBufferStream(); | ||
imageBmp.pack().pipe(croppedImageStream) | ||
png.pack().pipe(croppedImageStream) | ||
.on('finish', function () { | ||
@@ -74,339 +80,273 @@ resolve(croppedImageStream.getBuffer()); | ||
/** | ||
* Scaled a parsed image by a given factor. | ||
* | ||
* scaleImage - scaled a parsed image by a given factor. | ||
* | ||
* @param {Object} imageBmp - will be modified | ||
* @param {Float} scale factor to multiply the image dimensions by (lower than 1 for scale down) | ||
* @param {Object} promiseFactory | ||
* | ||
* @returns {Object} - Promise - empty | ||
* | ||
* @param {PNG} imageBmp - will be modified | ||
* @param {number} scaleRatio factor to multiply the image dimensions by (lower than 1 for scale down) | ||
* @param {ScaleMethod} scaleMethod | ||
* @param {PromiseFactory} promiseFactory | ||
* @returns {Promise<void>} | ||
**/ | ||
ImageUtils.scaleImage = function scaleImage(imageBmp, scale, promiseFactory) { | ||
// two-dimensional array coordinates to a vector index | ||
function xytoi(ix, iy, w) { | ||
// byte array, r,g,b,a | ||
return((ix + w * iy) * 4); | ||
ImageUtils.scaleImage = function scaleImage(imageBmp, scaleRatio, scaleMethod, promiseFactory) { | ||
if (scaleRatio === 1) { | ||
return promiseFactory.makePromise(function (resolve) { | ||
resolve(imageBmp); | ||
}); | ||
} | ||
function interpolate (t, a, b, c, d){ | ||
return 0.5 * (c - a + (2.0*a - 5.0*b + 4.0*c - d + (3.0*(b - c) + d - a)*t)*t)*t + b; | ||
} | ||
var scaledWidth = imageBmp.width * scaleRatio; | ||
var scaledHeight = imageBmp.height * scaleRatio; | ||
return ImageUtils.resizeImage(imageBmp, scaledWidth, scaledHeight, scaleMethod, promiseFactory); | ||
}; | ||
function interpolateBicubic(x, y, values) { | ||
var i0, i1, i2, i3; | ||
/** | ||
* Resize a parsed image by a given dimensions. | ||
* | ||
* @param {PNG} imageBmp - will be modified | ||
* @param {number} width The width to resize the image to | ||
* @param {number} height The height to resize the image to | ||
* @param {ScaleMethod} scaleMethod | ||
* @param {PromiseFactory} promiseFactory | ||
* @returns {Promise<void>} | ||
**/ | ||
ImageUtils.resizeImage = function resizeImage(imageBmp, width, height, scaleMethod, promiseFactory) { | ||
i0 = interpolate(x, values[0][0], values[1][0], values[2][0], values[3][0]); | ||
i1 = interpolate(x, values[0][1], values[1][1], values[2][1], values[3][1]); | ||
i2 = interpolate(x, values[0][2], values[1][2], values[2][2], values[3][2]); | ||
i3 = interpolate(x, values[0][3], values[1][3], values[2][3], values[3][3]); | ||
return interpolate(y, i0, i1, i2, i3); | ||
function doBicubicInterpolation(src, dst) { | ||
var bufSrc = src.data; | ||
var bufDst = dst.data; | ||
var wSrc = src.width; | ||
var hSrc = src.height; | ||
var wDst = dst.width; | ||
var hDst = dst.height; | ||
// when dst smaller than src/2, interpolate first to a multiple between 0.5 and 1.0 src, then sum squares | ||
var wM = Math.max(1, Math.floor(wSrc / wDst)); | ||
var wDst2 = wDst * wM; | ||
var hM = Math.max(1, Math.floor(hSrc / hDst)); | ||
var hDst2 = hDst * hM; | ||
var interpolateCubic = function (x0, x1, x2, x3, t) { | ||
var a0 = x3 - x2 - x0 + x1; | ||
var a1 = x0 - x1 - a0; | ||
var a2 = x2 - x0; | ||
return Math.max(0, Math.min(255, (a0 * (t * t * t)) + (a1 * (t * t)) + (a2 * t) + (x1))); | ||
}; | ||
var i, j, x, y, k, t, xPos, yPos, kPos, buf1Pos, buf2Pos; | ||
// Pass 1 - interpolate rows | ||
// buf1 has width of dst2 and height of src | ||
var buf1 = new Buffer(wDst2 * hSrc * 4); | ||
for (i = 0; i < hSrc; i++) { | ||
for (j = 0; j < wDst2; j++) { | ||
x = j * (wSrc - 1) / wDst2; | ||
xPos = Math.floor(x); | ||
t = x - xPos; | ||
var srcPos = (i * wSrc + xPos) * 4; | ||
buf1Pos = (i * wDst2 + j) * 4; | ||
for (k = 0; k < 4; k++) { | ||
kPos = srcPos + k; | ||
var x0 = (xPos > 0) ? bufSrc[kPos - 4] : 2 * bufSrc[kPos] - bufSrc[kPos + 4]; | ||
var x1 = bufSrc[kPos]; | ||
var x2 = bufSrc[kPos + 4]; | ||
var x3 = (xPos < wSrc - 2) ? bufSrc[kPos + 8] : 2 * bufSrc[kPos + 4] - bufSrc[kPos]; | ||
buf1[buf1Pos + k] = interpolateCubic(x0, x1, x2, x3, t); | ||
} | ||
} | ||
} | ||
// Pass 2 - interpolate columns | ||
// buf2 has width and height of dst2 | ||
var buf2 = new Buffer(wDst2 * hDst2 * 4); | ||
for (i = 0; i < hDst2; i++) { | ||
for (j = 0; j < wDst2; j++) { | ||
y = i * (hSrc - 1) / hDst2; | ||
yPos = Math.floor(y); | ||
t = y - yPos; | ||
buf1Pos = (yPos * wDst2 + j) * 4; | ||
buf2Pos = (i * wDst2 + j) * 4; | ||
for (k = 0; k < 4; k++) { | ||
kPos = buf1Pos + k; | ||
var y0 = (yPos > 0) ? buf1[kPos - wDst2 * 4] : 2 * buf1[kPos] - buf1[kPos + wDst2 * 4]; | ||
var y1 = buf1[kPos]; | ||
var y2 = buf1[kPos + wDst2 * 4]; | ||
var y3 = (yPos < hSrc - 2) ? buf1[kPos + wDst2 * 8] : 2 * buf1[kPos + wDst2 * 4] - buf1[kPos]; | ||
buf2[buf2Pos + k] = interpolateCubic(y0, y1, y2, y3, t); | ||
} | ||
} | ||
} | ||
// Pass 3 - scale to dst | ||
var m = wM * hM; | ||
if (m > 1) { | ||
for (i = 0; i < hDst; i++) { | ||
for (j = 0; j < wDst; j++) { | ||
var r = 0; | ||
var g = 0; | ||
var b = 0; | ||
var a = 0; | ||
for (y = 0; y < hM; y++) { | ||
yPos = i * hM + y; | ||
for (x = 0; x < wM; x++) { | ||
xPos = j * wM + x; | ||
var xyPos = (yPos * wDst2 + xPos) * 4; | ||
r += buf2[xyPos]; | ||
g += buf2[xyPos + 1]; | ||
b += buf2[xyPos + 2]; | ||
a += buf2[xyPos + 3]; | ||
} | ||
} | ||
var pos = (i * wDst + j) * 4; | ||
bufDst[pos] = Math.round(r / m); | ||
bufDst[pos + 1] = Math.round(g / m); | ||
bufDst[pos + 2] = Math.round(b / m); | ||
bufDst[pos + 3] = Math.round(a / m); | ||
} | ||
} | ||
} else { | ||
dst.data = buf2; | ||
} | ||
return dst; | ||
} | ||
function doBicubicScale(srcImg, scale) { | ||
function scaleImageIncrementally(src, dst, method) { | ||
var incrementCount = 0; | ||
var currentWidth = src.width, | ||
currentHeight = src.height; | ||
var targetWidth = dst.width, | ||
targetHeight = dst.height; | ||
var destWidth = srcImg.width * scale; | ||
var destHeight = srcImg.height * scale; | ||
var destImageData = []; | ||
var i, j; | ||
var dx, dy; | ||
var iyv, iy0, ixv, ix0; | ||
var repeatX, repeatY; | ||
var idxD; | ||
var offset_row0, offset_row1, offset_row2, offset_row3; | ||
var offset_col0, offset_col1, offset_col2, offset_col3; | ||
var red_pixels, green_pixels, blue_pixels, alpha_pixels; | ||
for (i = 0; i < destHeight; ++i) { | ||
iyv = i / scale; | ||
iy0 = Math.floor(iyv); | ||
dst.data = src.data; | ||
dst.width = src.width; | ||
dst.height = src.height; | ||
// We have to special-case the pixels along the border and repeat their values if neccessary | ||
repeatY = 0; | ||
if(iy0 < 1) repeatY = -1; | ||
else if(iy0 > srcImg.height - 3) repeatY = iy0 - (srcImg.height - 3); | ||
var fraction = (method == ScaleMethod.ULTRA_QUALITY ? 7 : 2); | ||
for (j = 0; j < destWidth; ++j) { | ||
ixv = j / scale; | ||
ix0 = Math.floor(ixv); | ||
do { | ||
var prevCurrentWidth = currentWidth; | ||
var prevCurrentHeight = currentHeight; | ||
// We have to special-case the pixels along the border and repeat their values if neccessary | ||
repeatX = 0; | ||
if(ix0 < 1) repeatX = -1; | ||
else if(ix0 > srcImg.width - 3) repeatX = ix0 - (srcImg.width - 3); | ||
/* | ||
* If the current width is bigger than our target, cut it in half | ||
* and sample again. | ||
*/ | ||
if (currentWidth > targetWidth) { | ||
currentWidth -= (currentWidth / fraction); | ||
offset_row1 = ((iy0) * srcImg.width + ix0) * 4; | ||
offset_row0 = repeatY < 0 ? offset_row1 : ((iy0-1) * srcImg.width + ix0) * 4; | ||
offset_row2 = repeatY > 1 ? offset_row1 : ((iy0+1) * srcImg.width + ix0) * 4; | ||
offset_row3 = repeatY > 0 ? offset_row2 : ((iy0+2) * srcImg.width + ix0) * 4; | ||
/* | ||
* If we cut the width too far it means we are on our last | ||
* iteration. Just set it to the target width and finish up. | ||
*/ | ||
if (currentWidth < targetWidth) | ||
currentWidth = targetWidth; | ||
} | ||
offset_col1 = 0; | ||
offset_col0 = repeatX < 0 ? offset_col1 : -4; | ||
offset_col2 = repeatX > 1 ? offset_col1 : 4; | ||
offset_col3 = repeatX > 0 ? offset_col2 : 8; | ||
/* | ||
* If the current height is bigger than our target, cut it in half | ||
* and sample again. | ||
*/ | ||
if (currentHeight > targetHeight) { | ||
currentHeight -= (currentHeight / fraction); | ||
//Each offset is for the start of a row's red pixels | ||
red_pixels = [[srcImg.data[offset_row0+offset_col0], srcImg.data[offset_row1+offset_col0], srcImg.data[offset_row2+offset_col0], srcImg.data[offset_row3+offset_col0]], | ||
[srcImg.data[offset_row0+offset_col1], srcImg.data[offset_row1+offset_col1], srcImg.data[offset_row2+offset_col1], srcImg.data[offset_row3+offset_col1]], | ||
[srcImg.data[offset_row0+offset_col2], srcImg.data[offset_row1+offset_col2], srcImg.data[offset_row2+offset_col2], srcImg.data[offset_row3+offset_col2]], | ||
[srcImg.data[offset_row0+offset_col3], srcImg.data[offset_row1+offset_col3], srcImg.data[offset_row2+offset_col3], srcImg.data[offset_row3+offset_col3]]]; | ||
offset_row0++; | ||
offset_row1++; | ||
offset_row2++; | ||
offset_row3++; | ||
//Each offset is for the start of a row's green pixels | ||
green_pixels = [[srcImg.data[offset_row0+offset_col0], srcImg.data[offset_row1+offset_col0], srcImg.data[offset_row2+offset_col0], srcImg.data[offset_row3+offset_col0]], | ||
[srcImg.data[offset_row0+offset_col1], srcImg.data[offset_row1+offset_col1], srcImg.data[offset_row2+offset_col1], srcImg.data[offset_row3+offset_col1]], | ||
[srcImg.data[offset_row0+offset_col2], srcImg.data[offset_row1+offset_col2], srcImg.data[offset_row2+offset_col2], srcImg.data[offset_row3+offset_col2]], | ||
[srcImg.data[offset_row0+offset_col3], srcImg.data[offset_row1+offset_col3], srcImg.data[offset_row2+offset_col3], srcImg.data[offset_row3+offset_col3]]]; | ||
offset_row0++; | ||
offset_row1++; | ||
offset_row2++; | ||
offset_row3++; | ||
//Each offset is for the start of a row's blue pixels | ||
blue_pixels = [[srcImg.data[offset_row0+offset_col0], srcImg.data[offset_row1+offset_col0], srcImg.data[offset_row2+offset_col0], srcImg.data[offset_row3+offset_col0]], | ||
[srcImg.data[offset_row0+offset_col1], srcImg.data[offset_row1+offset_col1], srcImg.data[offset_row2+offset_col1], srcImg.data[offset_row3+offset_col1]], | ||
[srcImg.data[offset_row0+offset_col2], srcImg.data[offset_row1+offset_col2], srcImg.data[offset_row2+offset_col2], srcImg.data[offset_row3+offset_col2]], | ||
[srcImg.data[offset_row0+offset_col3], srcImg.data[offset_row1+offset_col3], srcImg.data[offset_row2+offset_col3], srcImg.data[offset_row3+offset_col3]]]; | ||
offset_row0++; | ||
offset_row1++; | ||
offset_row2++; | ||
offset_row3++; | ||
//Each offset is for the start of a row's alpha pixels | ||
alpha_pixels =[[srcImg.data[offset_row0+offset_col0], srcImg.data[offset_row1+offset_col0], srcImg.data[offset_row2+offset_col0], srcImg.data[offset_row3+offset_col0]], | ||
[srcImg.data[offset_row0+offset_col1], srcImg.data[offset_row1+offset_col1], srcImg.data[offset_row2+offset_col1], srcImg.data[offset_row3+offset_col1]], | ||
[srcImg.data[offset_row0+offset_col2], srcImg.data[offset_row1+offset_col2], srcImg.data[offset_row2+offset_col2], srcImg.data[offset_row3+offset_col2]], | ||
[srcImg.data[offset_row0+offset_col3], srcImg.data[offset_row1+offset_col3], srcImg.data[offset_row2+offset_col3], srcImg.data[offset_row3+offset_col3]]]; | ||
/* | ||
* If we cut the height too far it means we are on our last | ||
* iteration. Just set it to the target height and finish up. | ||
*/ | ||
if (currentHeight < targetHeight) | ||
currentHeight = targetHeight; | ||
} | ||
// overall coordinates to unit square | ||
dx = ixv - ix0; dy = iyv - iy0; | ||
/* | ||
* Stop when we cannot incrementally step down anymore. | ||
* | ||
* This used to use a || condition, but that would cause problems | ||
* when using FIT_EXACT such that sometimes the width OR height | ||
* would not change between iterations, but the other dimension | ||
* would (e.g. resizing 500x500 to 500x250). | ||
* | ||
* Now changing this to an && condition requires that both | ||
* dimensions do not change between a resize iteration before we | ||
* consider ourselves done. | ||
*/ | ||
if (prevCurrentWidth == currentWidth && prevCurrentHeight == currentHeight) | ||
break; | ||
idxD = xytoi(j, i, destWidth); | ||
// Render the incremental scaled image. | ||
var incrementalImage = { | ||
data: new Buffer(currentWidth * currentHeight * 4), | ||
width: currentWidth, | ||
height: currentHeight | ||
}; | ||
doBicubicInterpolation(dst, incrementalImage); | ||
destImageData[idxD] = interpolateBicubic(dx, dy, red_pixels); | ||
/* | ||
* Now treat our incremental partially scaled image as the src image | ||
* and cycle through our loop again to do another incremental scaling of it (if necessary). | ||
*/ | ||
dst.data = incrementalImage.data; | ||
dst.width = incrementalImage.width; | ||
dst.height = incrementalImage.height; | ||
destImageData[idxD+1] = interpolateBicubic(dx, dy, green_pixels); | ||
// Track how many times we go through this cycle to scale the image. | ||
incrementCount++; | ||
} while (currentWidth != targetWidth || currentHeight != targetHeight); | ||
destImageData[idxD+2] = interpolateBicubic(dx, dy, blue_pixels); | ||
return dst; | ||
} | ||
destImageData[idxD+3] = interpolateBicubic(dx, dy, alpha_pixels); | ||
function doNearestNeighbor(src, dst) { | ||
var wSrc = src.width, hSrc = src.height, bufSrc = src.data, | ||
wDst = dst.width, hDst = dst.height, bufDst = dst.data, | ||
i, j, iSrc, jSrc, posDst, posSrc; | ||
var hScale = hSrc / hDst, wScale = wSrc / wDst; | ||
for (i = 0; i < hDst; i++) { | ||
for (j = 0; j < wDst; j++) { | ||
iSrc = Math.round(i * hScale); | ||
jSrc = Math.round(j * wScale); | ||
posDst = (i * wDst + j) << 2; | ||
posSrc = (iSrc * wSrc + jSrc) << 2; | ||
bufDst[posDst++] = bufSrc[posSrc++]; | ||
bufDst[posDst++] = bufSrc[posSrc++]; | ||
bufDst[posDst++] = bufSrc[posSrc++]; | ||
bufDst[posDst++] = bufSrc[posSrc++]; | ||
} | ||
} | ||
return { | ||
data: destImageData, | ||
width: destWidth, | ||
height: destHeight | ||
}; | ||
return dst; | ||
} | ||
//function doBilinearScale(srcImg, scale) { | ||
// // c.f.: wikipedia english article on bilinear interpolation | ||
// // taking the unit square, the inner loop looks like this | ||
// // note: there's a function call inside the double loop to this one | ||
// // maybe a performance killer, optimize this whole code as you need | ||
// function inner(f00, f10, f01, f11, x, y) { | ||
// var un_x = 1.0 - x; var un_y = 1.0 - y; | ||
// return (f00 * un_x * un_y + f10 * x * un_y + f01 * un_x * y + f11 * x * y); | ||
// } | ||
// var destWidth = srcImg.width * scale; | ||
// var destHeight = srcImg.height * scale; | ||
// var destImageData = []; | ||
// var i, j; | ||
// var iyv, iy0, iy1, ixv, ix0, ix1; | ||
// var idxD, idxS00, idxS10, idxS01, idxS11; | ||
// var dx, dy; | ||
// var r, g, b, a; | ||
// for (i = 0; i < destHeight; ++i) { | ||
// iyv = i / scale; | ||
// iy0 = Math.floor(iyv); | ||
// // Math.ceil can go over bounds | ||
// iy1 = ( Math.ceil(iyv) > (srcImg.height-1) ? (srcImg.height-1) : Math.ceil(iyv) ); | ||
// for (j = 0; j < destWidth; ++j) { | ||
// ixv = j / scale; | ||
// ix0 = Math.floor(ixv); | ||
// | ||
// // Math.ceil can go over bounds | ||
// ix1 = ( Math.ceil(ixv) > (srcImg.width-1) ? (srcImg.width-1) : Math.ceil(ixv) ); | ||
// idxD = xytoi(j, i, destWidth); | ||
// | ||
// // matrix to vector indices | ||
// idxS00 = xytoi(ix0, iy0, srcImg.width); | ||
// idxS10 = xytoi(ix1, iy0, srcImg.width); | ||
// idxS01 = xytoi(ix0, iy1, srcImg.width); | ||
// idxS11 = xytoi(ix1, iy1, srcImg.width); | ||
// | ||
// // overall coordinates to unit square | ||
// dx = ixv - ix0; dy = iyv - iy0; | ||
// | ||
// // I let the r, g, b, a on purpose for debugging | ||
// r = inner(srcImg.data[idxS00], srcImg.data[idxS10], | ||
// srcImg.data[idxS01], srcImg.data[idxS11], dx, dy); | ||
// destImageData[idxD] = r; | ||
// | ||
// g = inner(srcImg.data[idxS00+1], srcImg.data[idxS10+1], | ||
// srcImg.data[idxS01+1], srcImg.data[idxS11+1], dx, dy); | ||
// destImageData[idxD+1] = g; | ||
// | ||
// b = inner(srcImg.data[idxS00+2], srcImg.data[idxS10+2], | ||
// srcImg.data[idxS01+2], srcImg.data[idxS11+2], dx, dy); | ||
// destImageData[idxD+2] = b; | ||
// | ||
// a = inner(srcImg.data[idxS00+3], srcImg.data[idxS10+3], | ||
// srcImg.data[idxS01+3], srcImg.data[idxS11+3], dx, dy); | ||
// destImageData[idxD+3] = a; | ||
// } | ||
// } | ||
// | ||
// return { | ||
// data: destImageData, | ||
// width: destWidth, | ||
// height: destHeight | ||
// }; | ||
//} | ||
return promiseFactory.makePromise(function (resolve) { | ||
width = Math.ceil(width); | ||
height = Math.ceil(height); | ||
//function doLanczosScale(srcImgData, width, height, scale) { | ||
// function lanczosCreate(lobes) { | ||
// return function(x) { | ||
// if (x > lobes) | ||
// return 0; | ||
// x *= Math.PI; | ||
// if (Math.abs(x) < 1e-16) | ||
// return 1; | ||
// var xx = x / lobes; | ||
// return Math.sin(x) * Math.sin(xx) / x / xx; | ||
// }; | ||
// } | ||
// | ||
// function process1() { | ||
// center.x = (u + 0.5) * ratio; | ||
// icenter.x = Math.floor(center.x); | ||
// for (var v = 0; v < destHeight; v++) { | ||
// center.y = (v + 0.5) * ratio; | ||
// icenter.y = Math.floor(center.y); | ||
// var a, r, g, b; | ||
// a = r = g = b = 0; | ||
// for (var i = icenter.x - range2; i <= icenter.x + range2; i++) { | ||
// if (i < 0 || i >= width) | ||
// continue; | ||
// var f_x = Math.floor(1000 * Math.abs(i - center.x)); | ||
// if (!cacheLanc[f_x]) | ||
// cacheLanc[f_x] = {}; | ||
// for (var j = icenter.y - range2; j <= icenter.y + range2; j++) { | ||
// if (j < 0 || j >= height) | ||
// continue; | ||
// var f_y = Math.floor(1000 * Math.abs(j - center.y)); | ||
// if (cacheLanc[f_x][f_y] == undefined) | ||
// cacheLanc[f_x][f_y] = lanczos(Math.sqrt(Math.pow(f_x * rcp_ratio, 2) | ||
// + Math.pow(f_y * rcp_ratio, 2)) / 1000); | ||
// var weight = cacheLanc[f_x][f_y]; | ||
// if (weight > 0) { | ||
// var idx = (j * width + i) * 4; | ||
// a += weight; | ||
// r += weight * srcImgData[idx]; | ||
// g += weight * srcImgData[idx + 1]; | ||
// b += weight * srcImgData[idx + 2]; | ||
// } | ||
// } | ||
// } | ||
// var idx = (v * destWidth + u) * 4; | ||
// destImageData[idx] = r / a; | ||
// destImageData[idx + 1] = g / a; | ||
// destImageData[idx + 2] = b / a; | ||
// destImageData[idx + 3] = 0.5; | ||
// } | ||
// | ||
// u++; | ||
// } | ||
// | ||
// var lobes = 1; | ||
// var lanczos = lanczosCreate(lobes); | ||
// var destWidth = width * scale; | ||
// var destHeight = height * scale; | ||
// var destImageData = []; | ||
// var ratio = 1 / scale; | ||
// var rcp_ratio = 2 * scale; | ||
// var range2 = Math.ceil(ratio * lobes / 2); | ||
// var cacheLanc = {}; | ||
// var center = {}; | ||
// var icenter = {}; | ||
// var u = 0; | ||
// while (u < destWidth) { | ||
// process1(); | ||
// } | ||
// | ||
// return{ | ||
// data: destImageData, | ||
// width: destWidth, | ||
// height: destHeight | ||
// }; | ||
//} | ||
// | ||
//function resample_hermite(srcImg, scale){ | ||
// var destWidth = srcImg.width * scale; | ||
// var destHeight = srcImg.height * scale; | ||
// var destImageData = []; | ||
// var ratioHalf = Math.ceil(2/scale); | ||
// | ||
// for(var j = 0; j < destHeight; j++){ | ||
// for(var i = 0; i < destWidth; i++){ | ||
// var x2 = (i + j*destWidth) * 4; | ||
// var weight = 0; | ||
// var weights = 0; | ||
// var weights_alpha = 0; | ||
// var gx_r = 0, gx_g = 0, gx_b = 0, gx_a = 0; | ||
// var center_y = (j + 0.5)/scale; | ||
// for(var yy = Math.floor(j/scale); yy < (j + 1)/scale; yy++){ | ||
// var dy = Math.abs(center_y - (yy + 0.5))/ratioHalf; | ||
// var center_x = (i + 0.5) * ratioHalf; | ||
// var w0 = dy*dy //pre-calc part of w | ||
// for(var xx = Math.floor(i / scale); xx < (i + 1)/scale; xx++){ | ||
// var dx = Math.abs(center_x - (xx + 0.5))/ratioHalf; | ||
// var w = Math.sqrt(w0 + dx*dx); | ||
// if(w >= -1 && w <= 1){ | ||
// //hermite filter | ||
// weight = 2 * w*w*w - 3*w*w + 1; | ||
// if(weight > 0) { | ||
// dx = 4*(xx + yy*srcImg.width); | ||
// //alpha | ||
// gx_a += weight * srcImg.data[dx + 3]; | ||
// weights_alpha += weight; | ||
// //colors | ||
// if(srcImg.data[dx + 3] < 255) | ||
// weight = weight * srcImg.data[dx + 3] / 250; | ||
// gx_r += weight * srcImg.data[dx]; | ||
// gx_g += weight * srcImg.data[dx + 1]; | ||
// gx_b += weight * srcImg.data[dx + 2]; | ||
// weights += weight; | ||
// } | ||
// } | ||
// } | ||
// } | ||
// destImageData[x2] = gx_r / weights; | ||
// destImageData[x2 + 1] = gx_g / weights; | ||
// destImageData[x2 + 2] = gx_b / weights; | ||
// destImageData[x2 + 3] = gx_a / weights_alpha; | ||
// } | ||
// } | ||
// | ||
// return { | ||
// data: destImageData, | ||
// width: destWidth, | ||
// height: destHeight | ||
// }; | ||
//} | ||
var dst = { | ||
data: new Buffer(width * height * 4), | ||
width: width, | ||
height: height | ||
}; | ||
return promiseFactory.makePromise(function (resolve, reject) { | ||
if (scale === 1) { | ||
resolve(imageBmp); | ||
return; | ||
switch (scaleMethod) { | ||
case ScaleMethod.SPEED: | ||
doNearestNeighbor(imageBmp, dst); | ||
break; | ||
default: | ||
if (dst.width > imageBmp.width || dst.height > imageBmp.height) { | ||
doBicubicInterpolation(imageBmp, dst); | ||
} else { | ||
scaleImageIncrementally(imageBmp, dst, scaleMethod); | ||
} | ||
} | ||
var scaledBmp = doBicubicScale(imageBmp, scale); | ||
imageBmp.data = new Buffer(scaledBmp.data); | ||
imageBmp.width = scaledBmp.width; | ||
imageBmp.height = scaledBmp.height; | ||
imageBmp.data = dst.data; | ||
imageBmp.width = dst.width; | ||
imageBmp.height = dst.height; | ||
resolve(imageBmp); | ||
}); | ||
@@ -416,11 +356,8 @@ }; | ||
/** | ||
* Crops a parsed image - the image is changed | ||
* | ||
* cropImage - crops a parsed image - the image is changed | ||
* | ||
* @param {Object} imageBmp BMP | ||
* @param {Object} region to crop (left,top,width,height) | ||
* @param {Object} promiseFactory | ||
* | ||
* @returns {Object} - Promise - empty, just indicating completion | ||
* | ||
* @param {PNG} imageBmp | ||
* @param {{left: number, top: number, width: number, height: number}} region Region to crop | ||
* @param {PromiseFactory} promiseFactory | ||
* @returns {Promise<void>} | ||
**/ | ||
@@ -464,11 +401,8 @@ ImageUtils.cropImage = function cropImage(imageBmp, region, promiseFactory) { | ||
/** | ||
* Rotates a parsed image - the image is changed | ||
* | ||
* rotateImage - rotates a parsed image - the image is changed | ||
* | ||
* @param {Object} imageBmp BMP | ||
* @param {Object} deg how many degrees to rotate (in actuallity it's only by multipliers of 90) | ||
* @param {Object} promiseFactory | ||
* | ||
* @returns {Object} - Promise - empty, just indicating completion | ||
* | ||
* @param {PNG} imageBmp | ||
* @param {number} deg how many degrees to rotate (in actuality it's only by multipliers of 90) | ||
* @param {PromiseFactory} promiseFactory | ||
* @returns {Promise<void>} | ||
**/ | ||
@@ -485,3 +419,3 @@ ImageUtils.rotateImage = function rotateImage(imageBmp, deg, promiseFactory) { | ||
while (i > 0) { | ||
var bitmap = new Buffer(imageBmp.data.length); | ||
var buffer = new Buffer(imageBmp.data.length); | ||
var offset = 0; | ||
@@ -492,3 +426,3 @@ for (var x = 0; x < imageBmp.width; x++) { | ||
var data = imageBmp.data.readUInt32BE(idx, true); | ||
bitmap.writeUInt32BE(data, offset, true); | ||
buffer.writeUInt32BE(data, offset, true); | ||
offset += 4; | ||
@@ -498,4 +432,5 @@ } | ||
imageBmp.data = new Buffer(bitmap); | ||
imageBmp.data = Buffer.from(buffer); | ||
var tmp = imageBmp.width; | ||
//noinspection JSSuspiciousNameCombination | ||
imageBmp.width = imageBmp.height; | ||
@@ -513,7 +448,8 @@ imageBmp.height = tmp; | ||
* Copies pixels from the source image to the destination image. | ||
* | ||
* @param {PNG} dst The destination image. | ||
* @param dstPosition An object containing the top/left values of the pixel which is the starting point to copy to. | ||
* @param {{x: number, y: number}} dstPosition The pixel which is the starting point to copy to. | ||
* @param {PNG} src The source image. | ||
* @param srcPosition An object containing the top/left values of the pixel from which to start copying. | ||
* @param size An object containing width/height of the region to be copied. | ||
* @param {{x: number, y: number}} srcPosition The pixel from which to start copying. | ||
* @param {{width: number, height: number}} size The region to be copied. | ||
*/ | ||
@@ -523,8 +459,8 @@ ImageUtils.copyPixels = function copyPixels(dst, dstPosition, src, srcPosition, size) { | ||
for (y = 0; y < size.height; ++y) { | ||
dstY = dstPosition.top + y; | ||
srcY = srcPosition.top + y; | ||
dstY = dstPosition.y + y; | ||
srcY = srcPosition.y + y; | ||
for (x = 0; x < size.width; ++x) { | ||
dstX = dstPosition.left + x; | ||
srcX = srcPosition.left + x; | ||
dstX = dstPosition.x + x; | ||
srcX = srcPosition.x + x; | ||
@@ -543,9 +479,8 @@ // Since each pixel is composed of 4 values (RGBA) we multiply each index by 4. | ||
//noinspection JSValidateJSDoc | ||
/** | ||
* Creates a PNG instance from the given buffer. | ||
* | ||
* @param {Buffer} buffer A buffer containing PNG bytes. | ||
* @param {Object} promiseFactory | ||
* | ||
* @return {Promise} A promise which resolves to the PNG instance. | ||
* @param {PromiseFactory} promiseFactory | ||
* @return {Promise<PNG>} A promise which resolves to the PNG instance. | ||
*/ | ||
@@ -566,12 +501,12 @@ ImageUtils.createPngFromBuffer = function createPngFromBuffer(buffer, promiseFactory) { | ||
//noinspection JSValidateJSDoc | ||
/** | ||
* Stitches a part into the image. | ||
* @param stitchingPromise A promise which its "then" block will execute the stitching. T | ||
* @param {PNG} stitchedImage A PNG instance into which the part will be stitched. | ||
* @param {object} part A "part" object given in the {@code parts} argument of {@link ImageUtils.stitchImage}. | ||
* @param {Object} promiseFactory | ||
* | ||
* @return {Promise} A promise which is resolved when the stitching is done. | ||
* @private | ||
* @param {Promise<void>} stitchingPromise A promise which its "then" block will execute the stitching. | ||
* @param {PNG} stitchedImage A PNG instance into which the part will be stitched. | ||
* @param {{position: {x: number, y: number}, size: {width: number, height: number}, image: Buffer}} part | ||
* A "part" object given in the {@code parts} argument of {@link ImageUtils.stitchImage}. | ||
* @param {PromiseFactory} promiseFactory | ||
* @return {Promise<void>} A promise which is resolved when the stitching is done. | ||
*/ | ||
@@ -584,3 +519,3 @@ var _stitchPart = function (stitchingPromise, stitchedImage, part, promiseFactory) { | ||
ImageUtils.createPngFromBuffer(part.image, promiseFactory).then(function (pngImage) { | ||
ImageUtils.copyPixels(stitchedImage, part.position, pngImage, {left: 0, top: 0}, part.size); | ||
ImageUtils.copyPixels(stitchedImage, part.position, pngImage, {x: 0, y: 0}, part.size); | ||
resolve(stitchedImage); | ||
@@ -592,12 +527,10 @@ }); | ||
//noinspection JSValidateJSDoc | ||
/** | ||
* Stitches the given parts to a full image. | ||
* @param fullSize The size of the stitched image. Should have 'width' and 'height' properties. | ||
* @param {Array} parts The parts to stitch into an image. Each part should have: 'position' | ||
* (which includes top/left), 'size' (which includes width/height) and image | ||
* (a buffer containing PNG bytes) properties. | ||
* @param {Object} promiseFactory | ||
* | ||
* @return {Promise} A promise which resolves to the stitched image. | ||
* @param {{width: number, height: number}} fullSize The size of the stitched image. | ||
* @param {Array<{position: {x: number, y: number}, size: {width: number, height: number}, image: Buffer}>} parts | ||
* The parts to stitch into an image. | ||
* @param {PromiseFactory} promiseFactory | ||
* @return {Promise<Buffer>} A promise which resolves to the stitched image. | ||
*/ | ||
@@ -609,16 +542,10 @@ ImageUtils.stitchImage = function stitchImage(fullSize, parts, promiseFactory) { | ||
//noinspection JSLint | ||
for (var i = 0; i < parts.length; ++i) { | ||
//noinspection JSUnresolvedFunction | ||
stitchingPromise = _stitchPart(stitchingPromise, stitchedImage, parts[i], promiseFactory); | ||
} | ||
//noinspection JSUnresolvedFunction | ||
stitchingPromise.then(function () { | ||
var stitchedImageStream = new WritableBufferStream(); | ||
//noinspection JSUnresolvedFunction | ||
stitchedImage.pack().pipe(stitchedImageStream) | ||
.on('finish', function () { | ||
resolve(stitchedImageStream.getBuffer()); | ||
}); | ||
return ImageUtils.packImage(stitchedImage, promiseFactory); | ||
}).then(function (buffer) { | ||
resolve(buffer); | ||
}); | ||
@@ -625,0 +552,0 @@ }); |
@@ -17,9 +17,12 @@ /* | ||
var ImageUtils = require('./ImageUtils'); | ||
var disabled = !require('fs').open; | ||
var fs = require("fs"), | ||
ImageUtils = require('./ImageUtils'); | ||
var disabled = !fs.open; | ||
/** | ||
* Parses the image if possible - meaning dimensions and BMP are extracted and available | ||
* @param that - the context of the current instance of MutableImage | ||
* | ||
* @private | ||
* @param {MutableImage} that The context of the current instance of MutableImage | ||
*/ | ||
@@ -45,4 +48,5 @@ function _parseImage(that) { | ||
* Packs the image if possible - meaning the buffer is updated according to the edited BMP | ||
* @param that - the context of the current instance of MutableImage | ||
* | ||
* @private | ||
* @param {MutableImage} that The context of the current instance of MutableImage | ||
*/ | ||
@@ -65,7 +69,5 @@ function _packImage(that) { | ||
/** | ||
* C'tor = initializes the module settings | ||
* | ||
* @constructor | ||
* @param {Buffer} imageBuffer | ||
* @param {PromiseFactory} promiseFactory An object which will be used for creating deferreds/promises. | ||
* | ||
**/ | ||
@@ -83,8 +85,8 @@ function MutableImage(imageBuffer, promiseFactory) { | ||
//noinspection JSUnusedGlobalSymbols | ||
/** | ||
* Coordinates represent the image's position in a larger context (if any). E.g., A screenshot of the browser's | ||
* viewport of a web page. | ||
* Coordinates represent the image's position in a larger context (if any). | ||
* E.g., A screenshot of the browser's viewport of a web page. | ||
* | ||
* @return {Promise} A promise which resolves to the coordinates of the image in the larger | ||
* context (if any): {top: *, left: *} | ||
* @return {Promise.<{x: number, y: number}>} The coordinates of the image in the larger context (if any) | ||
*/ | ||
@@ -95,4 +97,4 @@ MutableImage.prototype.getCoordinates = function () { | ||
return { | ||
left: that._left, | ||
top: that._top | ||
x: that._left, | ||
y: that._top | ||
}; | ||
@@ -102,7 +104,9 @@ }); | ||
//noinspection JSUnusedGlobalSymbols | ||
/** | ||
* Coordinates represent the image's position in a larger context (if any). E.g., A screenshot of the browser's | ||
* viewport of a web page. | ||
* Coordinates represent the image's position in a larger context (if any). | ||
* E.g., A screenshot of the browser's viewport of a web page. | ||
* | ||
* @return {Promise} A promise which resolves once the set is done. | ||
* @param {{x: number, y: number}} coordinates | ||
* @return {Promise<void>} | ||
*/ | ||
@@ -112,4 +116,4 @@ MutableImage.prototype.setCoordinates = function (coordinates) { | ||
return _parseImage(that).then(function () { | ||
that._left = coordinates.left; | ||
that._top = coordinates.top; | ||
that._left = coordinates.x; | ||
that._top = coordinates.y; | ||
}); | ||
@@ -120,4 +124,5 @@ }; | ||
/** | ||
* Parses the image if necessary | ||
* @returns {Object} - the image size | ||
* Size of the image. Parses the image if necessary | ||
* | ||
* @return {Promise<{width: number, height: number}>} | ||
*/ | ||
@@ -137,5 +142,5 @@ MutableImage.prototype.getSize = function () { | ||
/** | ||
* return the image as buffer and image width and height. | ||
* Return the image as buffer and image width and height. | ||
* | ||
* {Object} Promise of an object with buffer and image dimensions | ||
* @return {Promise<{imageBuffer: Buffer, width: number, height: number}>} | ||
*/ | ||
@@ -161,6 +166,6 @@ MutableImage.prototype.asObject = function () { | ||
* | ||
* @param {Float} scale | ||
* @return {Object} promise - resolves without any value. | ||
* @param {ScaleProvider} scaleProvider | ||
* @return {Promise<MutableImage>} | ||
*/ | ||
MutableImage.prototype.scaleImage = function (scale) { | ||
MutableImage.prototype.scaleImage = function (scaleProvider) { | ||
var that = this; | ||
@@ -170,3 +175,3 @@ return _parseImage(that) | ||
if (that._isParsed) { | ||
return ImageUtils.scaleImage(that._imageBmp, scale, that._promiseFactory) | ||
return ImageUtils.scaleImage(that._imageBmp, scaleProvider.getScaleRatio(), scaleProvider.getScaleMethod(), that._promiseFactory) | ||
.then(function () { | ||
@@ -183,6 +188,6 @@ that._width = that._imageBmp.width; | ||
/** | ||
* crops the image according to the given region. | ||
* Crops the image according to the given region. | ||
* | ||
* @param {Object} region | ||
* @return {Object} promise - resolves without any value | ||
* @param {{left: number, top: number, width: number, height: number, relative: boolean=}} region | ||
* @return {Promise<MutableImage>} | ||
*/ | ||
@@ -194,11 +199,2 @@ MutableImage.prototype.cropImage = function (region) { | ||
if (that._isParsed) { | ||
// If the region's coordinates are relative to the image, we convert them to absolute coordinates. | ||
if (region && region.relative) { | ||
region = { | ||
left: region.left - that._left, | ||
top: region.top - that._top, | ||
width: region.width, | ||
height: region.height | ||
}; | ||
} | ||
return ImageUtils.cropImage(that._imageBmp, region, that._promiseFactory) | ||
@@ -216,6 +212,6 @@ .then(function () { | ||
/** | ||
* rotates the image according to the given degrees. | ||
* Rotates the image according to the given degrees. | ||
* | ||
* @param {Number} degrees | ||
* @return {Object} promise - resolves without any value | ||
* @return {Promise<MutableImage>} | ||
*/ | ||
@@ -238,3 +234,25 @@ MutableImage.prototype.rotateImage = function (degrees) { | ||
//noinspection JSUnusedGlobalSymbols | ||
/** | ||
* Write image to local directory | ||
* | ||
* @param {string} filename | ||
* @return {Promise<void>} | ||
*/ | ||
MutableImage.prototype.saveImage = function (filename) { | ||
var that = this; | ||
return this.asObject().then(function (imageObject) { | ||
return that._promiseFactory.makePromise(function (resolve, reject) { | ||
fs.writeFile(filename, imageObject.imageBuffer, function(err) { | ||
if(err) { | ||
reject(err); | ||
} | ||
resolve(that); | ||
}); | ||
}); | ||
}); | ||
}; | ||
module.exports = MutableImage; | ||
}()); |
@@ -14,4 +14,8 @@ /* | ||
"use strict"; | ||
/** | ||
* @constructor | ||
* @param {function} promiseFactoryFunc A function which receives as a parameter | ||
* the same function you would pass to a Promise constructor. | ||
* @param {function} deferredFactoryFunc A function which returns a deferred. | ||
*/ | ||
function PromiseFactory(promiseFactoryFunc, deferredFactoryFunc) { | ||
@@ -25,5 +29,6 @@ this._promiseFactoryFunc = promiseFactoryFunc; | ||
* Sets the factory methods which will be used to create promises and deferred-s. | ||
* @param promiseFactoryFunc A function which receives as a parameter the same function you would pass to a Promise | ||
* constructor. | ||
* @param deferredFactoryFunc A function which returns a deferred. | ||
* | ||
* @param {function} promiseFactoryFunc A function which receives as a parameter | ||
* the same function you would pass to a Promise constructor. | ||
* @param {function} deferredFactoryFunc A function which returns a deferred. | ||
*/ | ||
@@ -36,2 +41,7 @@ PromiseFactory.prototype.setFactoryMethods = function (promiseFactoryFunc, deferredFactoryFunc) { | ||
//noinspection JSUnusedGlobalSymbols | ||
/** | ||
* | ||
* @param {function} asyncAction | ||
* @returns {*} | ||
*/ | ||
PromiseFactory.prototype.makePromise = function (asyncAction) { | ||
@@ -38,0 +48,0 @@ if (this._promiseFactoryFunc) { |
@@ -16,2 +16,3 @@ /* | ||
Stream = require('stream'); | ||
//noinspection JSUnresolvedVariable | ||
@@ -22,8 +23,6 @@ var Readable = Stream.Readable; | ||
// --- ReadableBufferStream | ||
/** | ||
* ReadableBufferStream constructor. | ||
* @param {Buffer} buffer The buffer to be used as the stream's source. | ||
* @param {object|undefined} options (Optional) An "options" object to be passed to the stream constructor. | ||
* @param {object} [options] An "options" object to be passed to the stream constructor. | ||
* @constructor | ||
@@ -52,7 +51,5 @@ */ | ||
// --- WritableBufferStream | ||
/** | ||
* WritableBufferStream constructor. | ||
* @param {object|undefined} options (Optional) An "options" object to be passed to the stream constructor. | ||
* @param {object} [options] An "options" object to be passed to the stream constructor. | ||
* @constructor | ||
@@ -89,2 +86,32 @@ */ | ||
*/ | ||
WritableBufferStream.prototype.writeInt = function (value) { | ||
var buf = new Buffer(4); | ||
buf.writeInt32BE(value, 0); | ||
return this.write(buf); | ||
}; | ||
//noinspection JSUnusedGlobalSymbols | ||
/** | ||
* @return {Buffer} The buffer which contains the chunks written up to this point. | ||
*/ | ||
WritableBufferStream.prototype.writeShort = function (value) { | ||
var buf = new Buffer(2); | ||
buf.writeInt16BE(value, 0); | ||
return this.write(buf); | ||
}; | ||
//noinspection JSUnusedGlobalSymbols | ||
/** | ||
* @return {Buffer} The buffer which contains the chunks written up to this point. | ||
*/ | ||
WritableBufferStream.prototype.writeByte = function (value) { | ||
var buf = new Buffer(1); | ||
buf.writeInt8(value, 0); | ||
return this.write(buf); | ||
}; | ||
//noinspection JSUnusedGlobalSymbols | ||
/** | ||
* @return {Buffer} The buffer which contains the chunks written up to this point. | ||
*/ | ||
WritableBufferStream.prototype.getBuffer = function () { | ||
@@ -105,4 +132,2 @@ return this._buffer; | ||
// --- Exports | ||
var StreamUtils = {}; | ||
@@ -109,0 +134,0 @@ StreamUtils.ReadableBufferStream = ReadableBufferStream; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
126861
24
2902
1
+ Addedpngjs@^3.0.0
+ Addedpngjs@3.4.0(transitive)
- Removednode-png@0.4.3
- Removednode-png@0.4.3(transitive)