Fix/apply color mods (#4011)

* Fix rgb -> hsl -> rgb converting

* Remove unusual round

* Add hueMod

* Fix hueOff mod

* Fix round rgb if saturation equals 0

* Remove var

* Add check to eliminate unnecessary recalculations

* Remove check for gray color in satOff mod

* Fix round crgb color
This commit is contained in:
Vladimir Privezenov
2023-12-04 10:48:08 +03:00
committed by GitHub
parent bfaaf16d69
commit 5ae185beee
6 changed files with 437 additions and 61 deletions

View File

@ -32,6 +32,7 @@ jobs:
npm install --prefix build
grunt --gruntfile build/Gruntfile.js develop
node-qunit-puppeteer tests/common/api/api.html
node-qunit-puppeteer tests/common/color-mods/color-mods.html
node-qunit-puppeteer tests/cell/shortcuts/shortcuts.html
node-qunit-puppeteer tests/cell/spreadsheet-calculation/FormulaTests.html
node-qunit-puppeteer tests/cell/spreadsheet-calculation/PivotTests.html

View File

@ -32,6 +32,7 @@ jobs:
npm install --prefix build
grunt --gruntfile build/Gruntfile.js develop
node-qunit-puppeteer tests/common/api/api.html
node-qunit-puppeteer tests/common/color-mods/color-mods.html
node-qunit-puppeteer tests/cell/shortcuts/shortcuts.html
node-qunit-puppeteer tests/cell/spreadsheet-calculation/FormulaTests.html
node-qunit-puppeteer tests/cell/spreadsheet-calculation/PivotTests.html

View File

@ -1426,19 +1426,19 @@
if (H > 1.0) H -= 1.0;
}
H = ((H * max_hls) >> 0) & 0xFF;
H = H * max_hls;
if (H < 0)
H = 0;
if (H > 255)
H = 255;
S = ((S * max_hls) >> 0) & 0xFF;
S = S * max_hls;
if (S < 0)
S = 0;
if (S > 255)
S = 255;
L = ((L * max_hls) >> 0) & 0xFF;
L = L * max_hls;
if (L < 0)
L = 0;
if (L > 255)
@ -1450,9 +1450,10 @@
};
CColorModifiers.prototype.HSL2RGB = function (HSL, RGB) {
if (HSL.S == 0) {
RGB.R = HSL.L;
RGB.G = HSL.L;
RGB.B = HSL.L;
const clampL = AscFormat.ClampColor(HSL.L);
RGB.R = clampL;
RGB.G = clampL;
RGB.B = clampL;
} else {
var H = HSL.H / max_hls;
var S = HSL.S / max_hls;
@ -1465,28 +1466,13 @@
var v1 = 2.0 * L - v2;
var R = (255 * this.Hue_2_RGB(v1, v2, H + cd13)) >> 0;
var G = (255 * this.Hue_2_RGB(v1, v2, H)) >> 0;
var B = (255 * this.Hue_2_RGB(v1, v2, H - cd13)) >> 0;
var R = (255 * this.Hue_2_RGB(v1, v2, H + cd13));
var G = (255 * this.Hue_2_RGB(v1, v2, H));
var B = (255 * this.Hue_2_RGB(v1, v2, H - cd13));
if (R < 0)
R = 0;
if (R > 255)
R = 255;
if (G < 0)
G = 0;
if (G > 255)
G = 255;
if (B < 0)
B = 0;
if (B > 255)
B = 255;
RGB.R = R;
RGB.G = G;
RGB.B = B;
RGB.R = AscFormat.ClampColor(R);
RGB.G = AscFormat.ClampColor(G);
RGB.B = AscFormat.ClampColor(B);
}
};
CColorModifiers.prototype.Hue_2_RGB = function (v1, v2, vH) {
@ -1528,23 +1514,22 @@
//RGBA.B = (this.lclCrgbCompToRgbComp(this.lclGamma(RGBA.B, INC_GAMMA)) + 0.5) >> 0;
if (this.isUsePow) {
RGBA.R = (Math.pow(RGBA.R / 100000, INC_GAMMA) * 255 + 0.5) >> 0;
RGBA.G = (Math.pow(RGBA.G / 100000, INC_GAMMA) * 255 + 0.5) >> 0;
RGBA.B = (Math.pow(RGBA.B / 100000, INC_GAMMA) * 255 + 0.5) >> 0;
} else {
RGBA.R = AscFormat.ClampColor(RGBA.R);
RGBA.G = AscFormat.ClampColor(RGBA.G);
RGBA.B = AscFormat.ClampColor(RGBA.B);
RGBA.R = Math.pow(RGBA.R / 100000, INC_GAMMA) * 255;
RGBA.G = Math.pow(RGBA.G / 100000, INC_GAMMA) * 255;
RGBA.B = Math.pow(RGBA.B / 100000, INC_GAMMA) * 255;
}
RGBA.R = AscFormat.ClampColor(RGBA.R);
RGBA.G = AscFormat.ClampColor(RGBA.G);
RGBA.B = AscFormat.ClampColor(RGBA.B);
};
CColorModifiers.prototype.Apply = function (RGBA) {
if (null == this.Mods)
return;
var _len = this.Mods.length;
for (var i = 0; i < _len; i++) {
var colorMod = this.Mods[i];
var val = colorMod.val / 100000.0;
const _len = this.Mods.length;
for (let i = 0; i < _len; i++) {
const colorMod = this.Mods[i];
let val = colorMod.val / 100000.0;
if (colorMod.name === "alpha") {
RGBA.A = AscFormat.ClampColor(255 * val);
@ -1566,12 +1551,24 @@
RGBA.R = AscFormat.ClampColor(RGBA.R * val);
} else if (colorMod.name === "redOff") {
RGBA.R = AscFormat.ClampColor(RGBA.R + val * 255);
} else if (colorMod.name === "hueOff") {
var HSL = {H: 0, S: 0, L: 0};
} else if (colorMod.name === "hueMod") {
if (val === 1) {
continue;
}
const HSL = {H: 0, S: 0, L: 0};
this.RGB2HSL(RGBA.R, RGBA.G, RGBA.B, HSL);
HSL.H = AscCommon.trimMinMaxValue(HSL.H * val, 0, max_hls);
var res = (HSL.H + (val * 10.0) / 9.0 + 0.5) >> 0;
HSL.H = AscFormat.ClampColor2(res, 0, max_hls);
this.HSL2RGB(HSL, RGBA);
} else if (colorMod.name === "hueOff") {
if (val === 0) {
continue;
}
const HSL = {H: 0, S: 0, L: 0};
this.RGB2HSL(RGBA.R, RGBA.G, RGBA.B, HSL);
val = (colorMod.val / 60000) * (max_hls / 360);
const res = HSL.H + val;
HSL.H = AscCommon.trimMinMaxValue(res, 0, max_hls);
this.HSL2RGB(HSL, RGBA);
} else if (colorMod.name === "inv") {
@ -1579,35 +1576,48 @@
RGBA.G ^= 0xFF;
RGBA.B ^= 0xFF;
} else if (colorMod.name === "lumMod") {
var HSL = {H: 0, S: 0, L: 0};
if (val === 1) {
continue;
}
const HSL = {H: 0, S: 0, L: 0};
this.RGB2HSL(RGBA.R, RGBA.G, RGBA.B, HSL);
HSL.L = AscFormat.ClampColor2(HSL.L * val, 0, max_hls);
HSL.L = AscCommon.trimMinMaxValue(HSL.L * val, 0, max_hls);
this.HSL2RGB(HSL, RGBA);
} else if (colorMod.name === "lumOff") {
var HSL = {H: 0, S: 0, L: 0};
if (val === 0) {
continue;
}
const HSL = {H: 0, S: 0, L: 0};
this.RGB2HSL(RGBA.R, RGBA.G, RGBA.B, HSL);
var res = (HSL.L + val * max_hls + 0.5) >> 0;
HSL.L = AscFormat.ClampColor2(res, 0, max_hls);
const res = HSL.L + val * max_hls;
HSL.L = AscCommon.trimMinMaxValue(res, 0, max_hls);
this.HSL2RGB(HSL, RGBA);
} else if (colorMod.name === "satMod") {
var HSL = {H: 0, S: 0, L: 0};
if (val === 1) {
continue;
}
const HSL = {H: 0, S: 0, L: 0};
this.RGB2HSL(RGBA.R, RGBA.G, RGBA.B, HSL);
HSL.S = AscFormat.ClampColor2(HSL.S * val, 0, max_hls);
HSL.S = AscCommon.trimMinMaxValue(HSL.S * val, 0, max_hls);
this.HSL2RGB(HSL, RGBA);
} else if (colorMod.name === "satOff") {
var HSL = {H: 0, S: 0, L: 0};
if (val === 0) {
continue;
}
const HSL = {H: 0, S: 0, L: 0};
this.RGB2HSL(RGBA.R, RGBA.G, RGBA.B, HSL);
var res = (HSL.S + val * max_hls + 0.5) >> 0;
HSL.S = AscFormat.ClampColor2(res, 0, max_hls);
const res = HSL.S + val * max_hls;
HSL.S = AscCommon.trimMinMaxValue(res, 0, max_hls);
this.HSL2RGB(HSL, RGBA);
} else if (colorMod.name === "wordShade") {
var val_ = colorMod.val / 255;
if (colorMod.val === 255) {
continue;
}
const val_ = colorMod.val / 255;
//GBA.R = Math.max(0, (RGBA.R * (1 - val_)) >> 0);
//GBA.G = Math.max(0, (RGBA.G * (1 - val_)) >> 0);
//GBA.B = Math.max(0, (RGBA.B * (1 - val_)) >> 0);
@ -1617,22 +1627,25 @@
//RGBA.G = Math.max(0, ((1 - val_)*(- RGBA.G) + RGBA.G) >> 0);
//RGBA.B = Math.max(0, ((1 - val_)*(- RGBA.B) + RGBA.B) >> 0);
var HSL = {H: 0, S: 0, L: 0};
const HSL = {H: 0, S: 0, L: 0};
this.RGB2HSL(RGBA.R, RGBA.G, RGBA.B, HSL);
HSL.L = AscFormat.ClampColor2(HSL.L * val_, 0, max_hls);
HSL.L = AscCommon.trimMinMaxValue(HSL.L * val_, 0, max_hls);
this.HSL2RGB(HSL, RGBA);
} else if (colorMod.name === "wordTint") {
var _val = colorMod.val / 255;
if (colorMod.val === 255) {
continue;
}
const _val = colorMod.val / 255;
//RGBA.R = Math.max(0, ((1 - _val)*(255 - RGBA.R) + RGBA.R) >> 0);
//RGBA.G = Math.max(0, ((1 - _val)*(255 - RGBA.G) + RGBA.G) >> 0);
//RGBA.B = Math.max(0, ((1 - _val)*(255 - RGBA.B) + RGBA.B) >> 0);
var HSL = {H: 0, S: 0, L: 0};
const HSL = {H: 0, S: 0, L: 0};
this.RGB2HSL(RGBA.R, RGBA.G, RGBA.B, HSL);
var L_ = HSL.L * _val + (255 - colorMod.val);
HSL.L = AscFormat.ClampColor2(L_, 0, max_hls);
const L_ = HSL.L * _val + (255 - colorMod.val);
HSL.L = AscCommon.trimMinMaxValue(L_, 0, max_hls);
this.HSL2RGB(HSL, RGBA);
} else if (colorMod.name === "shade") {
this.RgbtoCrgb(RGBA);

View File

@ -13598,6 +13598,14 @@
{
return rad * 180.0 / Math.PI;
}
function trimMinMaxValue(value, min, max) {
if (value < min)
return min;
if (value > max)
return max;
return value;
}
//------------------------------------------------------------export---------------------------------------------------
window['AscCommon'] = window['AscCommon'] || {};
window["AscCommon"].getSockJs = getSockJs;
@ -13818,6 +13826,7 @@
window['AscCommon'].deg2rad = deg2rad;
window['AscCommon'].rad2deg = rad2deg;
window["AscCommon"].c_oAscImageUploadProp = c_oAscImageUploadProp;
window["AscCommon"].trimMinMaxValue = trimMinMaxValue;
})(window);
window["asc_initAdvancedOptions"] = function(_code, _file_hash, _docInfo)

View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Test color mods</title>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<link type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/qunit/2.16.0/qunit.css" rel="stylesheet" media="screen" />
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/qunit/2.16.0/qunit.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/xregexp/3.2.0/xregexp-all.min.js"></script>
<script type="text/javascript" src="../../../develop/sdkjs/word/scripts.js"></script>
<script type="text/javascript">
window.sdk_scripts.forEach(function(item){
document.write('<script type="text/javascript" src="' + item + '"><\/script>');
});
</script>
<script type="text/javascript" src="color-mods.js"></script>
</head>
<body>
<h1 id="qunit-header">Test applying color mods</h1>
<h2 id="qunit-banner"></h2>
<div id="qunit-testrunner-toolbar"></div>
<h2 id="qunit-userAgent"></h2>
<ol id="qunit-tests"></ol>
<div id="qunit-fixture">test markup, will be hidden</div>
</body>
</html>

View File

@ -0,0 +1,322 @@
/*
* (c) Copyright Ascensio System SIA 2010-2023
*
* This program is a free software product. You can redistribute it and/or
* modify it under the terms of the GNU Affero General Public License (AGPL)
* version 3 as published by the Free Software Foundation. In accordance with
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
* that Ascensio System SIA expressly excludes the warranty of non-infringement
* of any third-party rights.
*
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
*
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
* street, Riga, Latvia, EU, LV-1050.
*
* The interactive user interfaces in modified source and object code versions
* of the Program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU AGPL version 3.
*
* Pursuant to Section 7(b) of the License you must retain the original Product
* logo when distributing the program. Pursuant to Section 7(e) we decline to
* grant you any rights under trademark law for use of our trademarks.
*
* All the Product's GUI elements, including illustrations and icon sets, as
* well as technical writing content are licensed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
*
*/
$(function () {
function rgb(r, g, b, a) {
a = typeof a === 'number' ? a : 255;
return {R: r, G:g, B: b, A: a};
}
function test(startColor, arrMods, expectedColor) {
const oMods = new AscFormat.CColorModifiers();
const description = "Check applying ";
const modsDescription = [];
for (let i = 0; i < arrMods.length; i += 1) {
const mod = arrMods[i];
oMods.addMod(mod.name, mod.value);
modsDescription.push(mod.description);
}
oMods.Apply(startColor);
return {expected: expectedColor, result: startColor, description: description + modsDescription.join(', ')};
}
function mod(name, value) {
return {name: name, value: value, description: name + " with " + value + " value"};
}
QUnit.module("Test applying color mods");
const tests = [
test(
rgb(68, 114, 196),
[mod('satOff', 0), mod('lumOff', 0), mod('hueOff', 0)],
rgb(68, 114, 196)
),
test(
rgb(68, 114, 196),
[],
rgb(68, 114, 196)
),
test(
rgb(68, 114, 196),
[mod("hueMod", 100000), mod("satMod", 100000), mod("lumMod", 100000)],
rgb(68, 114, 196)
),
test(
rgb(68, 114, 196),
[mod("satMod", 5000)],
rgb(129, 131, 135)
),
test(
rgb(68, 114, 196),
[mod("satMod", 10000)],
rgb(126, 130, 138)
),
test(
rgb(68, 114, 196),
[mod("lumMod", 10000)],
rgb(6, 11, 20)
),
test(
rgb(68, 114, 196),
[mod("lumMod", 20000)],
rgb(13, 23, 40)
),
test(
rgb(68, 114, 196),
[mod("lumOff", 20000)],
rgb(146, 172, 220)
),
test(
rgb(68, 114, 196),
[mod("hueOff", 20000)],
rgb(68, 113, 196)
),
test(
rgb(68, 114, 196),
[mod("hueOff", -200000)],
rgb(68, 121, 196)
),
test(
rgb(165, 165, 165),
[mod("hueOff", 677650)],
rgb(165, 165, 165)
),
test(
rgb(165, 165, 165),
[mod("satOff", 0), mod("lumOff", 0), mod("hueOff", 0), mod("alphaOff", 0)],
rgb(165, 165, 165)
),
test(
rgb(134, 86, 64),
[mod("hueOff", 2710599)],
rgb(129, 134, 64)
),
test(
rgb(165, 165, 165),
[mod("lumOff", -3676)],
rgb(156, 156, 156)
),
test(
rgb(98, 168, 87),
[mod("tint", 12000)],
rgb(243, 247, 242)
),
test(
rgb(98, 168, 87),
[mod("tint", 50000)],
rgb(197, 217, 195)
),
test(
rgb(157, 54, 14),
[mod("hueMod", 44000)],
rgb(157, 32, 14)
)
];
const todoTests = [
test(
rgb(157, 54, 14),
[mod("satMod", 200000)],
rgb(229, 22, 0)
),
test(
rgb(157, 54, 14),
[mod("satMod", 0)],
rgb(85, 85, 85)
),
test(
rgb(157, 54, 14),
[mod("satMod", 10000)],
rgb(93, 82, 78)
),
test(
rgb(157, 54, 14),
[mod("satMod", 20000)],
rgb(100, 79, 71)
),
test(
rgb(157, 54, 14),
[mod("satMod", 30000)],
rgb(107, 76, 64)
),
test(
rgb(157, 54, 14),
[mod("satMod", 100000)],
rgb(157, 54, 14)
),
test(
rgb(157, 54, 14),
[mod("satMod", 150000)],
rgb(193, 38, 0)
),
test(
rgb(157, 54, 14),
[mod("satMod", 110000)],
rgb(164, 51, 7)
),
test(
rgb(157, 54, 14),
[mod("satMod", 120000)],
rgb(171, 48, 0)
),
test(
rgb(157, 54, 14),
[mod("satMod", 130000)],
rgb(178, 45, 0)
),
test(
rgb(157, 54, 14),
[mod("satMod", 130000)],
rgb(178, 45, 0)
),
test(
rgb(157, 54, 14),
[mod("satMod", 140000)],
rgb(186, 41, 0)
),
test(
rgb(157, 54, 14),
[mod("satMod", 150000)],
rgb(193, 38, 0)
),
test(
rgb(157, 54, 14),
[mod("satMod", 160000)],
rgb(200, 35, 0)
),
test(
rgb(157, 54, 14),
[mod("satMod", 170000)],
rgb(207, 32, 0)
),
test(
rgb(157, 54, 14),
[mod("satMod", 180000)],
rgb(214, 29, 0)
),
test(
rgb(157, 54, 14),
[mod("satMod", 190000)],
rgb(221, 26, 0)
),
test(
rgb(157, 54, 14),
[mod("satMod", 200000)],
rgb(229, 22, 0)
),
test(
rgb(157, 54, 14),
[mod("satOff", 10000)],
rgb(166, 50, 5)
),
test(
rgb(157, 54, 14),
[mod("satOff", 20000)],
rgb(174, 46, 0)
),
test(
rgb(165, 165, 165),
[mod("satOff", 25000)],
rgb(187, 142, 53)
),
test(
rgb(165, 165, 165),
[mod("satOff", 40000)],
rgb(201, 129, 0)
),
test(
rgb(165, 165, 165),
[mod("satOff", 10000)],
rgb(174, 156, 120)
),
test(
rgb(165, 165, 165),
[mod("satOff", 30000)],
rgb(192, 138, 30)
),
test(
rgb(165, 165, 165),
[mod("satOff", 35000)],
rgb(196, 134, 8)
),
test(
rgb(165, 165, 165),
[mod("hueOff", 677650), mod("satOff", 25000), mod("lumOff", -3676), mod("alphaOff", 0)],
rgb(173, 131, 48)
),
test(
rgb(165, 165, 165),
[mod("hueOff", 1355300), mod("satOff", 50000), mod("lumOff", -7353), mod("alphaOff", 0)],
rgb(173, 99, 0)
),
test(
rgb(165, 165, 165),
[mod("hueOff", 2032949), mod("satOff", 75000), mod("lumOff", -11029), mod("alphaOff", 0)],
rgb(176, 74, 0)
),
test(
rgb(165, 165, 165),
[mod("hueOff", 2710599), mod("satOff", 100000), mod("lumOff", -14706), mod("alphaOff", 0)],
rgb(180, 53, 0)
),
test(
rgb(131, 131, 131),
[mod("satOff", 99200)],
rgb(254, 8, 0)
),
test(
rgb(127, 127, 127),
[mod("satOff", 99200)],
rgb(253, 1, 0)
),
test(
rgb(223, 219, 213),
[mod("shade", 80000)],
rgb(202, 198, 193)
),
test(
rgb(223, 219, 213),
[mod("shade", 50000)],
rgb(70, 122, 62)
),
test(
rgb(98, 168, 87),
[mod("tint", 90000)],
rgb(126, 179, 119)
),
];
QUnit.test('Check colors with mods', (assert) => {
for (let i = 0; i < tests.length; i++) {
const test = tests[i];
assert.deepEqual(test.result, test.expected, test.description);
}
});
});