44
55#include " ImageMixer.hpp"
66
7+ #include < SOIL.h>
8+
9+ #include " IllegalStateException.hpp"
10+ #define ANDROID_LOG_TAG " ImageMixer++"
11+ #include " stddefs.h"
12+
13+ #include " oglshaderutils.hpp"
14+
715namespace imgmixer {
816
9- ImageMixer::ImageMixer (jni::JNIEnv &env)
10- {}
17+ static constexpr std::size_t MAX_IMAGES = 4 ;
18+ static thread_local bool is_egl_context_creator_thread = false ;
19+
20+ static const char gVertexShader [] =
21+ " #version 300 es\n "
22+ " precision mediump float;\n "
23+ " in vec2 aPosition;\n "
24+ " out vec2 vTexCoord;\n "
25+ " void main() {\n "
26+ " gl_Position = vec4(aPosition, 0.0, 1.0);\n "
27+ " vTexCoord = (aPosition + 1.0) / 2.0;\n "
28+ " }\n " ;
29+
30+ static const char gFragmentShader [] =
31+ " #version 300 es\n "
32+ " precision mediump float;\n "
33+ " uniform vec2 iResolution;\n "
34+ " uniform sampler2D iChannel0;\n "
35+ " uniform sampler2D iChannel1;\n "
36+ " uniform sampler2D iChannel2;\n "
37+ " uniform sampler2D iChannel3;\n "
38+ " in vec2 vTexCoord;\n "
39+ " out vec4 fragmentColor;\n "
40+ " void main() {\n "
41+ " vec2 uv = vTexCoord; //vec2(vTexCoord.x / 5.0, vTexCoord.y / 5.0);\n "
42+ " vec4 fragColor0 = vec4(texture(iChannel0, uv).rgb, 1.0);\n "
43+ " vec4 fragColor1 = vec4(texture(iChannel1, uv).rgb, 1.0);\n "
44+ " vec4 fragColor2 = vec4(texture(iChannel2, uv).rgb, 1.0);\n "
45+ " vec4 fragColor3 = vec4(texture(iChannel3, uv).rgb, 1.0);\n "
46+ " fragmentColor = mix(fragColor0, mix(fragColor1, mix(fragColor2, fragColor3, 0.5), 0.5), 0.5);\n "
47+ " }\n " ;
48+
49+ ImageMixer::ImageMixer (jni::JNIEnv &env, jni::jint width, jni::jint height)
50+ {
51+ // Step 1 - Get the default display.
52+ eglDisplay = eglGetDisplay (EGL_DEFAULT_DISPLAY);
53+
54+ // Step 2 - Initialize EGL.
55+ EGLint eglMajVers, eglMinVers;
56+ eglInitialize (eglDisplay, &eglMajVers, &eglMinVers);
57+ LOGI (" EGL init with version %d.%d" , eglMajVers, eglMinVers);
58+
59+
60+ // Step 3 - Make OpenGL ES the current API.
61+ eglBindAPI (EGL_OPENGL_ES_API);
62+
63+ // Step 4 - Specify the required configuration attributes.
64+ EGLint configAttr[] = {
65+ EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, // very important!
66+ EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, // we will create a pixelbuffer surface
67+ EGL_RED_SIZE, 8 ,
68+ EGL_GREEN_SIZE, 8 ,
69+ EGL_BLUE_SIZE, 8 ,
70+ EGL_NONE
71+ };
72+
73+ // Step 5 - Find a config that matches all requirements.
74+ int iConfigs;
75+ EGLConfig eglConfig;
76+ eglChooseConfig (eglDisplay, configAttr, &eglConfig, 1 ,
77+ &iConfigs);
78+
79+ if (iConfigs != 1 ) {
80+ jni::ThrowIllegalStateException (env, " Error: eglChooseConfig(): config not found" );
81+ }
82+
83+ const EGLint surfaceAttr[] = {
84+ EGL_WIDTH, width,
85+ EGL_HEIGHT, height,
86+ EGL_NONE
87+ };
88+ eglSurface = eglCreatePbufferSurface (eglDisplay, eglConfig, surfaceAttr);
89+
90+ // Step 7 - Create a context.
91+
92+ const EGLint ctxAttr[] = {
93+ EGL_CONTEXT_CLIENT_VERSION, 3 , // very important!
94+ EGL_NONE
95+ };
96+ eglContext = eglCreateContext (eglDisplay, eglConfig, NULL ,
97+ ctxAttr);
98+
99+ // Step 8 - Bind the context to the current thread
100+ eglMakeCurrent (eglDisplay, eglSurface, eglSurface, eglContext);
101+
102+ const GLubyte *glslVersion = glGetString (GL_SHADING_LANGUAGE_VERSION);
103+ LOGI (" %s" , glslVersion);
104+
105+ is_egl_context_creator_thread = true ;
106+
107+ glProgram = createProgram (gVertexShader , gFragmentShader );
108+ if (!glProgram) {
109+ jni::ThrowIllegalStateException (env, " Could not create program." );
110+ }
111+ gvPositionHandle = glGetAttribLocation (glProgram, " aPosition" );
112+ checkGlError (" glGetAttribLocation" );
113+ LOGI (" glGetAttribLocation(\" aPosition\" ) = %d\n " , gvPositionHandle);
114+
115+ glViewport (0 , 0 , width, height);
116+ checkGlError (" glViewport" );
117+
118+ glGenBuffers (1 , &quadBuffer);
119+ glBindBuffer (GL_ARRAY_BUFFER, quadBuffer);
120+ glBufferData (GL_ARRAY_BUFFER, sizeof (quadVertices), quadVertices, GL_STATIC_DRAW);
121+ glBindBuffer (GL_ARRAY_BUFFER, GL_NONE);
122+ }
11123
12124ImageMixer::~ImageMixer ()
13- {}
125+ {
126+ // TODO iterate over textures and unload them all
14127
15- jni::Local<jni::String> ImageMixer::convert (jni::JNIEnv &env,
16- jni::String& image0,
17- jni::String& image1,
18- jni::String& image2,
19- jni::String& image3)
128+ if (eglDisplay != EGL_NO_DISPLAY) {
129+ eglMakeCurrent (eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
130+ if (eglContext != EGL_NO_CONTEXT) {
131+ eglDestroyContext (eglDisplay, eglContext);
132+ }
133+ if (eglSurface != EGL_NO_SURFACE) {
134+ eglDestroySurface (eglDisplay, eglSurface);
135+ }
136+ eglTerminate (eglDisplay);
137+ }
138+
139+ eglDisplay = EGL_NO_DISPLAY;
140+ eglContext = EGL_NO_CONTEXT;
141+ eglSurface = EGL_NO_SURFACE;
142+ }
143+
144+ void ImageMixer::loadTexture (jni::JNIEnv &env, jni::String &imagePath)
20145{
21- // TODO all opengl stuff here
146+ if (!is_egl_context_creator_thread) {
147+ jni::ThrowIllegalStateException (env, " Called from thread which is different to one who created eglContext" );
148+ }
149+
150+ if (textures.size () >= MAX_IMAGES) {
151+ jni::ThrowIllegalStateException (env, " Attempt to add texture, but max supported textures exceed" );
152+ }
153+
154+ std::string image_path = jni::Make<std::string>(env, imagePath);
155+ GLuint tex = SOIL_load_OGL_texture (image_path.c_str (), SOIL_LOAD_AUTO, SOIL_CREATE_NEW_ID, SOIL_FLAG_INVERT_Y);
156+ checkGlError (" glTexImage2D" );
157+
158+ if (!tex) {
159+ jni::ThrowIllegalStateException (env, " Image cannot be loaded" );
160+ }
161+
162+ textures.push_back (tex);
163+ }
164+
165+ void ImageMixer::clearTextures (jni::JNIEnv &env)
166+ {
167+ if (!is_egl_context_creator_thread) {
168+ jni::ThrowIllegalStateException (env, " Called from thread which is different to one who created eglContext" );
169+ }
170+
171+ for (auto tex : textures) {
172+ glDeleteTextures (1 , &tex);
173+ checkGlError (" glDeleteTextures" );
174+ }
175+
176+ textures.clear ();
177+ }
178+
179+ void ImageMixer::convert (jni::JNIEnv &env, jni::String &outPath)
180+ {
181+ if (!is_egl_context_creator_thread) {
182+ jni::ThrowIllegalStateException (env, " Called from thread which is different to one who created eglContext" );
183+ }
184+ // commit the clear to the offscreen surface
185+ // eglSwapBuffers(eglDisplay, eglSurface);
186+
187+ glClearColor (1.0 , 1.0 , 1.0 , 1.0 );
188+ glClear (GL_COLOR_BUFFER_BIT);
189+
190+ glEnable (GL_BLEND);
191+ glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
192+
193+ checkGlError (" glClear" );
194+
195+ glUseProgram (glProgram);
196+ checkGlError (" glUseProgram" );
197+
198+ // bind textures
199+ for (GLuint unit = 0 ; unit < MAX_IMAGES; unit++) {
200+ if (unit < textures.size ()) {
201+ std::string uniformNameStr = " iChannel" + std::to_string (unit);
202+ GLint uniformName = glGetUniformLocation (glProgram, uniformNameStr.c_str ());
203+ checkGlError (" glGetUniformLocation" );
204+
205+ glActiveTexture (GL_TEXTURE0 + unit);
206+ checkGlError (" glActiveTexture" );
207+ glBindTexture (GL_TEXTURE_2D, textures[unit]);
208+ checkGlError (" glBindTexture" );
209+ glUniform1i (uniformName, unit);
210+ checkGlError (" glUniform1i" );
211+
212+ }
213+ }
214+ // bind resolution uniform
215+ GLint viewport[4 ];
216+
217+ glGetIntegerv (GL_VIEWPORT, viewport);
218+ GLsizei width = viewport[2 ], height = viewport[3 ];
219+ GLint resolutionName = glGetUniformLocation (glProgram, " iResolution" );
220+ checkGlError (" glGetUniformLocation" );
221+ glUniform2f (resolutionName, (GLfloat)width, (GLfloat)height);
222+ checkGlError (" glUniform2f" );
223+
224+ glEnableVertexAttribArray (gvPositionHandle);
225+ checkGlError (" glEnableVertexAttribArray" );
226+
227+ glBindBuffer (GL_ARRAY_BUFFER, quadBuffer);
228+ checkGlError (" glBindBuffer" );
229+ glVertexAttribPointer (
230+ gvPositionHandle, // attribute
231+ 2 , // number of elements per vertex, here (x,y)
232+ GL_FLOAT, // the type of each element
233+ GL_FALSE, // take our values as-is
234+ 0 , // no extra data between each position
235+ 0 // offset of first element
236+ );
237+ checkGlError (" glVertexAttribPointer" );
238+
239+ glDrawArrays (GL_TRIANGLE_STRIP, 0 , 4 );
240+ checkGlError (" glDrawArrays" );
241+
242+ glDisableVertexAttribArray (gvPositionHandle);
243+ checkGlError (" glDisableVertexAttribArray" );
244+
245+ for (GLuint unit = 0 ; unit < MAX_IMAGES; unit++) {
246+ if (unit < textures.size ()) {
247+ glActiveTexture (GL_TEXTURE0 + unit);
248+ checkGlError (" glActiveTexture" );
249+ glBindTexture (GL_TEXTURE_2D, GL_NONE);
250+ checkGlError (" glBindTexture" );
251+ }
252+ }
253+
254+ // read
255+
256+ const auto readPixels = std::get_temporary_buffer<uint32_t >(width * height);
257+ glReadPixels (viewport[0 ], viewport[1 ], viewport[2 ], viewport[3 ], GL_RGB, GL_UNSIGNED_BYTE, readPixels.first );
258+ checkGlError (" glReadPixels" );
259+
260+ glUseProgram (GL_NONE);
261+ checkGlError (" glUseProgram" );
262+
263+ // save
264+ std::string result_path = jni::Make<std::string>(env, outPath);
265+ bool success = (bool ) SOIL_save_screenshot (result_path.c_str (), SOIL_SAVE_TYPE_BMP, viewport[0 ], viewport[1 ], viewport[2 ], viewport[3 ]);
266+ std::return_temporary_buffer (readPixels.first );
22267
23- return jni::Local<jni::String>();
268+ if (!success) {
269+ jni::ThrowIllegalStateException (env, " Image cannot be saved to disk" );
270+ }
24271}
25272
26273};
@@ -35,13 +282,15 @@ extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
35282 jni::RegisterNativePeer<ImageMixer>(env,
36283 jni::Class<ImageMixer>::Find (env),
37284 " nativeHandle" ,
38- jni::MakePeer<ImageMixer>,
285+ jni::MakePeer<ImageMixer, jni::jint, jni::jint >,
39286 " initialize" ,
40287 " finalize" ,
288+ METHOD (&ImageMixer::loadTexture, " load0" ),
289+ METHOD (&ImageMixer::clearTextures, " clear" ),
41290 METHOD (&ImageMixer::convert, " convert0" )
42291 );
43292
44293#undef METHOD
45294
46- return jni::Unwrap (jni::jni_version_1_2 );
295+ return jni::Unwrap (jni::jni_version_1_6 );
47296}
0 commit comments