Skip to content

Commit e29a6b9

Browse files
authored
Merge pull request #142 from scratchcpp/effect_transform
Implement CPU graphic effects
2 parents b000c61 + 125bb06 commit e29a6b9

14 files changed

+423
-44
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/cputexturemanager.cpp

+29-11
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "cputexturemanager.h"
44
#include "texture.h"
5+
#include "effecttransform.h"
56

67
using namespace scratchcpprender;
78

@@ -51,10 +52,36 @@ const std::vector<QPoint> &CpuTextureManager::getTextureConvexHullPoints(const T
5152
return it->second;
5253
}
5354

54-
bool CpuTextureManager::textureContainsPoint(const Texture &texture, const QPointF &localPoint)
55+
QRgb CpuTextureManager::getPointColor(const Texture &texture, int x, int y, const std::unordered_map<ShaderManager::Effect, double> &effects)
56+
{
57+
const int width = texture.width();
58+
const int height = texture.height();
59+
60+
if (!effects.empty()) {
61+
// Get local position with effect transform
62+
QVector2D transformedCoords;
63+
const QVector2D localCoords(x / static_cast<float>(width), y / static_cast<float>(height));
64+
EffectTransform::transformPoint(effects, localCoords, transformedCoords);
65+
x = transformedCoords.x() * width;
66+
y = transformedCoords.y() * height;
67+
}
68+
69+
if ((x < 0 || x >= width) || (y < 0 || y >= height))
70+
return qRgba(0, 0, 0, 0);
71+
72+
GLubyte *pixels = getTextureData(texture);
73+
QRgb color = qRgba(pixels[(y * width + x) * 4], pixels[(y * width + x) * 4 + 1], pixels[(y * width + x) * 4 + 2], pixels[(y * width + x) * 4 + 3]);
74+
75+
if (effects.empty())
76+
return color;
77+
else
78+
return EffectTransform::transformColor(effects, color);
79+
}
80+
81+
bool CpuTextureManager::textureContainsPoint(const Texture &texture, const QPointF &localPoint, const std::unordered_map<ShaderManager::Effect, double> &effects)
5582
{
5683
// https://github.com/scratchfoundation/scratch-render/blob/7b823985bc6fe92f572cc3276a8915e550f7c5e6/src/Silhouette.js#L219-L226
57-
return getPointAlpha(texture, localPoint.x(), localPoint.y()) > 0;
84+
return qAlpha(getPointColor(texture, localPoint.x(), localPoint.y(), effects)) > 0;
5885
}
5986

6087
void CpuTextureManager::removeTexture(const Texture &texture)
@@ -137,12 +164,3 @@ bool CpuTextureManager::addTexture(const Texture &texture)
137164

138165
return true;
139166
}
140-
141-
int CpuTextureManager::getPointAlpha(const Texture &texture, int x, int y)
142-
{
143-
if ((x < 0 || x >= texture.width()) || (y < 0 || y >= texture.height()))
144-
return 0;
145-
146-
GLubyte *pixels = getTextureData(texture);
147-
return pixels[(y * texture.width() + x) * 4 + 3];
148-
}

src/cputexturemanager.h

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
#include <QtOpenGL>
77
#include <unordered_map>
88

9+
#include "shadermanager.h"
10+
911
namespace scratchcpprender
1012
{
1113

@@ -20,13 +22,13 @@ class CpuTextureManager
2022
GLubyte *getTextureData(const Texture &texture);
2123
const std::vector<QPoint> &getTextureConvexHullPoints(const Texture &texture);
2224

23-
bool textureContainsPoint(const Texture &texture, const QPointF &localPoint);
25+
QRgb getPointColor(const Texture &texture, int x, int y, const std::unordered_map<ShaderManager::Effect, double> &effects);
26+
bool textureContainsPoint(const Texture &texture, const QPointF &localPoint, const std::unordered_map<ShaderManager::Effect, double> &effects);
2427

2528
void removeTexture(const Texture &texture);
2629

2730
private:
2831
bool addTexture(const Texture &texture);
29-
int getPointAlpha(const Texture &texture, int x, int y);
3032

3133
std::unordered_map<GLuint, GLubyte *> m_textureData;
3234
std::unordered_map<GLuint, std::vector<QPoint>> m_convexHullPoints;

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

src/renderedtarget.cpp

+2-6
Original file line numberDiff line numberDiff line change
@@ -636,11 +636,7 @@ QRgb RenderedTarget::colorAtScratchPoint(double x, double y) const
636636
if ((x < 0 || x >= width) || (y < 0 || y >= height))
637637
return qRgba(0, 0, 0, 0);
638638

639-
GLubyte *data = textureManager()->getTextureData(m_cpuTexture);
640-
const int index = (y * width + x) * 4; // RGBA channels
641-
Q_ASSERT(index >= 0 && index < width * height * 4);
642-
// TODO: Apply graphic effects (#117)
643-
return qRgba(data[index], data[index + 1], data[index + 2], data[index + 3]);
639+
return textureManager()->getPointColor(m_cpuTexture, x, y, m_graphicEffects);
644640
}
645641

646642
bool RenderedTarget::touchingClones(const std::vector<libscratchcpp::Sprite *> &clones) const
@@ -817,7 +813,7 @@ void RenderedTarget::updateHullPoints()
817813

818814
bool RenderedTarget::containsLocalPoint(const QPointF &point) const
819815
{
820-
return textureManager()->textureContainsPoint(m_cpuTexture, point);
816+
return textureManager()->textureContainsPoint(m_cpuTexture, point, m_graphicEffects);
821817
}
822818

823819
QPointF RenderedTarget::transformPoint(double scratchX, double scratchY, double originX, double originY, double rot) const

src/shadermanager.cpp

+16-5
Original file line numberDiff line numberDiff line change
@@ -101,12 +101,10 @@ QOpenGLShaderProgram *ShaderManager::getShaderProgram(const std::unordered_map<E
101101
return it->second;
102102
}
103103

104-
void ShaderManager::setUniforms(QOpenGLShaderProgram *program, int textureUnit, const std::unordered_map<Effect, double> &effectValues)
104+
void ShaderManager::getUniformValuesForEffects(const std::unordered_map<Effect, double> &effectValues, std::unordered_map<Effect, float> &dst)
105105
{
106-
// Set the texture unit
107-
program->setUniformValue(TEXTURE_UNIT_UNIFORM, textureUnit);
106+
dst.clear();
108107

109-
// Set the uniform values for the enabled effects and reset the other effects
110108
for (const auto &[effect, name] : EFFECT_TO_NAME) {
111109
const auto it = effectValues.find(effect);
112110
double value;
@@ -117,10 +115,23 @@ void ShaderManager::setUniforms(QOpenGLShaderProgram *program, int textureUnit,
117115
value = it->second;
118116

119117
auto converter = EFFECT_CONVERTER.at(effect);
120-
program->setUniformValue(EFFECT_UNIFORM_NAME.at(effect), converter(value));
118+
dst[effect] = converter(value);
121119
}
122120
}
123121

122+
void ShaderManager::setUniforms(QOpenGLShaderProgram *program, int textureUnit, const std::unordered_map<Effect, double> &effectValues)
123+
{
124+
// Set the texture unit
125+
program->setUniformValue(TEXTURE_UNIT_UNIFORM, textureUnit);
126+
127+
// Set uniform values
128+
std::unordered_map<Effect, float> values;
129+
getUniformValuesForEffects(effectValues, values);
130+
131+
for (const auto &[effect, value] : values)
132+
program->setUniformValue(EFFECT_UNIFORM_NAME.at(effect), value);
133+
}
134+
124135
void ShaderManager::registerEffects()
125136
{
126137
// Register graphic effects in libscratchcpp

src/shadermanager.h

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class ShaderManager : public QObject
2626
static ShaderManager *instance();
2727

2828
QOpenGLShaderProgram *getShaderProgram(const std::unordered_map<Effect, double> &effectValues);
29+
static void getUniformValuesForEffects(const std::unordered_map<Effect, double> &effectValues, std::unordered_map<Effect, float> &dst);
2930
void setUniforms(QOpenGLShaderProgram *program, int textureUnit, const std::unordered_map<Effect, double> &effectValues);
3031

3132
private:

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)

0 commit comments

Comments
 (0)