Skip to content

Commit bf71a0e

Browse files
committed
Add EffectTransform class
1 parent e129996 commit bf71a0e

File tree

6 files changed

+267
-0
lines changed

6 files changed

+267
-0
lines changed

src/CMakeLists.txt

+2
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ qt_add_qml_module(scratchcpp-render
7272
textbubblepainter.h
7373
cputexturemanager.cpp
7474
cputexturemanager.h
75+
effecttransform.cpp
76+
effecttransform.h
7577
blocks/penextension.cpp
7678
blocks/penextension.h
7779
blocks/penblocks.cpp

src/effecttransform.cpp

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
3+
#include <QVector2D>
4+
5+
#include "effecttransform.h"
6+
7+
using namespace scratchcpprender;
8+
9+
QRgb EffectTransform::transformColor(const std::unordered_map<ShaderManager::Effect, double> &effectValues, QRgb color)
10+
{
11+
// https://github.com/scratchfoundation/scratch-render/blob/e075e5f5ebc95dec4a2718551624ad587c56f0a6/src/EffectTransform.js#L40-L119
12+
// If the color is fully transparent, don't bother attempting any transformations.
13+
if (qAlpha(color) == 0)
14+
return color;
15+
16+
QColor inOutColor = QColor::fromRgba(color);
17+
18+
std::unordered_map<ShaderManager::Effect, float> uniforms;
19+
ShaderManager::getUniformValuesForEffects(effectValues, uniforms);
20+
21+
const bool enableColor = uniforms[ShaderManager::Effect::Color] != 0;
22+
const bool enableBrightness = uniforms[ShaderManager::Effect::Brightness] != 0;
23+
24+
if (enableColor || enableBrightness) {
25+
// gl_FragColor.rgb /= gl_FragColor.a + epsilon;
26+
// Here, we're dividing by the (previously pre-multiplied) alpha to ensure HSV is properly calculated
27+
// for partially transparent pixels.
28+
const float alpha = inOutColor.alphaF();
29+
30+
if (alpha == 0) {
31+
inOutColor.setRed(255);
32+
inOutColor.setGreen(255);
33+
inOutColor.setBlue(255);
34+
} else {
35+
inOutColor.setRedF(inOutColor.redF() / alpha);
36+
inOutColor.setGreenF(inOutColor.greenF() / alpha);
37+
inOutColor.setBlueF(inOutColor.blueF() / alpha);
38+
}
39+
40+
if (enableColor) {
41+
// vec3 hsv = convertRGB2HSV(gl_FragColor.xyz);
42+
QColor hsv = inOutColor.toHsv();
43+
44+
// this code forces grayscale values to be slightly saturated
45+
// so that some slight change of hue will be visible
46+
// const float minLightness = 0.11 / 2.0;
47+
const float minV = 0.11f / 2.0f;
48+
// const float minSaturation = 0.09;
49+
const float minS = 0.09f;
50+
// if (hsv.z < minLightness) hsv = vec3(0.0, 1.0, minLightness);
51+
if (hsv.valueF() < minV) {
52+
hsv.setHsvF(0.0f, 1.0f, minV);
53+
// else if (hsv.y < minSaturation) hsv = vec3(0.0, minSaturation, hsv.z);
54+
} else if (hsv.saturationF() < minS) {
55+
hsv.setHsvF(0.0f, minS, hsv.valueF());
56+
}
57+
58+
// hsv.x = mod(hsv.x + u_color, 1.0);
59+
// if (hsv.x < 0.0) hsv.x += 1.0;
60+
float hue = std::fmod(uniforms[ShaderManager::Effect::Color] + hsv.hueF(), 1.0f);
61+
62+
if (hue < 0.0f)
63+
hue += 1.0f;
64+
65+
hsv.setHsvF(hue, hsv.saturationF(), hsv.valueF());
66+
67+
// gl_FragColor.rgb = convertHSV2RGB(hsl);
68+
inOutColor = hsv.toRgb();
69+
}
70+
71+
if (enableBrightness) {
72+
const float brightness = uniforms[ShaderManager::Effect::Brightness] * 255.0f;
73+
// gl_FragColor.rgb = clamp(gl_FragColor.rgb + vec3(u_brightness), vec3(0), vec3(1));
74+
inOutColor.setRed(std::clamp(inOutColor.red() + brightness, 0.0f, 255.0f));
75+
inOutColor.setGreen(std::clamp(inOutColor.green() + brightness, 0.0f, 255.0f));
76+
inOutColor.setBlue(std::clamp(inOutColor.blue() + brightness, 0.0f, 255.0f));
77+
}
78+
79+
// gl_FragColor.rgb *= gl_FragColor.a + epsilon;
80+
// Now we're doing the reverse, premultiplying by the alpha once again.
81+
inOutColor.setRedF(inOutColor.redF() * alpha);
82+
inOutColor.setGreenF(inOutColor.greenF() * alpha);
83+
inOutColor.setBlueF(inOutColor.blueF() * alpha);
84+
85+
// Restore alpha
86+
inOutColor.setAlphaF(alpha);
87+
}
88+
89+
const float ghost = uniforms[ShaderManager::Effect::Ghost];
90+
91+
if (ghost != 1) {
92+
// gl_FragColor *= u_ghost
93+
inOutColor.setRedF(inOutColor.redF() * ghost);
94+
inOutColor.setGreenF(inOutColor.greenF() * ghost);
95+
inOutColor.setBlueF(inOutColor.blueF() * ghost);
96+
inOutColor.setAlphaF(inOutColor.alphaF() * ghost);
97+
}
98+
99+
return inOutColor.rgba();
100+
}
101+
102+
void EffectTransform::transformPoint(const std::unordered_map<ShaderManager::Effect, double> &effectValues, const QVector2D &vec, QVector2D &dst)
103+
{
104+
// TODO: Implement remaining effects
105+
dst = vec;
106+
}

src/effecttransform.h

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
3+
#pragma once
4+
5+
#include <QColor>
6+
7+
#include "shadermanager.h"
8+
9+
namespace scratchcpprender
10+
{
11+
12+
class EffectTransform
13+
{
14+
public:
15+
EffectTransform() = delete;
16+
17+
static QRgb transformColor(const std::unordered_map<ShaderManager::Effect, double> &effectValues, QRgb color);
18+
static void transformPoint(const std::unordered_map<ShaderManager::Effect, double> &effectValues, const QVector2D &vec, QVector2D &dst);
19+
};
20+
21+
} // namespace scratchcpprender

test/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,4 @@ add_subdirectory(graphicseffect)
4141
add_subdirectory(shadermanager)
4242
add_subdirectory(textbubbleshape)
4343
add_subdirectory(textbubblepainter)
44+
add_subdirectory(effecttransform)

test/effecttransform/CMakeLists.txt

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
add_executable(
2+
effecttransform_test
3+
effecttransform_test.cpp
4+
)
5+
6+
target_link_libraries(
7+
effecttransform_test
8+
GTest::gtest_main
9+
scratchcpp-render
10+
qnanopainter
11+
)
12+
13+
add_test(effecttransform_test)
14+
gtest_discover_tests(effecttransform_test)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
#include <QVector2D>
2+
#include <effecttransform.h>
3+
4+
#include "../common.h"
5+
6+
using namespace scratchcpprender;
7+
8+
class EffectTransformTest : public testing::Test
9+
{
10+
public:
11+
void SetUp() override { }
12+
13+
std::unordered_map<ShaderManager::Effect, double> m_effects;
14+
};
15+
16+
TEST_F(EffectTransformTest, NoEffect)
17+
{
18+
QRgb color = qRgba(0, 0, 0, 0);
19+
ASSERT_EQ(EffectTransform::transformColor(m_effects, color), color);
20+
21+
color = qRgba(255, 0, 0, 255);
22+
ASSERT_EQ(EffectTransform::transformColor(m_effects, color), color);
23+
24+
color = qRgba(0, 255, 255, 255);
25+
ASSERT_EQ(EffectTransform::transformColor(m_effects, color), color);
26+
27+
color = qRgba(255, 255, 255, 128);
28+
ASSERT_EQ(EffectTransform::transformColor(m_effects, color), color);
29+
30+
QVector2D dst;
31+
EffectTransform::transformPoint(m_effects, QVector2D(0.5, -0.3), dst);
32+
ASSERT_EQ(dst, QVector2D(0.5, -0.3));
33+
}
34+
35+
TEST_F(EffectTransformTest, ColorEffect)
36+
{
37+
// 100
38+
m_effects[ShaderManager::Effect::Color] = 100;
39+
QRgb color = qRgba(0, 0, 0, 0);
40+
ASSERT_EQ(EffectTransform::transformColor(m_effects, color), color);
41+
42+
color = qRgba(255, 0, 0, 255);
43+
ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgb(0, 255, 255));
44+
45+
color = qRgba(100, 255, 200, 128);
46+
ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgba(128, 100, 100, 128));
47+
48+
// 175
49+
m_effects[ShaderManager::Effect::Color] = 175;
50+
color = qRgba(255, 0, 0, 255);
51+
ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgb(255, 0, 191));
52+
53+
color = qRgba(100, 255, 200, 128);
54+
ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgba(100, 128, 107, 128));
55+
}
56+
57+
TEST_F(EffectTransformTest, BrightnessEffect)
58+
{
59+
// -100
60+
m_effects[ShaderManager::Effect::Brightness] = -100;
61+
QRgb color = qRgba(0, 0, 0, 0);
62+
ASSERT_EQ(EffectTransform::transformColor(m_effects, color), color);
63+
64+
color = qRgba(255, 0, 0, 255);
65+
ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgb(0, 0, 0));
66+
67+
color = qRgba(100, 255, 200, 128);
68+
ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgba(0, 0, 0, 128));
69+
70+
// -50
71+
m_effects[ShaderManager::Effect::Brightness] = -50;
72+
color = qRgba(255, 0, 0, 255);
73+
ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgb(127, 0, 0));
74+
75+
color = qRgba(100, 255, 200, 128);
76+
ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgba(36, 64, 64, 128));
77+
78+
// 50
79+
m_effects[ShaderManager::Effect::Brightness] = 50;
80+
color = qRgba(255, 0, 0, 255);
81+
ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgb(255, 127, 127));
82+
83+
color = qRgba(100, 255, 200, 128);
84+
ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgba(128, 128, 128, 128));
85+
86+
// 100
87+
m_effects[ShaderManager::Effect::Brightness] = 100;
88+
color = qRgba(255, 0, 0, 255);
89+
ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgb(255, 255, 255));
90+
91+
color = qRgba(100, 255, 200, 128);
92+
ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgba(128, 128, 128, 128));
93+
}
94+
95+
TEST_F(EffectTransformTest, GhostEffect)
96+
{
97+
// 25
98+
m_effects[ShaderManager::Effect::Ghost] = 25;
99+
QRgb color = qRgba(0, 0, 0, 0);
100+
ASSERT_EQ(EffectTransform::transformColor(m_effects, color), color);
101+
102+
color = qRgba(255, 0, 0, 255);
103+
ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgba(191, 0, 0, 191));
104+
105+
color = qRgba(100, 255, 200, 128);
106+
ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgba(75, 191, 150, 96));
107+
108+
// 50
109+
m_effects[ShaderManager::Effect::Ghost] = 50;
110+
color = qRgba(255, 0, 0, 255);
111+
ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgba(128, 0, 0, 128));
112+
113+
color = qRgba(100, 255, 200, 128);
114+
ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgba(50, 128, 100, 64));
115+
116+
// 100
117+
m_effects[ShaderManager::Effect::Ghost] = 100;
118+
color = qRgba(255, 0, 0, 255);
119+
ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgba(0, 0, 0, 0));
120+
121+
color = qRgba(100, 255, 200, 128);
122+
ASSERT_EQ(EffectTransform::transformColor(m_effects, color), qRgba(0, 0, 0, 0));
123+
}

0 commit comments

Comments
 (0)