Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

eyes.utils

Package Overview
Dependencies
Maintainers
1
Versions
35
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

eyes.utils - npm Package Compare versions

Comparing version 0.0.17 to 0.0.18

src/CoordinatesType.js

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');

12

package.json
{
"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;

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc