Skip to content

Commit 46a4667

Browse files
committed
feat: add CGEHistogramFilter via GLES 3.1 compute shaders
Implement a luminance histogram overlay filter using three GPU-side compute-shader passes: Pass 1 (clear) - zero the 256x1 r32ui atomic counter texture Pass 2 (count) - imageAtomicAdd per pixel into the counter texture Pass 3 (draw) - render a normalised bar chart into a 256x256 rgba8 display texture The display texture is blended onto the frame at the configured position/size with 80% alpha, identical to CGEWaveformFilter. Filter rule syntax: @Style hist <left> <top> <width> <height> e.g. @Style hist 0.01 0.01 0.4 0.4 Changes: - Add cgeHistogramFilter.h / .cpp (new filter) - Register createHistogramFilter() in cgeAdvancedEffects.h/.cpp - Add 'hist' style parser in cgeDataParsingEngine.cpp - Add two demo entries in MainActivity.java EFFECT_CONFIGS Requires OpenGL ES 3.1+ (compute shaders + imageAtomicAdd).
1 parent e672a8e commit 46a4667

File tree

6 files changed

+296
-0
lines changed

6 files changed

+296
-0
lines changed

cgeDemo/src/main/java/org/wysaid/cgeDemo/MainActivity.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ public class MainActivity extends AppCompatActivity {
4646
"",
4747
"@curve RGB(0,255)(255,0) @style cm mapping0.jpg 80 80 8 3", // ASCII art effect
4848
"@style waveform 0.01 0.01 0.4 0.4",
49+
"@style hist 0.01 0.01 0.4 0.4", // Luminance histogram overlay
50+
"@style hist 0.01 0.01 0.4 0.4 @style waveform 0.45 0.01 0.4 0.4", // hist + waveform side by side
4951
"@beautify face 1 480 640", //Beautify
5052
"@adjust lut edgy_amber.png",
5153
"@adjust lut filmstock.png",

library/src/main/jni/cge/filters/cgeAdvancedEffects.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,9 @@ CGEWaveformFilter* createWaveformFilter()
141141
{
142142
COMMON_FUNC(CGEWaveformFilter);
143143
}
144+
145+
CGEHistogramFilter* createHistogramFilter()
146+
{
147+
COMMON_FUNC(CGEHistogramFilter);
148+
}
144149
} // namespace CGE

library/src/main/jni/cge/filters/cgeAdvancedEffects.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "cgeRandomBlurFilter.h"
2424
#include "cgeSketchFilter.h"
2525
#include "cgeWaveformFilter.h"
26+
#include "cgeHistogramFilter.h"
2627

2728
namespace CGE
2829
{
@@ -51,6 +52,7 @@ CGESketchFilter* createSketchFilter();
5152
CGEBeautifyFilter* createBeautifyFilter();
5253

5354
CGEWaveformFilter* createWaveformFilter();
55+
CGEHistogramFilter* createHistogramFilter();
5456
} // namespace CGE
5557

5658
#endif

library/src/main/jni/cge/filters/cgeDataParsingEngine.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,6 +1110,23 @@ CGEImageFilterInterface* CGEDataParsingEngine::advancedStyleParser(const char* p
11101110
filter->setFormSize(width, height);
11111111
}
11121112
}
1113+
else if (strcmp(buffer, "hist") == 0)
1114+
{
1115+
float x, y, width, height;
1116+
if (sscanf(pstr, "%f%*c%f%*c%f%*c%f", &x, &y, &width, &height) < 4)
1117+
{
1118+
LOG_ERROR_PARAM(pstr);
1119+
return nullptr;
1120+
}
1121+
1122+
CGEHistogramFilter* filter = createHistogramFilter();
1123+
if (filter != nullptr)
1124+
{
1125+
proc = filter;
1126+
filter->setFormPosition(x, y);
1127+
filter->setFormSize(width, height);
1128+
}
1129+
}
11131130
else if (strcmp(buffer, "edge") == 0)
11141131
{
11151132
ADJUSTHELP_COMMON_FUNC2(pstr, CGEEdgeSobelFilter, setIntensity, setStride);
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/*
2+
* cgeHistogramFilter.cpp
3+
*
4+
* Luminance histogram overlay rendered entirely on the GPU using three
5+
* GLES 3.1 compute-shader passes:
6+
*
7+
* Pass 1 (clear) — zero the 256×1 r32ui atomic-counter texture
8+
* Pass 2 (count) — imageAtomicAdd per pixel into the counter texture
9+
* Pass 3 (draw) — write bar-chart pixels into the 256×256 display texture
10+
*
11+
* The display texture is then blended onto the frame at the configured
12+
* position / size, exactly like CGEWaveformFilter.
13+
*
14+
* Requirements: OpenGL ES 3.1 (compute shaders + image load/store).
15+
*/
16+
17+
#include "cgeHistogramFilter.h"
18+
19+
#define USING_ALPHA 1 /// 80 % semi-transparent blend, same as WaveformFilter
20+
21+
// ---------------------------------------------------------------------------
22+
// Pass 1 — clear the 256 histogram bins
23+
// Dispatched as glDispatchCompute(256, 1, 1)
24+
// ---------------------------------------------------------------------------
25+
static CGEConstString s_cshHistClear = "#version 310 es\n" CGE_SHADER_STRING(
26+
precision highp float;
27+
precision highp int;
28+
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
29+
layout(r32ui, binding = 2) writeonly uniform highp uimage2D histogramImage;
30+
31+
void main() {
32+
imageStore(histogramImage, ivec2(int(gl_GlobalInvocationID.x), 0), uvec4(0u, 0u, 0u, 0u));
33+
});
34+
35+
// ---------------------------------------------------------------------------
36+
// Pass 2 — accumulate luminance histogram via atomic add
37+
// Dispatched as glDispatchCompute(frameWidth, frameHeight, 1)
38+
// ---------------------------------------------------------------------------
39+
static CGEConstString s_cshHistCount = "#version 310 es\n" CGE_SHADER_STRING(
40+
precision highp float;
41+
precision highp int;
42+
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
43+
layout(rgba8ui, binding = 0) uniform readonly highp uimage2D inputImageTexture;
44+
// coherent volatile restrict are required for imageAtomicAdd
45+
layout(r32ui, binding = 2) coherent volatile restrict uniform highp uimage2D histogramImage;
46+
47+
void main() {
48+
ivec2 texCoord = ivec2(gl_GlobalInvocationID.xy);
49+
uvec3 color = imageLoad(inputImageTexture, texCoord).rgb;
50+
51+
// BT.601 luminance (integer approximation kept in float for clarity)
52+
float lum = dot(vec3(color.rgb), vec3(0.299, 0.587, 0.114));
53+
uint bin = clamp(uint(lum), 0u, 255u);
54+
imageAtomicAdd(histogramImage, ivec2(bin, 0), 1u);
55+
});
56+
57+
// ---------------------------------------------------------------------------
58+
// Pass 3 — render bar chart into the 256×256 display texture
59+
// Dispatched as glDispatchCompute(256, 256, 1)
60+
//
61+
// u_scaleInv = 256.0 / (frameWidth * frameHeight)
62+
// → a bin containing 1/256 of all pixels exactly fills the bar height
63+
// ---------------------------------------------------------------------------
64+
static CGEConstString s_cshHistDraw = "#version 310 es\n" CGE_SHADER_STRING(
65+
precision highp float;
66+
precision highp int;
67+
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
68+
layout(r32ui, binding = 2) readonly uniform highp uimage2D histogramImage;
69+
layout(rgba8ui, binding = 1) writeonly uniform highp uimage2D displayImage;
70+
71+
uniform float u_scaleInv; // 256.0 / (frameWidth * frameHeight)
72+
73+
void main() {
74+
ivec2 coord = ivec2(gl_GlobalInvocationID.xy);
75+
uint count = imageLoad(histogramImage, ivec2(coord.x, 0)).r;
76+
77+
// Normalise: full height when the bin contains 1/256 of total pixels
78+
uint barHeight = uint(clamp(float(count) * u_scaleInv, 0.0, 1.0) * 256.0);
79+
80+
// coord.y == 0 is the bottom of the bar (low luminance side after flip)
81+
if (uint(coord.y) < barHeight) {
82+
imageStore(displayImage, coord, uvec4(255u, 255u, 255u, 255u));
83+
} else {
84+
imageStore(displayImage, coord, uvec4(0u, 0u, 0u, 255u));
85+
}
86+
});
87+
88+
// ===========================================================================
89+
90+
namespace CGE
91+
{
92+
CGEHistogramFilter::~CGEHistogramFilter()
93+
{
94+
if (m_histogramTex != 0)
95+
{
96+
glDeleteTextures(1, &m_histogramTex);
97+
m_histogramTex = 0;
98+
}
99+
}
100+
101+
bool CGEHistogramFilter::init()
102+
{
103+
if (!m_clearProgram.initWithComputeShader(s_cshHistClear) ||
104+
!m_countProgram.initWithComputeShader(s_cshHistCount) ||
105+
!m_drawProgram.initWithComputeShader(s_cshHistDraw))
106+
{
107+
CGE_LOG_ERROR(
108+
"CGEHistogramFilter::init failed.\n"
109+
"This filter requires GLES 3.1+ (compute shaders + imageAtomicAdd).\n");
110+
CGE_LOG_ERROR(" clear shader : %s\n", s_cshHistClear);
111+
return false;
112+
}
113+
114+
setFormPosition(0.1f, 0.1f);
115+
setFormSize(0.3f, 0.3f);
116+
117+
m_drawer.reset(TextureDrawer::create());
118+
m_drawer->setFlipScale(1.0f, -1.0f); // flip to match GL coordinate system
119+
120+
m_displayTexture = std::make_unique<TextureObject>();
121+
// Allocate the 256×256 display texture (RGBA8, same as waveform uses).
122+
m_displayTexture->resize(256, 256);
123+
124+
// Create 256×1 r32ui texture for atomic luminance counters.
125+
glGenTextures(1, &m_histogramTex);
126+
glBindTexture(GL_TEXTURE_2D, m_histogramTex);
127+
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 256, 1);
128+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
129+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
130+
glBindTexture(GL_TEXTURE_2D, 0);
131+
132+
return true;
133+
}
134+
135+
void CGEHistogramFilter::render2Texture(CGEImageHandlerInterface* handler,
136+
GLuint /*srcTexture*/,
137+
GLuint /*vertexBufferID*/)
138+
{
139+
auto&& sz = handler->getOutputFBOSize();
140+
141+
// scale factor: a uniform histogram bin (1/256 of pixels) → full bar height
142+
const float scaleInv = 256.0f / static_cast<float>(sz.width * sz.height);
143+
144+
// -----------------------------------------------------------------------
145+
// Bind image units
146+
// binding 0 : source frame (rgba8ui, read-only)
147+
// binding 1 : display texture (rgba8ui, write-only)
148+
// binding 2 : histogram bins (r32ui, read-write for atomic)
149+
// -----------------------------------------------------------------------
150+
glBindImageTexture(0, handler->getTargetTextureID(), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8UI);
151+
glBindImageTexture(1, m_displayTexture->texture(), 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8UI);
152+
glBindImageTexture(2, m_histogramTex, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32UI);
153+
154+
// -----------------------------------------------------------------------
155+
// Pass 1 — clear bins
156+
// -----------------------------------------------------------------------
157+
m_clearProgram.bind();
158+
glDispatchCompute(256, 1, 1);
159+
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
160+
161+
// -----------------------------------------------------------------------
162+
// Pass 2 — count luminance values
163+
// -----------------------------------------------------------------------
164+
m_countProgram.bind();
165+
glDispatchCompute(sz.width, sz.height, 1);
166+
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
167+
168+
// -----------------------------------------------------------------------
169+
// Pass 3 — render bar chart
170+
// -----------------------------------------------------------------------
171+
m_drawProgram.bind();
172+
m_drawProgram.sendUniformf("u_scaleInv", scaleInv);
173+
glDispatchCompute(256, 256, 1);
174+
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
175+
176+
// -----------------------------------------------------------------------
177+
// Composite the display texture onto the frame
178+
// -----------------------------------------------------------------------
179+
#if USING_ALPHA
180+
glEnable(GL_BLEND);
181+
glBlendColor(1, 1, 1, 0.8f);
182+
glBlendFunc(GL_ONE, GL_ONE_MINUS_CONSTANT_ALPHA);
183+
#endif
184+
185+
handler->setAsTarget();
186+
glViewport(
187+
static_cast<GLint>(m_position[0] * sz.width),
188+
static_cast<GLint>(m_position[1] * sz.height),
189+
static_cast<GLsizei>(m_size[0] * sz.width),
190+
static_cast<GLsizei>(m_size[1] * sz.height));
191+
192+
m_drawer->drawTexture(m_displayTexture->texture());
193+
194+
#if USING_ALPHA
195+
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
196+
glDisable(GL_BLEND);
197+
#endif
198+
}
199+
200+
void CGEHistogramFilter::setFormPosition(float left, float top)
201+
{
202+
m_position = { left, top };
203+
}
204+
205+
void CGEHistogramFilter::setFormSize(float width, float height)
206+
{
207+
m_size = { width, height };
208+
}
209+
210+
} // namespace CGE
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* cgeHistogramFilter.h
3+
*
4+
* Luminance histogram overlay filter using GLES 3.1 compute shaders
5+
* with imageAtomicAdd for GPU-side accumulation.
6+
*
7+
* Rule syntax: @style hist <left> <top> <width> <height>
8+
*
9+
* e.g. "@style hist 0.05 0.05 0.25 0.25"
10+
* Renders a 256×256 histogram in the top-left corner of the frame.
11+
*/
12+
13+
#ifndef _CGE_HISTOGRAM_FILTER_H_
14+
#define _CGE_HISTOGRAM_FILTER_H_
15+
16+
#include "cgeImageFilter.h"
17+
#include "cgeTextureUtils.h"
18+
#include "cgeVec.h"
19+
20+
namespace CGE
21+
{
22+
class CGEHistogramFilter : public CGEImageFilterInterface
23+
{
24+
public:
25+
~CGEHistogramFilter() override;
26+
27+
/**
28+
* @brief Position of the top-left corner of the overlay (normalised, [0, 1]).
29+
* Defaults to (0.1, 0.1).
30+
*/
31+
void setFormPosition(float left, float top);
32+
33+
/**
34+
* @brief Size of the histogram overlay (normalised, [0, 1]).
35+
* Defaults to (0.3, 0.3).
36+
*/
37+
void setFormSize(float width, float height);
38+
39+
bool init() override;
40+
41+
void render2Texture(CGEImageHandlerInterface* handler,
42+
GLuint srcTexture,
43+
GLuint vertexBufferID) override;
44+
45+
protected:
46+
std::unique_ptr<TextureDrawer> m_drawer;
47+
std::unique_ptr<TextureObject> m_displayTexture; ///< 256×256 rgba8 — drawn onto frame
48+
49+
GLuint m_histogramTex = 0; ///< 256×1 r32ui — atomic luminance counters
50+
51+
ProgramObject m_clearProgram; ///< pass 1: zero out histogram counters
52+
ProgramObject m_countProgram; ///< pass 2: accumulate per-pixel luminance
53+
ProgramObject m_drawProgram; ///< pass 3: render bar chart into display tex
54+
55+
Vec2f m_position; ///< normalised top-left
56+
Vec2f m_size; ///< normalised width/height
57+
};
58+
} // namespace CGE
59+
60+
#endif // _CGE_HISTOGRAM_FILTER_H_

0 commit comments

Comments
 (0)