import analyzeImage from './services/analyzeImage';
import EffectManager from './services/EffectManager';
import { getImageDimensions, calculateAspectRatioFit, convertFileToBase64 } from './utils/generalFunctions';
import RevieveARComponent from './components/RevieveARComponent';
/** Class representing a RevieveAR image manipulator. */
class RevieveAR {
/**
* RevieveAR class.
* The core of the Revieve AR SDK.
* This should be instantiated for each image you want to manipulate.
* @constructor
* @param {Object} userConf - Configuration for the instance
* @param {String} userConf.partnerID - PartnerID assigned to your organization by Revieve
* @param {String=} userConf.skintone - User skintone in scale of 1-6 (fitzpatrick scale).
* Necessary for correct eye color and undertone calculations.
* @param {Object} userImage - Image to manipulate. This parameter could be a base64
* representation of the image or a file HTML object
* @param {Object=} containerId - Container's id where the results will be rendered.
* If you don't specify any container, you can use
* the method [getResults]{@link RevieveAR#getResults}
* to get the resulting div. If containerId is specified, results will be applied
* automatically to the container.
*/
constructor(userConf, userImage, containerId = null) {
if (!userConf) {
console.error('Constructor needs configuration object');
return Object.create(null);
}
if (userConf && (typeof userConf.partnerID === 'undefined' || typeof userConf.partnerID !== 'string')) {
console.error('Configuration must have partnerID and it must be string typed');
return Object.create(null);
}
if (!userImage) {
console.error('Constructor needs image parameter');
return Object.create(null);
}
this._configuration = userConf;
if (containerId) {
this._container = document.getElementById(containerId);
}
this._image = {
before: userImage,
};
this._status = null;
}
/**
* Use this method to initialize the API. You must call this method before apply any effect.
* @returns {Promise}
*/
initialize() {
return new Promise((resolve) => {
if (typeof this._image.before !== 'string') {
// image is in file format
convertFileToBase64(this._image.before).then((imageBase64) => {
this._image = {
before: imageBase64,
};
this._initializeActions().then(() => {
resolve();
});
});
} else {
this._initializeActions().then(() => {
resolve();
});
}
});
}
_initializeActions() {
return new Promise((resolve, reject) => {
getImageDimensions(this._image.before)
.then((dimensions) => {
if (this._container) {
this._finalDimensions = calculateAspectRatioFit(dimensions.width,
dimensions.height,
this._container.offsetWidth);
}
this._image.width = dimensions.width;
this._image.height = dimensions.height;
analyzeImage(this._image.before, this._configuration.partnerID,
this._configuration.skintone)
.then((response) => {
if (!response || (response && response.message === 'Error')) {
this._error = true;
reject(new Error('Error processing image in server'));
} else {
let ratio = null;
if (this._finalDimensions) {
ratio = this._finalDimensions.width / this._image.width;
}
if (response.status) {
this._status = response.status;
for (const status of this._status) {
if (status.isError) {
this._error = true;
}
}
}
this._effectManager = new EffectManager(response.results,
this._image.width, this._image.height, ratio);
this._error = false;
resolve();
}
});
});
});
}
/**
* Use this method to check if there were errors in image analysis.
* @returns {Boolean} Error
*/
hasError() {
return this._error;
}
/**
* Use this method to check warnings and errors in image analysis.
* @returns {Object[]} JSON object with description, idx, and isError boolean.
*/
getStatus() {
return this._status;
}
/**
* Get the original image ("before image") back.
* @returns {Object} BeforeImage
*/
getImageBefore() {
return this._image.before;
}
/**
* See the results of the manipulations.
* @returns {HTMLDivElement} After image - Div containing original image with added manipulations
*/
getResults() {
return this._resultDiv;
}
/**
* Reduce eyebags in the user image.
* Resulting effects will be applied to div containing the image returned by getResults
* or to the container specified in constructor.
* @returns {Promise}
* @see [getResults]{@link RevieveAR#getResults}
* @param {Float} strength - Strength of the reduction effect.
*/
reduceEyebags(strength) {
return this.setEffectByName('reduceEyebags', null, strength);
}
/**
* Reduce crows feet in the user image.
* Resulting effects will be applied to div containing the image returned by getResults
* or to the container specified in constructor.
* @returns {Promise}
* @see [getResults]{@link RevieveAR#getResults}
* @param {Float} strength - Strength of the reduction effect.
*/
reduceCrowsFeet(strength) {
return this.setEffectByName('reduceCrowsFeet', null, strength);
}
/**
* Reduce dark circles in the user image.
* Resulting effects will be applied to div containing the image returned by getResults
* or to the container specified in constructor.
* @returns {Promise}
* @see [getResults]{@link RevieveAR#getResults}
* @param {Float} strength - Strength of the reduction effect.
*/
reduceDarkcircles(strength) {
return this.setEffectByName('reduceDarkcircles', null, strength);
}
/**
* Reduce redness in the user image areas defined in masks parameter.
* Resulting effects will be applied to div containing the image returned by getResults
* or to the container specified in constructor.
* @returns {Promise}
* @see [getResults]{@link RevieveAR#getResults}
* @param {Float} strength - Strength of the reduction effect.
* @param {String[]} masks - Array of {@link RevieveAR.masks} defining the areas
* where the effect is going to be applied.
*/
reduceRedness(strength, masks) {
return this.setEffectByName('reduceRedness', masks, strength);
}
/**
* Reduce wrinkles in the user image areas defined in masks parameter.
* Resulting effects will be applied to div containing the image returned by getResults
* or to the container specified in constructor.
* @returns {Promise}
* @see [getResults]{@link RevieveAR#getResults}
* @param {Float} strength - Strength of the reduction effect.
* @param {String[]} masks - Array of {@link RevieveAR.masks} defining the areas
* where the effect is going to be applied.
*/
reduceWrinkles(strength, masks) {
return this.setEffectByName('reduceWrinkles', masks, strength);
}
/**
* Brighten the skin in the user image areas defined in masks parameter.
* Resulting effects will be applied to div containing the image returned by getResults
* or to the container specified in constructor.
* @returns {Promise}
* @see [getResults]{@link RevieveAR#getResults}
* @param {Float} strength - Strength of the reduction effect.
* @param {String[]} masks - Array of {@link RevieveAR.masks} defining the areas
* where the effect is going to be applied.
*/
brightenSkin(strength, masks) {
return this.setEffectByName('brightenSkin', masks, strength);
}
/**
* Apply lipstick in the user image.
* Resulting effects will be applied to div containing the image returned by getResults
* or to the container specified in constructor.
* @returns {Promise}
* @see [getResults]{@link RevieveAR#getResults}
* @param {Float} strength - Strength of the reduction effect.
* @param {String} color - rgb, hex or name of the lipstick's color you want to be applied.
*/
applyLipstick(strength, color) {
return this.setEffectByName('applyLipstick', null, strength, color);
}
/**
* Apply blush in the user image.
* Resulting effects will be applied to div containing the image returned by getResults
* or to the container specified in constructor.
* @returns {Promise}
* @see [getResults]{@link RevieveAR#getResults}
* @param {Float} strength - Strength of the reduction effect.
* @param {String} color - rgb, hex or name of the blush's color you want to be applied.
*/
applyBlush(strength, color) {
return this.setEffectByName('applyBlush', null, strength, color);
}
/**
* Apply foundation in the user image.
* Resulting effects will be applied to div containing the image returned by getResults
* or to the container specified in constructor.
* returned by getResults
* @returns {Promise}
* @see [getResults]{@link RevieveAR#getResults}
* @param {Float} strength - Strength of the reduction effect.
*/
applyFoundation(strength) {
return this.setEffectByName('applyFoundation', null, strength);
}
/**
* Please don't use this method directly. You have the general effect methods to work with them.
* Set an effect determined by parameter effect name in the user image.
* Resulting effects will be applied to div containing the image returned by getResults
* or to the container specified in constructor.
* @returns {Promise}
* @see [getResults]{@link RevieveAR#getResults}
* @param {String} effectName - Name of the effect to be applied.
* @param {String[]} masks - Array of {@link RevieveAR.masks} defining the areas where
* the effect is going to be applied.
* @param {Float} strength - Strength of the reduction effect.
* @param {String=} fillColor - Rgb, hex or name of the blush's color you want to be applied.
*/
setEffectByName(effectName, masks, strength, fillColor) {
return new Promise((resolve, reject) => {
if (!this._effectManager) {
this._error = true;
console.error('RevieveAR not properly initialized');
reject();
}
this._effectManager.setEffect(effectName, masks, strength, fillColor,
this._image.before, this._configuration.skintone)
.then(() => {
this._error = false;
if (!this._resultDiv) {
// we need to create a new div
this._resultDiv = document.createElement('div');
this._resultDiv.style.position = 'relative';
let originalImage = document.createElement('img');
originalImage.src = this._image.before;
originalImage.width = this._finalDimensions
? this._finalDimensions.width : this._image.width;
originalImage.height = this._finalDimensions
? this._finalDimensions.height : this._image.height;
this._resultDiv.appendChild(originalImage);
}
let canvas = this._effectManager._layers[effectName];
for (let divChildren of this._resultDiv.children) {
if (divChildren.id === canvas.id) {
this._resultDiv.removeChild(divChildren);
break;
}
}
if (parseFloat(strength) > 0) {
this._resultDiv.appendChild(canvas);
}
if (!this._container.firstChild) {
this._container.appendChild(this._resultDiv);
}
resolve();
})
.catch((error) => {
this._error = true;
if (error) {
console.error(error);
}
console.error('Error applying effects');
reject();
});
});
}
/**
* Method to reset results and delete all the effects applied before.
*/
reset() {
for (let divChildren of this._resultDiv.children) {
this._resultDiv.removeChild(divChildren);
}
this._effectManager._layers = [];
}
/**
* Test method that add to the root of the HTML document two objects: the original image
* and the resulting div with all the effects applied
*/
testImages() {
if (!document.getElementById('originalImage')) {
let originalImage = document.createElement('img');
originalImage.id = 'originalImage';
originalImage.src = this._image.before;
document.body.appendChild(originalImage);
}
if (this._resultDiv) {
document.body.appendChild(this._resultDiv);
}
}
}
/**
* List of possible areas where an effect could be applied.
*/
RevieveAR.masks = EffectManager.masks;
RevieveAR._effectCatalog = EffectManager.effectBundles;
window.RevieveAR = RevieveAR;
export {
RevieveAR,
RevieveARComponent,
};