diff --git a/cgeDemo/src/main/java/org/wysaid/cgeDemo/MainActivity.java b/cgeDemo/src/main/java/org/wysaid/cgeDemo/MainActivity.java
index 9ab17665..00d12207 100644
--- a/cgeDemo/src/main/java/org/wysaid/cgeDemo/MainActivity.java
+++ b/cgeDemo/src/main/java/org/wysaid/cgeDemo/MainActivity.java
@@ -31,8 +31,9 @@ public class MainActivity extends AppCompatActivity {
 
     public static final String EFFECT_CONFIGS[] = {
             "",
-            "@curve RGB(0,255)(255,0) @style cm mapping0.jpg 80 80 8 3", // ASCII art (字符画效果)
+            "@style hist 0.01 0.01 0.4 0.4 0.0 0.0 0.0 0.9",
             "@style waveform 0.01 0.01 0.4 0.4",
+            "@curve RGB(0,255)(255,0) @style cm mapping0.jpg 80 80 8 3", // ASCII art (字符画效果)
             "@beautify face 1 480 640", //Beautify
             "@adjust lut edgy_amber.png",
             "@adjust lut filmstock.png",
diff --git a/library/src/main/jni/Android.mk b/library/src/main/jni/Android.mk
index b8e8c6fb..6459ed47 100644
--- a/library/src/main/jni/Android.mk
+++ b/library/src/main/jni/Android.mk
@@ -75,6 +75,7 @@ LOCAL_SRC_FILES :=  \
 			$(CGE_SOURCE)/filters/cgeEmbossFilter.cpp \
 			\
 			$(CGE_SOURCE)/filters/cgeWaveformFilter.cpp \
+			$(CGE_SOURCE)/filters/cgeHistogramFilter.cpp \
 			\
 			$(CGE_SOURCE)/filters/cgeCrosshatchFilter.cpp \
 			$(CGE_SOURCE)/filters/cgeLiquifyFilter.cpp \
diff --git a/library/src/main/jni/cge/common/cgeGLFunctions.cpp b/library/src/main/jni/cge/common/cgeGLFunctions.cpp
index fd4a53fa..ff20ff20 100644
--- a/library/src/main/jni/cge/common/cgeGLFunctions.cpp
+++ b/library/src/main/jni/cge/common/cgeGLFunctions.cpp
@@ -1,4 +1,4 @@
-/*
+/*
  * cgeGLFunctions.cpp
  *
  *  Created on: 2013-12-5
@@ -6,7 +6,7 @@
  */
 
 #include "cgeGLFunctions.h"
-
+#include <EGL/egl.h>
 #include <cmath>
 
 CGE_LOG_CODE(
@@ -390,4 +390,75 @@ bool FrameBufferWithTexture::checkStatus()
     return ret == GL_FRAMEBUFFER_COMPLETE;
 }
 
+//////////////
+
+    FrameBufferTexture::FrameBufferTexture() : m_fbo(0), m_texture(0), m_width(0), m_height(0)
+    {
+        glGenFramebuffers(1, &m_fbo);
+        assert(m_fbo != 0);
+    }
+
+    FrameBufferTexture::~FrameBufferTexture()
+    {
+        glDeleteFramebuffers(1, &m_fbo);
+        glDeleteTextures(1, &m_texture);
+    }
+
+    GLuint FrameBufferTexture::texture() const
+    {
+        return m_texture;
+    }
+
+    GLsizei FrameBufferTexture::width() const
+    {
+        return m_width;
+    }
+
+    GLsizei FrameBufferTexture::height() const
+    {
+        return m_height;
+    }
+
+    void FrameBufferTexture::bindTexture2D(GLsizei width, GLsizei height)
+    {
+        if (m_texture == 0)
+        {
+            glGenTextures(1, &m_texture);
+            assert(m_texture != 0);
+        }
+
+        glBindTexture(GL_TEXTURE_2D, m_texture);
+        glTexImage2D(GL_TEXTURE_2D, 0, GL_R32F, width, height, 0, GL_RED, GL_FLOAT, nullptr);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+        glBindTexture(GL_TEXTURE_2D, 0);
+
+        m_width = width;
+        m_height = height;
+    }
+
+    void FrameBufferTexture::bind()
+    {
+        glBindFramebuffer(GL_FRAMEBUFFER, m_fbo);
+        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_texture, 0);
+    }
+
+    unsigned char* FrameBufferTexture::mapBuffer()
+    {
+        glBindTexture(GL_TEXTURE_2D, m_texture);
+        glPixelStorei(GL_PACK_ALIGNMENT, 1);
+        const auto bufferSize = m_width * m_height * sizeof(float);
+        auto buffer = new unsigned char[bufferSize];
+        glReadPixels(0, 0, m_width, m_height, GL_RGBA, GL_UNSIGNED_BYTE, buffer);
+//        glGetTexImage(GL_TEXTURE_2D, 0, GL_RED, GL_UNSIGNED_BYTE, buffer);
+        return buffer;
+    }
+
+    void FrameBufferTexture::unmapBuffer()
+    {
+        glBindTexture(GL_TEXTURE_2D, 0);
+    }
+
 } // namespace CGE
diff --git a/library/src/main/jni/cge/common/cgeGLFunctions.h b/library/src/main/jni/cge/common/cgeGLFunctions.h
index f317cdcd..d1596248 100644
--- a/library/src/main/jni/cge/common/cgeGLFunctions.h
+++ b/library/src/main/jni/cge/common/cgeGLFunctions.h
@@ -1,4 +1,4 @@
-/*
+/*
  * cgeGLFunctions.h
  *
  *  Created on: 2013-12-5
@@ -103,13 +103,14 @@ class FrameBuffer
         bindTexture2D(texID, attachment);
         glViewport(x, y, w, h);
     }
-
     inline GLuint fbo() { return m_framebuffer; }
 
+
 protected:
     GLuint m_framebuffer;
 };
 
+
 struct CGESizei
 {
     CGESizei() :
@@ -210,6 +211,27 @@ class FrameBufferWithTexture : protected FrameBuffer, public TextureObject
     GLuint m_renderBuffer = 0;
 };
 
+class FrameBufferTexture
+    {
+    public:
+    FrameBufferTexture();
+        ~FrameBufferTexture();
+
+        GLuint texture() const;
+        GLsizei width() const;
+        GLsizei height() const;
+        void bindTexture2D(GLsizei width, GLsizei height);
+        void bind();
+        unsigned char* mapBuffer();
+        void unmapBuffer();
+
+    private:
+        GLuint m_fbo;
+        GLuint m_texture;
+        GLsizei m_width;
+        GLsizei m_height;
+};
+
 struct CGELuminance
 {
     enum
diff --git a/library/src/main/jni/cge/filters/cgeAdvancedEffects.cpp b/library/src/main/jni/cge/filters/cgeAdvancedEffects.cpp
index 8ba92182..bf161dc2 100644
--- a/library/src/main/jni/cge/filters/cgeAdvancedEffects.cpp
+++ b/library/src/main/jni/cge/filters/cgeAdvancedEffects.cpp
@@ -137,6 +137,11 @@ CGEBeautifyFilter* createBeautifyFilter()
     COMMON_FUNC(CGEBeautifyFilter);
 }
 
+CGEHistogramFilter* createHistogramFilter()
+{
+    COMMON_FUNC(CGEHistogramFilter);
+}
+
 CGEWaveformFilter* createWaveformFilter()
 {
     COMMON_FUNC(CGEWaveformFilter);
diff --git a/library/src/main/jni/cge/filters/cgeAdvancedEffects.h b/library/src/main/jni/cge/filters/cgeAdvancedEffects.h
index a5c756e4..aac44680 100644
--- a/library/src/main/jni/cge/filters/cgeAdvancedEffects.h
+++ b/library/src/main/jni/cge/filters/cgeAdvancedEffects.h
@@ -23,6 +23,7 @@
 #include "cgeRandomBlurFilter.h"
 #include "cgeSketchFilter.h"
 #include "cgeWaveformFilter.h"
+#include "cgeHistogramFilter.h"
 
 namespace CGE
 {
@@ -51,6 +52,7 @@ CGESketchFilter* createSketchFilter();
 CGEBeautifyFilter* createBeautifyFilter();
 
 CGEWaveformFilter* createWaveformFilter();
+CGEHistogramFilter* createHistogramFilter();
 } // namespace CGE
 
 #endif
diff --git a/library/src/main/jni/cge/filters/cgeDataParsingEngine.cpp b/library/src/main/jni/cge/filters/cgeDataParsingEngine.cpp
index 92fc3fb6..3bcfd6e8 100644
--- a/library/src/main/jni/cge/filters/cgeDataParsingEngine.cpp
+++ b/library/src/main/jni/cge/filters/cgeDataParsingEngine.cpp
@@ -1093,6 +1093,23 @@ CGEImageFilterInterface* CGEDataParsingEngine::advancedStyleParser(const char* p
     {
         ADJUSTHELP_COMMON_FUNC2(pstr, CGECrosshatchFilter, setCrosshatchSpacing, setLineWidth);
     }
+        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, "waveform") == 0)
     {
         float x, y, width, height;
diff --git a/library/src/main/jni/cge/filters/cgeHistogramFilter.cpp b/library/src/main/jni/cge/filters/cgeHistogramFilter.cpp
new file mode 100644
index 00000000..7e50c6ff
--- /dev/null
+++ b/library/src/main/jni/cge/filters/cgeHistogramFilter.cpp
@@ -0,0 +1,126 @@
+
+#include "cgeHistogramFilter.h"
+
+#include <EGL/egl.h>
+#include <array>
+
+static CGEConstString s_vshHistogram = "#version 320 es\n" CGE_SHADER_STRING_PRECISION_H(
+        layout(location = 0) in vec2 position;
+        layout(location = 0) out vec2 textureCoordinate;
+        void main() {
+            gl_Position = vec4(position, 0.0, 1.0);
+            textureCoordinate = (position.xy + 1.0) / 2.0;
+        });
+
+static CGEConstString s_fshHistogram = "#version 320 es\n" CGE_SHADER_STRING(
+        precision highp float;
+        precision highp int;
+        layout(location = 0) in vec2 textureCoordinate;
+        layout(rgba8ui, binding = 0) uniform readonly highp uimage2D inputImageTexture;
+        layout(rgba8ui, binding = 1) uniform writeonly highp uimage2D outputImage;
+        layout(location = 0) out vec4 fragColor;
+
+        void main() {
+            fragColor = vec4(1.0);
+
+            vec4 color = texture(inputImageTexture, textureCoordinate);
+            float lum = dot(color.rgb, vec3(0.299, 0.587, 0.114));
+            int newLoc = int(lum * 255.0);
+            if (newLoc >= 0 && newLoc < 256) {
+                //vec2 location = vec2(newLoc, 0.0);
+                ivec2 location = ivec2(newLoc, 0);
+                atomicAdd(imageAtomic(outputImage, location), 1);
+                //imageAtomic(outputImage, ivec2(location.x, location.y), uvec4(1, 0, 0, 0));
+            }
+        });
+
+namespace CGE {
+
+    CGEHistogramFilter::~CGEHistogramFilter() {}
+
+    bool CGEHistogramFilter::init() {
+        if (initShadersFromString(s_vshHistogram, s_fshHistogram)) {
+            m_program.bind();
+            setFormPosition(0.1f, 0.1f);
+            setFormSize(0.3f, 0.3f);
+            m_drawer.reset(TextureDrawer::create());
+            m_drawer->setFlipScale(1.0f, -1.0f);
+            m_renderTarget = std::make_unique<FrameBufferTexture>();
+            return true;
+        }
+
+        return false;
+    }
+
+    void CGEHistogramFilter::render2Texture(CGEImageHandlerInterface* handler, GLuint srcTexture, GLuint vertexBufferID) {
+        auto&& sz = handler->getOutputFBOSize();
+        if (sz.width != m_renderTarget->width() || m_renderTarget->texture() == 0) {
+            m_renderTarget->bindTexture2D(sz.width, sz.height);
+        }
+        m_renderTarget->bind();
+        glClearColor(0, 0, 0, 1);
+        glClear(GL_COLOR_BUFFER_BIT);
+
+        glFinish();
+
+        m_program.bind();
+        glColorMask(false, false, false, false);
+
+        glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID);
+        glEnableVertexAttribArray(0);
+        glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
+        glActiveTexture(GL_TEXTURE0);
+        glBindTexture(GL_TEXTURE_2D, srcTexture);
+
+        glUniform1i(m_program.uniformLocation("inputImageTexture"), 0);
+
+        // 获取纹理尺寸,用于计算像素位置
+        const auto texSize = handler->getOutputFBOSize();
+        // 清空直方图数组
+        std::array<float, 256> histogram{};
+        // 遍历纹理像素,计算亮度直方图
+        std::vector<unsigned char> pixels(texSize.width * texSize.height * 4);
+        glReadPixels(0, 0, texSize.width, texSize.height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
+//        glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
+        for (int i = 0; i < pixels.size(); i += 4) {
+            const auto r = pixels[i];
+            const auto g = pixels[i + 1];
+            const auto b = pixels[i + 2];
+            const auto lum = 0.299f * r + 0.587f * g + 0.114f * b;
+            histogram[static_cast<int>(lum * 255)]++;
+        }
+
+        // 将直方图数据传入图像数据,写入 GLSL Image 中
+        auto data = m_renderTarget->mapBuffer();
+        for (int i = 0; i < histogram.size(); i++) {
+            const auto height = static_cast<int>(histogram[i] * 256);
+            for (int j = 0; j < height; j++) {
+                const auto pixel = reinterpret_cast<unsigned char*>(data) + j * m_renderTarget->width() * 4 + i * 4;
+                pixel[0] = 255;
+                pixel[1] = 255;
+                pixel[2] = 255;
+                pixel[3] = 255;
+            }
+        }
+        m_renderTarget->unmapBuffer();
+
+        glViewport(m_position[0] * sz.width, m_position[1] * sz.height, m_size[0] * sz.width, m_size[1] * sz.height);
+        // 绘制直方图
+        m_drawer->drawTexture(m_renderTarget->texture());
+
+        glColorMask(true, true, true, true);
+
+        glDisableVertexAttribArray(0);
+        glBindBuffer(GL_ARRAY_BUFFER, 0);
+    }
+
+    void CGEHistogramFilter::setFormPosition(float left, float top)
+    {
+        m_position = {left, top};
+    }
+
+    void CGEHistogramFilter::setFormSize(float width, float height)
+    {
+        m_size = {width, height};
+    }
+}
diff --git a/library/src/main/jni/cge/filters/cgeHistogramFilter.h b/library/src/main/jni/cge/filters/cgeHistogramFilter.h
new file mode 100644
index 00000000..3b032b2a
--- /dev/null
+++ b/library/src/main/jni/cge/filters/cgeHistogramFilter.h
@@ -0,0 +1,29 @@
+#ifndef _HISTOGRAMFILTER_H_
+#define _HISTOGRAMFILTER_H_
+
+#include "cgeImageFilter.h"
+#include "cgeTextureUtils.h"
+#include "cgeVec.h"
+
+namespace CGE{
+class CGEHistogramFilter : public CGEImageFilterInterface
+    {
+    public:
+        ~CGEHistogramFilter() override;
+
+        void setFormPosition(float left, float top);
+
+        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<FrameBufferTexture> m_renderTarget;
+        Vec2f m_position;
+        Vec2f m_size;
+    };
+}
+#endif