Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cgeDemo/src/main/java/org/wysaid/cgeDemo/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ public class MainActivity extends AppCompatActivity {
"",
"@curve RGB(0,255)(255,0) @style cm mapping0.jpg 80 80 8 3", // ASCII art effect
"@style waveform 0.01 0.01 0.4 0.4",
"@style hist 0.01 0.01 0.4 0.4", // Luminance histogram overlay
"@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
"@beautify face 1 480 640", //Beautify
"@adjust lut edgy_amber.png",
"@adjust lut filmstock.png",
Expand Down
5 changes: 5 additions & 0 deletions library/src/main/jni/cge/filters/cgeAdvancedEffects.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,9 @@ CGEWaveformFilter* createWaveformFilter()
{
COMMON_FUNC(CGEWaveformFilter);
}

CGEHistogramFilter* createHistogramFilter()
{
COMMON_FUNC(CGEHistogramFilter);
}
} // namespace CGE
2 changes: 2 additions & 0 deletions library/src/main/jni/cge/filters/cgeAdvancedEffects.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "cgeRandomBlurFilter.h"
#include "cgeSketchFilter.h"
#include "cgeWaveformFilter.h"
#include "cgeHistogramFilter.h"

namespace CGE
{
Expand Down Expand Up @@ -51,6 +52,7 @@ CGESketchFilter* createSketchFilter();
CGEBeautifyFilter* createBeautifyFilter();

CGEWaveformFilter* createWaveformFilter();
CGEHistogramFilter* createHistogramFilter();
} // namespace CGE

#endif
17 changes: 17 additions & 0 deletions library/src/main/jni/cge/filters/cgeDataParsingEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1110,6 +1110,23 @@ CGEImageFilterInterface* CGEDataParsingEngine::advancedStyleParser(const char* p
filter->setFormSize(width, height);
}
}
else if (strcmp(buffer, "hist") == 0)
{
float x, y, width, height;
if (sscanf(pstr, "%f%*c%f%*c%f%*c%f", &x, &y, &width, &height) < 4)
{
LOG_ERROR_PARAM(pstr);
return nullptr;
}

CGEHistogramFilter* filter = createHistogramFilter();
if (filter != nullptr)
{
proc = filter;
filter->setFormPosition(x, y);
filter->setFormSize(width, height);
}
}
else if (strcmp(buffer, "edge") == 0)
{
ADJUSTHELP_COMMON_FUNC2(pstr, CGEEdgeSobelFilter, setIntensity, setStride);
Expand Down
210 changes: 210 additions & 0 deletions library/src/main/jni/cge/filters/cgeHistogramFilter.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/*
* cgeHistogramFilter.cpp
*
* Luminance histogram overlay rendered entirely on the GPU using three
* GLES 3.1 compute-shader passes:
*
* Pass 1 (clear) — zero the 256×1 r32ui atomic-counter texture
* Pass 2 (count) — imageAtomicAdd per pixel into the counter texture
* Pass 3 (draw) — write bar-chart pixels into the 256×256 display texture
*
* The display texture is then blended onto the frame at the configured
* position / size, exactly like CGEWaveformFilter.
*
* Requirements: OpenGL ES 3.1 (compute shaders + image load/store).
*/

#include "cgeHistogramFilter.h"

#define USING_ALPHA 1 /// 80 % semi-transparent blend, same as WaveformFilter

// ---------------------------------------------------------------------------
// Pass 1 — clear the 256 histogram bins
// Dispatched as glDispatchCompute(256, 1, 1)
// ---------------------------------------------------------------------------
static CGEConstString s_cshHistClear = "#version 310 es\n" CGE_SHADER_STRING(
precision highp float;
precision highp int;
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
layout(r32ui, binding = 2) writeonly uniform highp uimage2D histogramImage;

void main() {
imageStore(histogramImage, ivec2(int(gl_GlobalInvocationID.x), 0), uvec4(0u, 0u, 0u, 0u));
});
Comment on lines +25 to +33
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use project precision macros for inline GLSL compute sources.

Line 25, Line 39, and Line 64 currently build shaders with CGE_SHADER_STRING(...) directly. Please switch these shader literals to the required precision helper macros for consistency with repository shader conventions.

As per coding guidelines: library/src/main/jni/**/*.{cpp,h,c} requires inline GLSL via CGE_SHADER_STRING_PRECISION_H() / CGE_SHADER_STRING_PRECISION_M() macros.

Also applies to: 39-55, 64-87

🧰 Tools
🪛 Cppcheck (2.19.0)

[error] 25-25: There is an unknown macro here somewhere. Configuration is required. If CGE_SHADER_STRING is a macro then please configure it.

(unknownMacro)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@library/src/main/jni/cge/filters/cgeHistogramFilter.cpp` around lines 25 -
33, Replace the raw CGE_SHADER_STRING(...) usages for the inline GLSL compute
sources with the repository precision helper macros; specifically update the
s_cshHistClear shader literal to use CGE_SHADER_STRING_PRECISION_H(...) (or
CGE_SHADER_STRING_PRECISION_M(...) if medium precision is required) and do the
same for the other shader literals in the same file (the ones around the other
comment ranges at lines ~39-55 and ~64-87); ensure you keep the exact shader
text but wrap it with the appropriate CGE_SHADER_STRING_PRECISION_* macro so the
file follows the project's precision macro convention.


// ---------------------------------------------------------------------------
// Pass 2 — accumulate luminance histogram via atomic add
// Dispatched as glDispatchCompute(frameWidth, frameHeight, 1)
// ---------------------------------------------------------------------------
static CGEConstString s_cshHistCount = "#version 310 es\n" CGE_SHADER_STRING(
precision highp float;
precision highp int;
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
layout(rgba8ui, binding = 0) uniform readonly highp uimage2D inputImageTexture;
// coherent volatile restrict are required for imageAtomicAdd
layout(r32ui, binding = 2) coherent volatile restrict uniform highp uimage2D histogramImage;

void main() {
ivec2 texCoord = ivec2(gl_GlobalInvocationID.xy);
uvec3 color = imageLoad(inputImageTexture, texCoord).rgb;

// BT.601 luminance (integer approximation kept in float for clarity)
float lum = dot(vec3(color.rgb), vec3(0.299, 0.587, 0.114));
uint bin = clamp(uint(lum), 0u, 255u);
imageAtomicAdd(histogramImage, ivec2(bin, 0), 1u);
});

// ---------------------------------------------------------------------------
// Pass 3 — render bar chart into the 256×256 display texture
// Dispatched as glDispatchCompute(256, 256, 1)
//
// u_scaleInv = 256.0 / (frameWidth * frameHeight)
// → a bin containing 1/256 of all pixels exactly fills the bar height
// ---------------------------------------------------------------------------
static CGEConstString s_cshHistDraw = "#version 310 es\n" CGE_SHADER_STRING(
precision highp float;
precision highp int;
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
layout(r32ui, binding = 2) readonly uniform highp uimage2D histogramImage;
layout(rgba8ui, binding = 1) writeonly uniform highp uimage2D displayImage;

uniform float u_scaleInv; // 256.0 / (frameWidth * frameHeight)

void main() {
ivec2 coord = ivec2(gl_GlobalInvocationID.xy);
uint count = imageLoad(histogramImage, ivec2(coord.x, 0)).r;

// Normalise: full height when the bin contains 1/256 of total pixels
uint barHeight = uint(clamp(float(count) * u_scaleInv, 0.0, 1.0) * 256.0);

// coord.y == 0 is the bottom of the bar (low luminance side after flip)
if (uint(coord.y) < barHeight) {
imageStore(displayImage, coord, uvec4(255u, 255u, 255u, 255u));
} else {
imageStore(displayImage, coord, uvec4(0u, 0u, 0u, 255u));
}
});

// ===========================================================================

namespace CGE
{
CGEHistogramFilter::~CGEHistogramFilter()
{
if (m_histogramTex != 0)
{
glDeleteTextures(1, &m_histogramTex);
m_histogramTex = 0;
}
}

bool CGEHistogramFilter::init()
{
if (!m_clearProgram.initWithComputeShader(s_cshHistClear) ||
!m_countProgram.initWithComputeShader(s_cshHistCount) ||
!m_drawProgram.initWithComputeShader(s_cshHistDraw))
{
CGE_LOG_ERROR(
"CGEHistogramFilter::init failed.\n"
"This filter requires GLES 3.1+ (compute shaders + imageAtomicAdd).\n");
CGE_LOG_ERROR(" clear shader : %s\n", s_cshHistClear);
return false;
}

setFormPosition(0.1f, 0.1f);
setFormSize(0.3f, 0.3f);

m_drawer.reset(TextureDrawer::create());
m_drawer->setFlipScale(1.0f, -1.0f); // flip to match GL coordinate system

m_displayTexture = std::make_unique<TextureObject>();
// Allocate the 256×256 display texture (RGBA8, same as waveform uses).
m_displayTexture->resize(256, 256);

// Create 256×1 r32ui texture for atomic luminance counters.
glGenTextures(1, &m_histogramTex);
glBindTexture(GL_TEXTURE_2D, m_histogramTex);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 256, 1);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glBindTexture(GL_TEXTURE_2D, 0);

return true;
}

void CGEHistogramFilter::render2Texture(CGEImageHandlerInterface* handler,
GLuint /*srcTexture*/,
GLuint /*vertexBufferID*/)
{
auto&& sz = handler->getOutputFBOSize();

// scale factor: a uniform histogram bin (1/256 of pixels) → full bar height
const float scaleInv = 256.0f / static_cast<float>(sz.width * sz.height);

// -----------------------------------------------------------------------
// Bind image units
// binding 0 : source frame (rgba8ui, read-only)
// binding 1 : display texture (rgba8ui, write-only)
// binding 2 : histogram bins (r32ui, read-write for atomic)
// -----------------------------------------------------------------------
glBindImageTexture(0, handler->getTargetTextureID(), 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8UI);
glBindImageTexture(1, m_displayTexture->texture(), 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8UI);
glBindImageTexture(2, m_histogramTex, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32UI);

// -----------------------------------------------------------------------
// Pass 1 — clear bins
// -----------------------------------------------------------------------
m_clearProgram.bind();
glDispatchCompute(256, 1, 1);
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);

// -----------------------------------------------------------------------
// Pass 2 — count luminance values
// -----------------------------------------------------------------------
m_countProgram.bind();
glDispatchCompute(sz.width, sz.height, 1);
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);

// -----------------------------------------------------------------------
// Pass 3 — render bar chart
// -----------------------------------------------------------------------
m_drawProgram.bind();
m_drawProgram.sendUniformf("u_scaleInv", scaleInv);
glDispatchCompute(256, 256, 1);
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n library/src/main/jni/cge/filters/cgeHistogramFilter.cpp | sed -n '160,210p'

Repository: wysaid/android-gpuimage-plus

Length of output: 2108


🏁 Script executed:

grep -n "m_displayTexture" library/src/main/jni/cge/filters/cgeHistogramFilter.cpp | head -20

Repository: wysaid/android-gpuimage-plus

Length of output: 342


🏁 Script executed:

grep -n "m_drawProgram" library/src/main/jni/cge/filters/cgeHistogramFilter.cpp | head -10

Repository: wysaid/android-gpuimage-plus

Length of output: 226


🏁 Script executed:

grep -n "s_cshHistDraw\|initWithComputeShader" library/src/main/jni/cge/filters/cgeHistogramFilter.cpp | head -20

Repository: wysaid/android-gpuimage-plus

Length of output: 354


🏁 Script executed:

sed -n '64,100p' library/src/main/jni/cge/filters/cgeHistogramFilter.cpp

Repository: wysaid/android-gpuimage-plus

Length of output: 1371


Add GL_TEXTURE_FETCH_BARRIER_BIT to synchronize compute image writes with texture fetches.

The compute shader writes to displayImage (binding 1) via imageStore() at line 173, but line 192 reads from the same texture via sampler. The barrier at line 174 only uses GL_SHADER_IMAGE_ACCESS_BARRIER_BIT, which synchronizes image-to-image accesses. Since the write happens via image interface and the read via texture sampler interface, GL_TEXTURE_FETCH_BARRIER_BIT is required to guarantee visibility of the compute shader writes.

🔧 Suggested fix
-    glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
+    glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_TEXTURE_FETCH_BARRIER_BIT);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_TEXTURE_FETCH_BARRIER_BIT);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@library/src/main/jni/cge/filters/cgeHistogramFilter.cpp` at line 174, The
current glMemoryBarrier call only uses GL_SHADER_IMAGE_ACCESS_BARRIER_BIT but
the compute shader writes to displayImage (binding 1) via imageStore() while
later code samples that same texture via a sampler; update the glMemoryBarrier
call in cgeHistogramFilter.cpp (the call that follows the compute imageStore to
displayImage) to include GL_TEXTURE_FETCH_BARRIER_BIT as well (bitwise OR it
with GL_SHADER_IMAGE_ACCESS_BARRIER_BIT) so texture-fetch reads see the compute
shader writes.


// -----------------------------------------------------------------------
// Composite the display texture onto the frame
// -----------------------------------------------------------------------
#if USING_ALPHA
glEnable(GL_BLEND);
glBlendColor(1, 1, 1, 0.8f);
glBlendFunc(GL_ONE, GL_ONE_MINUS_CONSTANT_ALPHA);
#endif

handler->setAsTarget();
glViewport(
static_cast<GLint>(m_position[0] * sz.width),
static_cast<GLint>(m_position[1] * sz.height),
static_cast<GLsizei>(m_size[0] * sz.width),
static_cast<GLsizei>(m_size[1] * sz.height));

m_drawer->drawTexture(m_displayTexture->texture());

#if USING_ALPHA
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glDisable(GL_BLEND);
#endif
}

void CGEHistogramFilter::setFormPosition(float left, float top)
{
m_position = { left, top };
}

void CGEHistogramFilter::setFormSize(float width, float height)
{
m_size = { width, height };
}
Comment on lines +200 to +208
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Enforce normalized bounds in histogram overlay setters.

Line 200 and Line 205 accept unrestricted values even though the API contract is normalized [0, 1]. Clamping here prevents invalid or out-of-range overlay geometry from propagating into rendering.

🔧 Suggested fix
 void CGEHistogramFilter::setFormPosition(float left, float top)
 {
-    m_position = { left, top };
+    left = left < 0.0f ? 0.0f : (left > 1.0f ? 1.0f : left);
+    top  = top  < 0.0f ? 0.0f : (top  > 1.0f ? 1.0f : top);
+    m_position = { left, top };
 }
 
 void CGEHistogramFilter::setFormSize(float width, float height)
 {
-    m_size = { width, height };
+    width  = width  < 0.0f ? 0.0f : (width  > 1.0f ? 1.0f : width);
+    height = height < 0.0f ? 0.0f : (height > 1.0f ? 1.0f : height);
+    m_size = { width, height };
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@library/src/main/jni/cge/filters/cgeHistogramFilter.cpp` around lines 200 -
208, CGEHistogramFilter's setters accept arbitrary floats but the API expects
normalized [0,1] values; update CGEHistogramFilter::setFormPosition and
CGEHistogramFilter::setFormSize to clamp incoming left/top/width/height into the
[0.0f, 1.0f] range before assigning to m_position and m_size (use std::clamp or
equivalent), ensuring any out-of-range inputs are constrained and the stored
m_position/m_size remain normalized.


} // namespace CGE
60 changes: 60 additions & 0 deletions library/src/main/jni/cge/filters/cgeHistogramFilter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* cgeHistogramFilter.h
*
* Luminance histogram overlay filter using GLES 3.1 compute shaders
* with imageAtomicAdd for GPU-side accumulation.
*
* Rule syntax: @style hist <left> <top> <width> <height>
*
* e.g. "@style hist 0.05 0.05 0.25 0.25"
* Renders a 256×256 histogram in the top-left corner of the frame.
*/

#ifndef _CGE_HISTOGRAM_FILTER_H_
#define _CGE_HISTOGRAM_FILTER_H_

#include "cgeImageFilter.h"
#include "cgeTextureUtils.h"
#include "cgeVec.h"

namespace CGE
{
class CGEHistogramFilter : public CGEImageFilterInterface
{
public:
~CGEHistogramFilter() override;

/**
* @brief Position of the top-left corner of the overlay (normalised, [0, 1]).
* Defaults to (0.1, 0.1).
*/
void setFormPosition(float left, float top);

/**
* @brief Size of the histogram overlay (normalised, [0, 1]).
* Defaults to (0.3, 0.3).
*/
void setFormSize(float width, float height);

bool init() override;

void render2Texture(CGEImageHandlerInterface* handler,
GLuint srcTexture,
GLuint vertexBufferID) override;

protected:
std::unique_ptr<TextureDrawer> m_drawer;
std::unique_ptr<TextureObject> m_displayTexture; ///< 256×256 rgba8 — drawn onto frame

GLuint m_histogramTex = 0; ///< 256×1 r32ui — atomic luminance counters

ProgramObject m_clearProgram; ///< pass 1: zero out histogram counters
ProgramObject m_countProgram; ///< pass 2: accumulate per-pixel luminance
ProgramObject m_drawProgram; ///< pass 3: render bar chart into display tex

Vec2f m_position; ///< normalised top-left
Vec2f m_size; ///< normalised width/height
};
} // namespace CGE

#endif // _CGE_HISTOGRAM_FILTER_H_
Loading