ccctool-lib
Version:
1,075 lines (936 loc) • 75 kB
JavaScript
import { isCMSJSON, isNumber, checkIndex, checkInterpolationType, checkColorSpaceNotation, isColorJSON, isCMSKey } from "../helper/guardClauses.js";
import { getRatio } from "../helper/math.js";
import { colorToVector, vectorToColor } from "../color/colorHelper.js";
import { linearInterpolation, splineInterpolation } from "../helper/interpolation.js";
import { equalColors } from "../color/colorHelper.js";
import { KeyCMS } from "./class_cmsKey.js";
import { Color } from "../color/class_Color.js";
//////////////////////////////////////////////
class CMS {
// use p_ as placeholder for # (# private variables are not supported by e.g. react)
constructor() {
this.p_name = "Customer Colormap";
this.p_interpolationSpace = "lab";
this.p_interpolationType = "linear"; // linear or spline or optimization
this.p_colorRefIntPre = undefined; // for spline interpolation
this.p_colorRefIntStart = undefined;
this.p_colorRefIntEnd = undefined;
this.p_colorRefIntNext = undefined; // for spline interpolation
this.p_colorInterpolated = new Color("rgb", 0, 0, 0);
this.p_colorNaN = new Color("rgb", 0, 0, 0);
this.p_colorBelow = new Color("rgb", 0, 0, 0);
this.p_colorAbove = new Color("rgb", 0, 0, 0);
this.p_keys = [];
this.p_deltaColors = []; // save onlye lab colorjson
this.p_jnd = 1; // just noticeable difference
/// Probes
this.p_probeSetArray = [];
/// work
this.p_workOn = false;
this.p_creationDate = new Date();
this.p_lastUpdateDate = new Date();
}
clear() {
this.p_keys = [];
this.p_deltaColors = [];
this.p_probeSetArray = [];
}
/**************************************************************************************************
**************************************************************************************************
************************************ CMS Specific Methods ****************************************
**************************************************************************************************
*************************************************************************************************/
/**************************************************************************************************
**************************************************************************************************
* Function : calculateColor
* Description : calc the color for the reference value _val of this CMS (with the colorspace _space)
* Ouput : Color JSON
**************************************************************************************************
*************************************************************************************************/
calculateColor(_val, _space) {
let space = checkColorSpaceNotation(_space, false);
if (!space[0]) throw new TypeError('Error (CMS) :: Function "calculateColor" :: Unknown Colorspace.');
if (!isNumber(_val)) {
return this.getNaNColor(space[1]);
}
if (_val < this.getKeyRef(0)) {
return this.getBelowColor(space[1]);
}
if (_val > this.getKeyRef(this.getKeyLength() - 1)) {
return this.getAboveColor(space[1]);
}
let index = this.p_keys.findIndex(function (key) {
return key.getRef() >= _val; // find the first key with the reference value smaller than the searched value (or equal)
});
index = index <= 0 ? 0 : index - 1; //
return this.calculateBandColor(index, _val, space[1]);
}
/**************************************************************************************************
**************************************************************************************************
* Function : calculateBandColor
* Description : calc the color for the reference value _val of the Band "bandID" of this CMS (with the colorspace _space)
* Ouput : Color JSON
**************************************************************************************************
*************************************************************************************************/
calculateBandColor(_bandID, _val, _space) {
let space = checkColorSpaceNotation(_space, false);
if (!space[0]) throw new TypeError('Error (CMS) :: Function "calculateBandColor" :: Unknown Colorspace.');
if (!isNumber(_val)) {
return this.getNaNColor(space[1]);
}
if (!checkIndex(_bandID, this.getKeyLength() - 1)) throw new TypeError('Error (CMS) :: Function "calculateBandColor" :: Incorrect bandID! The value ' + _bandID + " is not a valid index!");
// Determine
this._determineInterpolationColors(_bandID);
switch (true) {
case this.p_keys[_bandID].getRef() == _val:
//////////////////////////////////////////
///////// Hit the First Key /////////
//////////////////////////////////////////
return this._getBandKeyColor(_bandID, true, space[1]);
case this.p_keys[_bandID + 1].getRef() == _val:
//////////////////////////////////////////
///////// Hit the Second Key /////////
//////////////////////////////////////////
// check middle of triple color
return this._getBandKeyColor(_bandID, false, space[1]);
default:
// value is between _bandID and _bandID+1
let ratio = getRatio(this.p_keys[_bandID].getRef(), this.p_keys[_bandID + 1].getRef(), _val);
if (this.p_interpolationType == "linear" && (this.interpolationSpace == "de94-ds" || this.interpolationSpace == "de2000-ds") && this.p_deltaColors[_bandID].length > 0) {
/////////////////////////////////////////
///// In this case we have delta colors (Interpolation ds-de94 or ds-de2000) (=> only linear interpolation type)
/////////////////////////////////////////
let index = this.p_deltaColors[_bandID].findIndex(function (deltaColor) {
return deltaColor[1] >= _val; // find the first key with the reference value smaller than the searched value (or equal)
});
switch (index) {
case 0:
this.p_colorRefIntEnd = this.p_deltaColors[_bandID][index][0];
ratio = getRatio(this.p_keys[_bandID].getRef(), this.p_deltaColors[_bandID][index][1], _val);
break;
case this.p_deltaColors[_bandID].length - 1:
this.p_colorRefIntStart = this.p_deltaColors[_bandID][index][0];
ratio = getRatio(this.p_deltaColors[_bandID][index][1], this.p_keys[_bandID + 1].getRef(), _val);
break;
default:
this.p_colorRefIntStart = this.p_deltaColors[_bandID][index - 1][0];
this.p_colorRefIntEnd = this.p_deltaColors[_bandID][index][0];
ratio = getRatio(this.p_deltaColors[_bandID][index - 1][1], this.p_deltaColors[_bandID][index][1], _val);
break;
}
}
this.interpolate(ratio);
return this.p_colorInterpolated.getColorJSON(space[1]);
}
}
/**************************************************************************************************
**************************************************************************************************
* Function : _getBandKeyColor
* Description : if calculateColor or calculateBandColor want to get a color at a reference point of a key.
* this function determine which color need to be returned (Depends on MoT,Key Type).
* This function is "private" (=> "_functionname"), because only this class methods need access to this method.
**************************************************************************************************
*************************************************************************************************/
_getBandKeyColor(_bandID, _isStartKey, _space) {
// private method, only call able inside this class => proofed input
let keyIndex = _isStartKey ? _bandID : _bandID + 1;
switch (this.getKeyType(keyIndex)) {
case "nil":
return this.getKeyCL_JSON(keyIndex + 1, _space);
case "left":
// if not private, we would have to check if the
return this.p_keys[keyIndex].getMoT() ? this.getKeyCL_JSON(keyIndex + 1, _space) : this.getKeyCL_JSON(keyIndex, _space);
case "twin":
return this.p_keys[keyIndex].getMoT() ? this.getKeyCR_JSON(keyIndex, _space) : this.getKeyCL_JSON(keyIndex, _space);
//case "right":
// if not private we should check if _isStartKey=true, because right keys are alway at the first key of a CMS
// return this.getKeyCR_JSON(keyIndex,space);
default:
return this.getKeyCR_JSON(keyIndex, _space);
}
}
/**************************************************************************************************
**************************************************************************************************
* Function : _determineInterpolationColors
* Description : determine the colors needed for the interpolation inside of a band (between a key pair)
* Ouput : None
**************************************************************************************************
*************************************************************************************************/
_determineInterpolationColors(_bandID) {
this.p_colorRefIntPre = undefined;
this.p_colorRefIntStart = undefined;
this.p_colorRefIntEnd = undefined;
this.p_colorRefIntNext = undefined;
switch (this.getKeyType(_bandID)) {
case "nil":
case "left":
this.p_colorRefIntStart = this.getKeyCL(_bandID + 1);
this.p_colorRefIntEnd = this.getKeyCL(_bandID + 1);
break;
default:
if (this.getKeyType(_bandID) !== "right" && this.getKeyType(_bandID) !== "twin") {
this.p_colorRefIntPre = this.getKeyCR(_bandID - 1);
}
this.p_colorRefIntStart = this.getKeyCR(_bandID);
this.p_colorRefIntEnd = this.getKeyCL(_bandID + 1);
if (this.getKeyType(_bandID + 1) !== "left" && this.getKeyType(_bandID + 1) !== "twin" && _bandID + 1 !== this.getKeyLength() - 1) {
// this.getKeyLength()-1 is alwas a left key. For the push creation this don't have to be.
this.p_colorRefIntNext = this.getKeyCL(_bandID + 2);
}
}
}
/**************************************************************************************************
**************************************************************************************************
* Function : interpolate
* Description : if calculateColor or calculateBandColor want to get a color at a reference point of a key.
* this function determine which color need to be returned (Depends on MoT,Key Type).
* This function is "private" (=> "_functionname"), because only this class methods need access to this method.
* Ouput : None
**************************************************************************************************
*************************************************************************************************/
interpolate(_ratio) {
if (this.p_colorRefIntStart.equalTo(this.p_colorRefIntEnd.getColorJSON())) {
//////////////////////////////////////////
///////// Constant Band /////////
//////////////////////////////////////////
this.p_colorInterpolated.setColorJSON(this.p_colorRefIntStart.getColorJSON());
} else {
//////////////////////////////////////////
///////// Scaled Band /////////
//////////////////////////////////////////
let color_V0 = [0, 0, 0];
let color_V1 = colorToVector(this.p_colorRefIntStart.getColorJSON(this.p_interpolationSpace));
let color_V2 = colorToVector(this.p_colorRefIntEnd.getColorJSON(this.p_interpolationSpace));
let color_V3 = [0, 0, 0];
switch (this.p_interpolationType) {
case "linear":
/////////////////////////////////////////////////////////////////
///////// Scaled Band :: Linear Interpolation /////////
/////////////////////////////////////////////////////////////////
this.p_colorInterpolated.setColorJSON(vectorToColor(linearInterpolation(color_V1, color_V2, _ratio), this.p_interpolationSpace));
break;
case "spline":
if (this.p_colorRefIntPre !== undefined) color_V0 = colorToVector(this.p_colorRefIntPre.getColorJSON(this.p_interpolationSpace));
if (this.p_colorRefIntNext !== undefined) color_V3 = colorToVector(this.p_colorRefIntNext.getColorJSON(this.p_interpolationSpace));
/////////////////////////////////////////////////////////////////
///////// Scaled Band :: Spline Interpolation /////////
/////////////////////////////////////////////////////////////////
this.p_colorInterpolated.setColorJSON(vectorToColor(splineInterpolation(color_V0, color_V1, color_V2, color_V3, _ratio, 1.0), this.p_interpolationSpace));
break;
}
}
}
/**************************************************************************************************
**************************************************************************************************
* Function : searchForContinuousSections
* Description : This algorithm search key sequences that cause a continues part in the CMS
* Return : This algorithm return an array. Each element include the start index and end
* index of a continouse part
* Ouput : multi dim array (1d => number of continues section, 2d => start and end index of this continuous section)
* Unit Test : check
**************************************************************************************************
*************************************************************************************************/
searchForContinuousSections(_startKey, _endKey) {
if (_startKey == undefined) _startKey = 0;
if (_endKey == undefined) _endKey = this.getKeyLength() - 1;
if (!checkIndex(_startKey, this.getKeyLength())) throw new TypeError('Error (CMS) :: Function "calculateBandColor" :: Incorrect _startKey! The value ' + _startKey + " is not a valid index!");
if (!checkIndex(_endKey, this.getKeyLength())) throw new TypeError('Error (CMS) :: Function "calculateBandColor" :: Incorrect bandID! The value ' + _endKey + " is not a valid index!");
let continuousSections = [];
let beforeConstant = false;
//let startKey = 0;
if (this.getKeyType(_startKey) === "twin" || this.getKeyType(_startKey) === "left") beforeConstant = true;
for (let i = _startKey; i <= _endKey; i++) {
switch (this.getKeyType(i)) {
case "nil":
beforeConstant = true;
break;
case "left":
if (!beforeConstant) {
let tmpStart = _startKey;
let tmpEnd = i;
continuousSections.push([tmpStart, tmpEnd]);
}
_startKey = i;
beforeConstant = true;
break;
case "twin":
if (!beforeConstant) {
let tmpStart = _startKey;
let tmpEnd = i;
continuousSections.push([tmpStart, tmpEnd]);
}
_startKey = i;
beforeConstant = false;
break;
default:
if (beforeConstant) {
_startKey = i;
beforeConstant = false;
} else {
if (i == _endKey) {
let tmpStart = _startKey;
let tmpEnd = i;
continuousSections.push([tmpStart, tmpEnd]);
}
}
}
}
return continuousSections;
}
/**************************************************************************************************
**************************************************************************************************
* Function : calcReverse
* Description : This algorithm relocade the Key Colors so this CMS will be a reverse version of its previouse state
* Ouput : None
* Unit Test : check
**************************************************************************************************
*************************************************************************************************/
calcReverse() {
if (this.getKeyLength() < 2) return;
this.p_workOn = true;
let tmpkeys = [];
let startPos = this.getKeyRef(0);
let endPos = this.getKeyRef(this.getKeyLength() - 1);
for (let i = 0; i < this.getKeyLength(); i++) {
if ((this.getKeyType(i) === "nil" || this.getKeyType(i) === "left") && i != this.getKeyLength() - 1) {
this.setKeyCR(i, this.getKeyCL_JSON(i + 1, this.p_interpolationSpace));
this.setKeyCL(i + 1, undefined);
}
let newPos = startPos + (endPos - this.getKeyRef(i));
let tmpColor = this.getKeyCL_JSON(i, this.p_interpolationSpace);
this.setKeyCL(i, this.getKeyCR_JSON(i, this.p_interpolationSpace));
this.setKeyCR(i, tmpColor);
tmpkeys.splice(0, 0, this.getKeyClone(i));
this.setKeyRef(0, newPos);
}
this.p_keys = tmpkeys;
this.p_workOn = false;
this._calcDeltaColors();
}
/**************************************************************************************************
**************************************************************************************************
* Function : setAutoRange
* Description : This algorithm rescale the CMS to a range from _newStart to _newEnd
* Ouput : None
* Unit Test : check
**************************************************************************************************
*************************************************************************************************/
setAutoRange(_newStart, _newEnd) {
let currentStart = this.getKeyRef(0);
let currentdistance = this.getKeyRef(this.getKeyLength() - 1) - currentStart;
let newDistance = _newEnd - _newStart;
for (let i = 0; i < this.getKeyLength(); i++) {
let ratio = (this.getKeyRef(i) - currentStart) / currentdistance;
let newPos = _newStart + ratio * newDistance;
this.setKeyRef(i, newPos);
}
this._calcDeltaColors();
}
/**************************************************************************************************
**************************************************************************************************
* Function : equalKeyIntervals
* Description : This algorithm set equal distances between all keys
* Ouput : None
* Unit Test : check
**************************************************************************************************
*************************************************************************************************/
equalKeyIntervals() {
if (this.getKeyLength() > 2) {
let equalDis = Math.abs(this.getKeyRef(this.getKeyLength() - 1) - this.getKeyRef(0)) / (this.getKeyLength() - 1);
for (let i = 1; i < this.getKeyLength() - 1; i++) {
this.setKeyRef(i, this.getKeyRef(0) + i * equalDis);
}
}
this._calcDeltaColors();
}
/**************************************************************************************************
**************************************************************************************************
* Function : getCMSJSON
* Description : Create a JSON file with the all the informaiton of this CMS (Not the Paraview Colormap JSON format)
* Ouput : CMS JSON
* Unit Test : check
**************************************************************************************************
*************************************************************************************************/
getCMSJSON() {
let cmsJSON = {};
cmsJSON.isCMS = true; // for import methods to check if the json has the CCC-Tool format
cmsJSON.cmsType = "ccc"; // for import methods to check if the json has the CCC-Tool format
cmsJSON.name = this.p_name;
cmsJSON.interpolationSpace = this.p_interpolationSpace;
cmsJSON.interpolationType = this.p_interpolationType; // linear or spline or optimization
cmsJSON.colorNaN = this.getNaNColor(this.p_interpolationSpace);
cmsJSON.colorBelow = this.getBelowColor(this.p_interpolationSpace);
cmsJSON.colorAbove = this.getAboveColor(this.p_interpolationSpace);
cmsJSON.keys = [];
cmsJSON.jnd = this.p_jnd;
cmsJSON.creationDate = this.p_creationDate.getTime();
cmsJSON.lastUpdateDate = this.p_lastUpdateDate.getTime();
for (let i = 0; i < this.getKeyLength(); i++) {
cmsJSON.keys.push(this.p_keys[i].getKeyJSON());
}
// probes
return cmsJSON;
}
/**************************************************************************************************
**************************************************************************************************
* Function : setByJSON
* Description : set all parameters of this CMS by a cmsJSON file.
* Ouput : None
* Unit Test : check
**************************************************************************************************
*************************************************************************************************/
setByJSON(_cmsJSON) {
if (!isCMSJSON(_cmsJSON)) throw new TypeError('Error (CMS) :: Function "setByJSON" :: Incorrect cmsJSON file!');
this.clear();
this.p_name = _cmsJSON.name;
if (typeof _cmsJSON.interpolationType === "string") if (_cmsJSON.interpolationType === "linear" || _cmsJSON.interpolationType === "spline") this.p_interpolationType = _cmsJSON.interpolationType;
let space = checkColorSpaceNotation(_cmsJSON.interpolationSpace);
if (space[0]) this.p_interpolationSpace = space[1];
if ("colorNaN" in _cmsJSON) this.setNaNColor(_cmsJSON.colorNaN);
if ("colorBelow" in _cmsJSON) this.setBelowColor(_cmsJSON.colorBelow);
if ("colorAbove" in _cmsJSON) this.setAboveColor(_cmsJSON.colorAbove);
if ("creationDate" in _cmsJSON) this.p_creationDate = new Date(parseInt(_cmsJSON.creationDate));
if ("lastUpdateDate" in _cmsJSON) this.p_lastUpdateDate = new Date(parseInt(_cmsJSON.lastUpdateDate));
/// Keys ///
for (let index = 0; index < _cmsJSON.keys.length; index++) {
this._pushKey(_keyJsonToInstance(_cmsJSON.keys[index]));
}
if (isNumber(_cmsJSON.jnd)) this.p_jnd = _cmsJSON.jnd;
}
/**************************************************************************************************
**************************************************************************************************
* Function : insertCMS
* Description : insert the keys of an other CMS into this key array.
* Ouput : None
* Unit Test : check
**************************************************************************************************
*************************************************************************************************/
insertCMS(_cmsJSON, _index) {
if (!isCMSJSON(_cmsJSON)) throw new TypeError('Error (CMS) :: Function "insertCMS" :: Incorrect cmsJSON file!');
/// if the current CMS is empty we add all the cmsJSON information to this
if (this.getKeyLength() == 0) {
for (let index = 0; index < _cmsJSON.keys.length; index++) {
this._pushKey(_keyJsonToInstance(_cmsJSON.keys[index]));
}
return;
}
if (!checkIndex(_index, this.getKeyLength())) throw new TypeError('Error (CMS) :: Function "insertCMS" :: Incorrect index!');
this.p_workOn = true;
let cmsDis = _cmsJSON.keys[_cmsJSON.keys.length - 1].ref - _cmsJSON.keys[0].ref;
let startPos = undefined;
let dist = undefined;
switch (_index) {
case this.getKeyLength() - 1:
// case scaled band
let tmpVal = this.getKeyRef(_index);
dist = Math.abs(tmpVal - this.getKeyRef(_index - 1)) * 0.5;
startPos = tmpVal - dist;
this.setKeyRef(_index, startPos);
this.setKeyCR(_index, _cmsJSON.keys[0].cR); // right color of first key
for (let i = 1; i < _cmsJSON.keys.length; i++) {
let ratio = (_cmsJSON.keys[i].ref - _cmsJSON.keys[i - 1].ref) / cmsDis;
startPos = startPos + dist * ratio;
if (i == _cmsJSON.keys.length - 1) this._pushKey(new KeyCMS(_cmsJSON.keys[i].cL, _cmsJSON.keys[i].cR, tmpVal, _cmsJSON.keys[i].isBur, _cmsJSON.keys[i].mot));
else this._pushKey(new KeyCMS(_cmsJSON.keys[i].cL, _cmsJSON.keys[i].cR, startPos, _cmsJSON.keys[i].isBur, _cmsJSON.keys[i].mot));
}
break;
default:
startPos = this.getKeyRef(_index);
dist = Math.abs(this.getKeyRef(_index + 1) - startPos) * 0.5;
let endPos = startPos + dist;
this.setKeyRef(_index, endPos);
let oldColor = this.getKeyCL_JSON(_index, "lab");
this.setKeyCL(_index, _cmsJSON.keys[_cmsJSON.keys.length - 1].cL); // left key color of the last key of the package
this.setBur(_index, true);
for (let i = _cmsJSON.keys.length - 2; i >= 0; i--) {
let ratio = (_cmsJSON.keys[i + 1].ref - _cmsJSON.keys[i].ref) / cmsDis;
endPos = endPos - dist * ratio;
this._insertKey(_index, new KeyCMS(_cmsJSON.keys[i].cL, _cmsJSON.keys[i].cR, endPos, _cmsJSON.keys[i].isBur, _cmsJSON.keys[i].mot));
}
this.setKeyCL(_index, oldColor);
this.setBur(_index, true);
}
this.p_workOn = false;
}
/**************************************************************************************************
**************************************************************************************************
************************************ CMS Specific Methods ****************************************
**************************************************************************************************
*************************************************************************************************/
_updateSurroundingDeltaColors() {}
_calcDeltaColors() {}
_updateBandDeltaColors(_bandID) {}
/**************************************************************************************************
**************************************************************************************************
***************************************** Manage Keys ******************************************
**************************************************************************************************
*************************************************************************************************/
deleteKey(_index) {
if (!checkIndex(_index, this.getKeyLength())) return;
this.p_keys.splice(_index, 1);
this.p_deltaColors.splice(_index, 1);
if (_index != 0 && _index != this.getKeyLength() - 1) this._updateBandDeltaColors(_index - 1);
}
getKeyLength() {
return this.p_keys.length;
}
// Unit Test : check
getKey(_index) {
if (!checkIndex(_index, this.getKeyLength())) throw new TypeError('Error (CMS) :: Function "getKey" :: Incorrect index!');
return this.p_keys[_index];
}
// Unit Test : check
getKeyClone(_index) {
if (!checkIndex(_index, this.getKeyLength())) throw new TypeError('Error (CMS) :: Function "getKeyClone" :: Incorrect index!');
return _keyJsonToInstance(this.p_keys[_index].getKeyJSON());
}
// Unit Test : check
setKeyCL(_index, _cL) {
if (!checkIndex(_index, this.getKeyLength())) throw new TypeError('Error (CMS) :: Function "setKeyCL" :: Incorrect index!');
if (this.p_workOn) {
this.p_keys[_index].setCL(_cL);
return;
}
if (!checkIndex(_index, this.getKeyLength())) throw new TypeError('Error (CMS) :: Function "setKeyCL" :: Incorrect index!');
switch (_index) {
case 0:
if (_cL !== undefined) throw new TypeError('Error (CMS) :: Function "setKeyCL" :: The left color of the first key need to be undefined!');
this.p_keys[_index].setCL(_cL);
break;
default:
if (_cL === undefined) throw new TypeError('Error (CMS) :: Function "setKeyCL" :: Only the left color of the first key is allowed to be undefined!');
this.p_keys[_index].setCL(_cL);
this._updateBandDeltaColors(_index - 1);
/*if (this.interpolationType === "spline") {
if (_index - 2 >= 0) {
if (this.p_keys[_index - 1].getKeyType() === "dual") {
this._updateBandDeltaColors(_index - 2);
if (_index - 3 >= 0) {
if (this.p_keys[_index - 2].getKeyType() === "dual") this._updateBandDeltaColors(index - 3);
}
}
}
}*/
break;
}
}
// Unit Test : check
setKeyCR(_index, _cR) {
if (!checkIndex(_index, this.getKeyLength())) throw new TypeError('Error (CMS) :: Function "setKeyCR" :: Incorrect index!');
if (this.p_workOn) {
this.p_keys[_index].setCR(_cR);
return;
}
switch (_index) {
case this.getKeyLength() - 1:
if (_cR !== undefined) throw new TypeError('Error (CMS) :: Function "setKeyCR" :: The right color of the last key need to be undefined!');
this.p_keys[_index].setCR(_cR);
break;
default:
this.p_keys[_index].setCR(_cR);
this._updateBandDeltaColors(_index);
/*if (this.interpolationType === "spline") {
if (index + 1 < this.getKeyLength()) {
if (this.p_keys[index + 1].getKeyType() === "dual") {
this._updateBandDeltaColors(index + 1);
if (index + 2 < this.getKeyLength()) {
if (this.p_keys[index + 2].getKeyType() === "dual") this._updateBandDeltaColors(index + 2);
}
}
}
}*/
break;
}
}
// Unit Test : check
getKeyCL(_index) {
if (!checkIndex(_index, this.getKeyLength())) throw new TypeError('Error (CMS) :: Function "getKeyCL" :: Incorrect _index! The value ' + _index + " is not a valid index!");
return this.p_keys[_index].getCL();
}
// Unit Test : check
getKeyCR(_index) {
if (!checkIndex(_index, this.getKeyLength())) throw new TypeError('Error (CMS) :: Function "getKeyCR" :: Incorrect _index! The value ' + _index + " is not a valid index!");
return this.p_keys[_index].getCR();
}
getKeyCL_JSON(_index, _space) {
if (!checkIndex(_index, this.getKeyLength())) throw new TypeError('Error (CMS) :: Function "getKeyCL_JSON" :: Incorrect _index! The value ' + _index + " is not a valid index!");
return this.p_keys[_index].getCL_JSON(_space);
}
getKeyCR_JSON(_index, _space) {
if (!checkIndex(_index, this.getKeyLength())) throw new TypeError('Error (CMS) :: Function "getKeyCR_JSON" :: Incorrect _index! The value ' + _index + " is not a valid index!");
return this.p_keys[_index].getCR_JSON(_space);
}
getKeyType(_index) {
if (!checkIndex(_index, this.getKeyLength())) throw new TypeError('Error (CMS) :: Function "getKeyType" :: Incorrect _index! The value ' + _index + " is not a valid index!");
return this.p_keys[_index].getType();
}
getMoT(_index) {
if (!checkIndex(_index, this.getKeyLength())) throw new TypeError('Error (CMS) :: Function "getMoT" :: Incorrect _index! The value ' + _index + " is not a valid index!");
return this.p_keys[_index].getMoT();
}
setMoT(_index, _mot) {
if (!checkIndex(_index, this.getKeyLength())) throw new TypeError('Error (CMS) :: Function "setMoT" :: Incorrect _index! The value ' + _index + " is not a valid index!");
this.p_keys[_index].setMoT(_mot);
}
// Unit Test : check
addKey(_key) {
if (isCMSKey(_key)) _key = _keyJsonToInstance(_key);
if (!(_key instanceof KeyCMS)) throw new TypeError('Error (CMS) :: Function "addKey" :: Incorrect parameter! The key is not a instance of the class KeyCMS!');
let index = undefined;
switch (this.getKeyLength()) {
case 0:
index = 0;
break;
case 1:
// star key is already set
if (_key.ref <= this.p_keys[0].getRef()) throw new TypeError('Error (CMS) :: Function "addKey" :: The reference value of added key is equal to the reference value of an existing key!');
index = 1;
break;
default:
// find position
let ref = _key.getRef();
switch (true) {
case ref < this.getKeyRef(0):
index = 0;
break;
case ref > this.getKeyRef(this.getKeyLength() - 1):
index = this.getKeyLength();
break;
default:
for (let i = 1; i < this.getKeyLength(); i++) {
if (ref == this.getKeyRef(i - 1) || ref == this.getKeyRef(i)) throw new TypeError('Error (CMS) :: Function "addKey" :: The reference value of added key is equal to the reference value of an existing key!');
if (ref > this.getKeyRef(i - 1) && ref < this.getKeyRef(i)) {
index = i;
break;
}
}
break;
}
break;
}
if (index == undefined) throw new TypeError('Error (CMS) :: Function "addKey" :: The function was not able to find a index for this key!');
// We need to check if the key type is correct for the determined index
switch (index) {
case 0:
if (_key.getType() !== "nil" && _key.getType() !== "right") throw new TypeError('Error (CMS) :: Function "addKey" :: Incompatible index and key type! For the determined index=0, a "nil" or "right" key type is necessary!');
// We cannot allow a new start key if another already exist
if (this.getKeyLength() != 0) throw new TypeError('Error (CMS) :: Function "addKey" :: There is already a start key. Key types "nil" or "right" can not be added to the CMS.!');
break;
case this.getKeyLength():
if (_key.getType() !== "left") throw new TypeError('Error (CMS) :: Function "addKey" :: Incompatible index and key type! For the determined last index (' + this.getKeyLength() + '), a "left" key type is necessary!');
break;
default:
if (_key.getType() === "nil" && _key.getType() === "right") throw new TypeError('Error (CMS) :: Function "addKey" :: Incompatible index and key type! For the determined index, a "nil" or "right" key type is not allowed!');
}
this._insertKey(index, _key);
}
_insertKey(_index, _key) {
//if (!checkIndex(_index, this.getKeyLength())) throw new TypeError('Error (CMS) :: Function "_insertKey" :: Incorrect _index! The value ' + _index + " is not a valid index!");
//if (isCMSKey(_key)) _key = _keyJsonToInstance(_key);
//if (!(_key instanceof KeyCMS)) throw new TypeError('Error (CMS) :: Function "_insertKey" :: Incorrect parameter! The key is not a instance of the class KeyCMS!');
this.p_keys.splice(_index, 0, _key);
if (this.getKeyLength() > 1) {
this.p_deltaColors.splice(_index, 0, []);
this._updateSurroundingDeltaColors(_index);
}
}
_pushKey(_key) {
//if (isCMSKey(_key)) _key = _keyJsonToInstance(_key);
//if (!(_key instanceof KeyCMS)) throw new TypeError('Error (CMS) :: Function "_pushKey" :: Incorrect parameter! The key is not a instance of the class KeyCMS!');
/*if (this.getKeyLength() != 0) {
if (_key.getRef() <= this.getKeyRef(this.getKeyLength() - 1)) throw new TypeError('Error (CMS) :: Function "_pushKey" :: Incorrect parameter! The reference value of pushed key need to be larger than the reference value of the previouse key!');
}*/
this.p_keys.push(_key);
if (this.getKeyLength() > 1) this.p_deltaColors.push([]);
}
getBur(_index) {
if (!checkIndex(_index, this.getKeyLength())) throw new TypeError('Error (CMS) :: Function "getBur" :: Incorrect _index! The value ' + _index + " is not a valid index!");
return this.p_keys[_index].getBur();
}
setBur(_index, _isBur) {
if (!checkIndex(_index, this.getKeyLength())) throw new TypeError('Error (CMS) :: Function "setBur" :: Incorrect _index! The value ' + _index + " is not a valid index!");
this.p_keys[_index].setBur(_isBur);
}
deleteBand(_index) {
let tmpRightColor = this.getKeyCR_JSON(_index + 1, "lab");
this.p_keys[_index].setKeyCR(tmpRightColor);
this.deleteKey(_index + 1);
}
/**************************************************************************************************
**************************************************************************************************
************************************ CMS Drawing Methods *****************************************
**************************************************************************************************
*************************************************************************************************/
/**************************************************************************************************
**************************************************************************************************
* Function : drawHorizontal
* Description : draw this cms horizontal in an imgageData object.
* Ouput : ImageData
**************************************************************************************************
*************************************************************************************************/
drawCMS(_width, _height, _drawVertical, _colorBlind, _alpha) {
if (!Number.isInteger(_width) || !Number.isInteger(_height)) return new ImageData(1, 1);
if (_width < 2 || _height < 2) return new ImageData(1, 1);
let cmsImg = new ImageData(_width, _height);
let disRef = this.getRefRange();
if (_drawVertical) {
for (let i = 0; i < this.getKeyLength() - 1; i++) {
let linearKey_yPos = Math.round(((this.getKeyRef(i) - this.getKeyRef(0)) / disRef) * _height);
let elementheight = Math.round(((this.getKeyRef(i + 1) - this.getKeyRef(i)) / disRef) * _height) + 1;
this.drawBand(cmsImg, true, 0, _height - linearKey_yPos, i, _width, elementheight, _colorBlind, _alpha);
}
} else {
for (let i = 0; i < this.getKeyLength() - 1; i++) {
let linearKey_xPos = Math.round(((this.getKeyRef(i) - this.getKeyRef(0)) / disRef) * _width);
let elementwidth = Math.round(((this.getKeyRef(i + 1) - this.getKeyRef(i)) / disRef) * _width) + 1; // plus 1 because sometimes a pixel column is empty
this.drawBand(cmsImg, false, linearKey_xPos, 0, i, elementwidth, _height, _colorBlind, _alpha);
}
}
return cmsImg;
}
drawBand(_imgRef, _drawVertical, _xStart, _yStart, _bandID, _bandWidth, _bandHeight, _doColorBlind, _alpha) {
_xStart = Math.round(_xStart);
_yStart = Math.round(_yStart);
_bandID = Math.round(_bandID);
_bandWidth = Math.round(_bandWidth);
_bandHeight = Math.round(_bandHeight);
_alpha = isNumber(isNumber) ? _alpha : 1;
_alpha = _alpha < 0 ? 0 : _alpha;
_alpha = _alpha > 1 ? 1 : _alpha;
_alpha *= 255;
this._determineInterpolationColors(_bandID);
//this.interpolate(ratio);
//return this.p_colorInterpolated.getColorJSON(space[1]);
let tmpColorJSON = undefined;
let tmpSpace = _doColorBlind ? "rgb_cb" : "rgb";
if (_drawVertical) {
for (let y = _yStart; y >= _yStart - _bandHeight; y--) {
if (_yStart - y < 0) break;
let tmpRatio = (_yStart - y) / _bandHeight;
this.interpolate(tmpRatio);
tmpColorJSON = this.p_colorInterpolated.getColorJSON(tmpSpace);
for (let x = _xStart; x < _xStart + _bandWidth; x++) {
let index = (x + (_yStart - (_yStart - y)) * _imgRef.width) * 4;
_imgRef.data[index + 0] = Math.round(tmpColorJSON.c1 * 255); // r
_imgRef.data[index + 1] = Math.round(tmpColorJSON.c2 * 255); // g
_imgRef.data[index + 2] = Math.round(tmpColorJSON.c3 * 255); // b
_imgRef.data[index + 3] = _alpha; //a
}
}
} else {
for (let x = _xStart; x < _xStart + _bandWidth; x++) {
if (x >= _imgRef.width) continue;
let tmpRatio = (x - _xStart) / _bandWidth;
this.interpolate(tmpRatio);
tmpColorJSON = this.p_colorInterpolated.getColorJSON(tmpSpace);
for (let y = _yStart; y < _yStart + _bandHeight; y++) {
let index = (x + y * _imgRef.width) * 4;
//let index = ((xStart+x) + y * _imgRef.width) * 4;
_imgRef.data[index + 0] = Math.round(tmpColorJSON.c1 * 255); // r
_imgRef.data[index + 1] = Math.round(tmpColorJSON.c2 * 255); // g
_imgRef.data[index + 2] = Math.round(tmpColorJSON.c3 * 255); // b
_imgRef.data[index + 3] = _alpha; //a
}
}
}
}
/**************************************************************************************************
**************************************************************************************************
***************************************** Parser ******************************************
**************************************************************************************************
*************************************************************************************************/
parser_JSON(_cmsJSON) {
let name = "Loaded Colormap";
this.clear();
switch (true) {
case "isCMS" in _cmsJSON:
this.setByJSON(_cmsJSON);
case "colormaps" in _cmsJSON:
// Mabye JSON Colormoves Format
break;
default:
if (_cmsJSON.hasOwnProperty("Name")) name = _cmsJSON[0].Name;
this.p_creationDate = new Date(0);
this.setLastUpdateDate(); // the paraview json format has no last update information.
let cSpace = ""; //_cmsJSON[0].ColorSpace;
let pointName = "";
// In the early age of ccc-tool we also offered a json download with HSV Lab or DIN99 points. The original paraview json only has RGBPoints
if (_cmsJSON[0].hasOwnProperty("RGBPoints")) {
cSpace = "rgb";
pointName = "RGBPoints";
}
if (_cmsJSON[0].hasOwnProperty("HSVPoints")) {
cSpace = "hsv";
pointName = "HSVPoints";
}
if (_cmsJSON[0].hasOwnProperty("LabPoints")) {
cSpace = "lab";
pointName = "LabPoints";
}
if (_cmsJSON[0].hasOwnProperty("DIN99Points")) {
cSpace = "din99";
pointName = "DIN99Points";
}
if (pointName === "") {
console.error('Error (cmsParser) :: Function "cmsParser_JSON" :: Missing attribute "RGBPoints"!');
break;
}
if (_cmsJSON[0][pointName].length == 0) return;
let val1_RatioFactor = 1;
let val2_RatioFactor = 1;
let val3_RatioFactor = 1;
let hasKeyInfo = false;
let hasMoTInfo = false;
if (_cmsJSON[0].hasOwnProperty("isCMS")) {
hasKeyInfo = true;
}
if (_cmsJSON[0].hasOwnProperty("isMoT")) {
hasMoTInfo = true;
}
let hasNaNColor = false;
if (_cmsJSON[0].hasOwnProperty("NanColor")) {
hasNaNColor = true;
}
let hasAboveColor = false;
if (_cmsJSON[0].hasOwnProperty("AboveColor")) {
hasAboveColor = true;
}
let hasBelowColor = false;
if (_cmsJSON[0].hasOwnProperty("BelowColor")) {
hasBelowColor = true;
}
if (_cmsJSON[0].hasOwnProperty("Name")) {
this.setCMSName(_cmsJSON[0].Name);
}
if (cSpace == undefined) return;
////////////////////////////////////////////
if (cSpace === "rgb" || cSpace === "RGB" || cSpace === "Rgb") {
for (let i = 0; i < _cmsJSON[0][pointName].length / 4; i++) {
let r = parseFloat(_cmsJSON[0][pointName][i * 4 + 1]);
let g = parseFloat(_cmsJSON[0][pointName][i * 4 + 2]);
let b = parseFloat(_cmsJSON[0][pointName][i * 4 + 3]);
if (r > 1.0 || g > 1.0 || b > 1.0) {
val1_RatioFactor = 255;
val2_RatioFactor = 255;
val3_RatioFactor = 255;
break;
}
}
} else {
break;
}
let keys = [];
let lastIndex = _cmsJSON[0][pointName].length / 4 - 1;
for (let i = 0; i < _cmsJSON[0][pointName].length / 4; i++) {
let x = parseFloat(_cmsJSON[0].RGBPoints[i * 4]);
let val1 = parseFloat(_cmsJSON[0].RGBPoints[i * 4 + 1]) / val1_RatioFactor;
let val2 = parseFloat(_cmsJSON[0].RGBPoints[i * 4 + 2]) / val2_RatioFactor;
let val3 = parseFloat(_cmsJSON[0].RGBPoints[i * 4 + 3]) / val3_RatioFactor;
let tmpColor = { space: cSpace, c1: val1, c2: val2, c3: val3 };
let tmpColor2 = undefined;
switch (i) {
case 0:
let val1_Next = parseFloat(_cmsJSON[0].RGBPoints[(i + 1) * 4 + 1]) / val1_RatioFactor;
let val2_Next = parseFloat(_cmsJSON[0].RGBPoints[(i + 1) * 4 + 2]) / val2_RatioFactor;
let val3_Next = parseFloat(_cmsJSON[0].RGBPoints[(i + 1) * 4 + 3]) / val3_RatioFactor;
tmpColor2 = { space: cSpace, c1: val1_Next, c2: val2_Next, c3: val3_Next };
if (equalColors(tmpColor, tmpColor2)) {
// nil key
keys.push(new KeyCMS(undefined, undefined, x, true, false));
} else {
// right key
keys.push(new KeyCMS(undefined, tmpColor, x, true, false));
}
break;
case lastIndex:
// right key
keys.push(new KeyCMS(tmpColor, undefined, x, true, false));
break;
default:
if (hasKeyInfo) {
if (_cmsJSON[0].isCMS[i] == false) {
continue; // continue if cms attribute exist and if it is false
}
}
let x_Previous = parseFloat(_cmsJSON[0].RGBPoints[(i - 1) * 4]);
let x_Next = parseFloat(_cmsJSON[0].RGBPoints[(i + 1) * 4]);
let val1_N = parseFloat(_cmsJSON[0].RGBPoints[(i + 1) * 4 + 1]) / val1_RatioFactor;
let val2_N = parseFloat(_cmsJSON[0].RGBPoints[(i + 1) * 4 + 2]) / val2_RatioFactor;
let val3_N = parseFloat(_cmsJSON[0].RGBPoints[(i + 1) * 4 + 3]) / val3_RatioFactor;
tmpColor2 = { space: cSpace, c1: val1_N, c2: val2_N, c3: val3_N };
if (x_Previous == x) {
let val1_Prev = parseFloat(_cmsJSON[0].RGBPoints[(i - 1) * 4 + 1]) / val1_RatioFactor;
let val2_Prev = parseFloat(_cmsJSON[0].RGBPoints[(i - 1) * 4 + 2]) / val2_RatioFactor;
let val3_Prev = parseFloat(_cmsJSON[0].RGBPoints[(i - 1) * 4 + 3]) / val3_RatioFactor;
let tmpColor_Prev = { space: cSpace, c1: val1_Prev, c2: val2_Prev, c3: val3_Prev };
if (equalColors(tmpColor, tmpColor2)) {
// left key
let newKey = new KeyCMS(tmpColor_Prev, undefined, x, true, false);
if (hasMoTInfo) {
if (_cmsJSON[0].isMoT[i] == true) newKey.setMoT(true); // if right key color isMoT (left is default)
}
keys.push(newKey);
} else {
// twin key
let newKey = new KeyCMS(tmpColor_Prev, tmpColor, x, true, false);
if (hasMoTInfo) {
if (_cmsJSON[0].isMoT[i] == true) newKey.setMoT(true); // if right key color isMoT (left is default)
}
keys.push(newKey);
}
} else {
if (x != x_Next) {
// dual key
keys.push(new KeyCMS(tmpColor, tmpColor, x, false, false));
}
}
} //switch
}
if (keys.length >= 2) {
this.addKey(keys[0]);
this.addKey(keys[keys.length - 1]);
for (let i = 1; i < keys.length - 1; i++) this.addKey(keys[i]);
}
if (hasNaNColor) {
let val1 = parseFloat(_cmsJSON[0].NanColor[0]) / val1_RatioFactor;
let val2 = parseFloat(_cmsJSON[0].NanColor[1]) / val2_RatioFactor;
let val3 = parseFloat(_cmsJSON[0].NanColor[2]) / val3_RatioFactor;
this.setNaNColor({ space: cSpace, c1: val1, c2: val2, c3: val3 });
}
if (hasAboveColor) {
let val1 = parseFloat(_cmsJSON[0].AboveColor[0]) / val1_RatioFactor;
let val2 = parseFloat(_cmsJSON[0].AboveColor[1]) / val2_RatioFactor;
let val3 = parseFloat(_cmsJSON[0].AboveColor[2]) / val3_RatioFactor;
this.setAboveColor({ space: cSpace, c1: val1, c2: val2, c3: val3 });
}
if (hasBelowColor) {
let val1 = parseFloat(_cmsJSON[0].BelowColor[0]) / val1_RatioFactor;
let val2 = parseFloat(_cmsJSON[0].BelowColor[1]) / val2_RatioFactor;
let val3 = parseFloat(_cmsJSON[0].BelowColor[2]) / val3_RatioFactor;
this.setBelowColor({ space: cSpace, c1: val1, c2: val2, c3: val3 });
}
}
}
parser_XML(_xmlCMS) {
this.clear();
this.p_creationDate = new Date(0);
this.setLastUpdateDate();
let pointObject = _xmlCMS.getElementsByTagName("Point");
let cSpace = checkXMLColorspace(pointObject);
let isrgb255 = false;
let val1Name, val2Name, val3Name;
switch (cSpace) {
case "RGB":
case "rgb":
case "Rgb":
for (let i = 0; i < pointObject.length; i++) {
let r = parseFloat(pointObject[i].getAttribute("r"));
let g = parseFloat(pointObject[i].getAttribute("g"));
let b = parseFloat(pointObject[i].getAttribute("b"));
if (r > 1.0 || g > 1.0 || b > 1.0) {
isrgb255 = true;
break;
}
}
val1Name = "r";
val