summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Makefile19
-rw-r--r--debayer.fs.glsl41
-rw-r--r--debayer.vs.glsl6
-rw-r--r--glsldebayer.c314
-rwxr-xr-xlinpoly.py90
-rw-r--r--pixels.pngbin0 -> 855257 bytes
7 files changed, 472 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..effd64c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+*.o
+*.sw?
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..e4d2f8c
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,19 @@
+.PHONY: all clean allclean
+
+all: glsldebayer
+
+pixels.raw: pixels.png
+ magick $< GRAY:$@
+
+blob.o: pixels.raw debayer.vs.glsl debayer.fs.glsl
+ $(LD) -z noexecstack -r -b binary -o $@ $?
+ objcopy -R '*ABS*' --rename-section .data=.lrodata,contents,alloc,load,readonly,data $@ $@
+
+glsldebayer: LDFLAGS += -lm -lSDL2 -lEGL -lGLESv2
+glsldebayer: glsldebayer.o blob.o
+
+clean:
+ -rm *.o glsldebayer
+
+allclean: clean
+ -rm *.raw
diff --git a/debayer.fs.glsl b/debayer.fs.glsl
new file mode 100644
index 0000000..e6eace4
--- /dev/null
+++ b/debayer.fs.glsl
@@ -0,0 +1,41 @@
+uniform sampler2D u_sampler;
+uniform highp mat4 u_coef_r;
+uniform highp mat4 u_coef_g;
+uniform highp mat4 u_coef_b;
+varying highp vec2 v_position;
+
+/* Evaluate power serial polynomial of degree 16, with 0-th order term 0 */
+highp float polyeval(highp mat4 c, highp float x){
+ highp float y = c[3][3];
+ for(int i=4, j=3; 0<i; --i, j=4){ for(; 0<j; --j){
+ y = y*x + c[i-1][j-1];
+ }}
+ return y*x;
+}
+
+void main(){
+ /* Even and odd rows each contain alternating primitives of the
+ * Bayer pattern;
+ * RGRGRGRGRG
+ * GBGBGBGBGB
+ * RGRGRGRGRG
+ * GBGBGBGBGB
+ * ...
+ *
+ * The pixel data was uploaded to the texture in
+ * a way, that the even rows are in 0<x<0.5 and the odd rows
+ * in 0.5<x<1, i.e. we end up with
+ * RGRGRGRGRG GBGBGBGBGB
+ * RGRGRGRGRG GBGBGBGBGB
+ * ...
+ *
+ * Also OpenGL assumes texture origin lower left, so flip y here */
+ lowp vec2 v_pos_even = vec2(0.5*( v_position.x), 1.-v_position.y);
+ lowp vec2 v_pos_odd = vec2(0.5*(1.+v_position.x), 1.-v_position.y);
+ highp vec4 rggb = vec4(texture2D(u_sampler, v_pos_even).wx, texture2D(u_sampler, v_pos_odd ).wx);
+ highp vec3 rgb = vec3(
+ polyeval(u_coef_r, rggb[0]),
+ mix(polyeval(u_coef_g, rggb[1]), polyeval(u_coef_g, rggb[2]), 0.5),
+ polyeval(u_coef_b, rggb[3]) );
+ gl_FragColor = vec4(rgb, 1.);
+}
diff --git a/debayer.vs.glsl b/debayer.vs.glsl
new file mode 100644
index 0000000..149b18c
--- /dev/null
+++ b/debayer.vs.glsl
@@ -0,0 +1,6 @@
+attribute highp vec2 a_position;
+varying highp vec2 v_position;
+void main(){
+ v_position = a_position;
+ gl_Position = vec4(vec2(-1.)+2.*a_position, 0., 1.);
+}
diff --git a/glsldebayer.c b/glsldebayer.c
new file mode 100644
index 0000000..1e0720e
--- /dev/null
+++ b/glsldebayer.c
@@ -0,0 +1,314 @@
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+#include <EGL/egl.h>
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_syswm.h>
+
+GLuint load_gl_shader_from_sources(
+ GLenum shader_unit,
+ char const * const * const sources,
+ GLint const * const lengths )
+{
+ GLuint shader = 0;
+ GLint shader_status = GL_FALSE;
+ char *shader_infolog = NULL;
+ do {
+ size_t n_sources = 0;
+ while( sources[n_sources] ){ ++n_sources; }
+
+ shader = glCreateShader(shader_unit);
+ if( !shader ){ break; }
+ glShaderSource(shader, n_sources, sources, lengths);
+ glCompileShader(shader);
+
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &shader_status);
+ if( !shader_status ){
+ GLint log_length, returned_length;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length);
+
+ shader_infolog = malloc(log_length);
+ if( shader_infolog ){
+ memset(shader_infolog, 0, log_length);
+ glGetShaderInfoLog(
+ shader,
+ log_length,
+ &returned_length,
+ shader_infolog );
+ char const * shader_unit_str = NULL;
+ switch(shader_unit) {
+ case GL_VERTEX_SHADER: shader_unit_str = "vertex"; break;
+ case GL_FRAGMENT_SHADER: shader_unit_str = "fragment"; break;
+ }
+ fprintf(stderr,
+ "%s shader compilation failed;\n%*s",
+ shader_unit_str,
+ returned_length, shader_infolog );
+ }
+ }
+ } while(0);
+
+
+ if( !shader_status ){ glDeleteShader(shader); shader = 0; }
+ if( shader_infolog ){ free(shader_infolog); }
+
+ return shader;
+}
+
+GLuint load_gl_program_from_sources(
+ char const * const * const sources_vs,
+ GLint const * const lengths_vs,
+ char const * const * const sources_fs,
+ GLint const * const lengths_fs )
+{
+ GLint linkStatus = GL_FALSE;
+ GLuint program = 0, vert_shader = 0, frag_shader = 0;
+ char const *msg = NULL;
+ do {
+ program = glCreateProgram();
+ if( !program ){ fprintf(stderr, "glCreateProgram(): %d", glGetError()); break; }
+
+ if( sources_vs ){
+ vert_shader = load_gl_shader_from_sources(GL_VERTEX_SHADER, sources_vs, lengths_vs);
+ if( !vert_shader ){ break; }
+ glAttachShader(program, vert_shader);
+ }
+ if( sources_fs ){
+ frag_shader = load_gl_shader_from_sources(GL_FRAGMENT_SHADER, sources_fs, lengths_fs);
+ if( !frag_shader ){ break; }
+ glAttachShader(program, frag_shader);
+ }
+
+ glLinkProgram(program);
+ glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
+ if( !linkStatus ){
+ GLint log_length, returned_length;
+ glGetProgramiv(program, GL_INFO_LOG_LENGTH, &log_length);
+
+ char *program_infolog = malloc(log_length);
+ if( program_infolog ){
+ glGetProgramInfoLog(program, log_length, &returned_length, program_infolog);
+ fprintf(stderr,
+ "shader program linking failed;\n%*s",
+ returned_length, program_infolog );
+ free(program_infolog);
+ }
+ }
+ } while(0);
+
+ /* shaders are retained inside the GL context until their last holder,
+ * i.e. the shader program that was just linked is deleted. */
+ if( vert_shader ){ glDeleteShader(vert_shader); }
+ if( frag_shader ){ glDeleteShader(frag_shader); }
+
+ if( !linkStatus ){ glDeleteProgram(program); program = 0; }
+
+ return program;
+}
+
+extern char const _binary_pixels_raw_start;
+extern char const _binary_pixels_raw_end;
+#define _binary_pixels_raw ((void const*const)&_binary_pixels_raw_start)
+#define _binary_pixels_raw_size (size_t)(&_binary_pixels_raw_end - &_binary_pixels_raw_start)
+#define _binary_pixels_raw_width 1280
+#define _binary_pixels_raw_height 960
+
+extern char const _binary_debayer_vs_glsl_end;
+extern char const _binary_debayer_vs_glsl_start;
+#define _binary_debayer_vs_glsl ((void const*const)&_binary_debayer_vs_glsl_start)
+#define _binary_debayer_vs_glsl_size (size_t)(&_binary_debayer_vs_glsl_end - &_binary_debayer_vs_glsl_start)
+
+extern char const _binary_debayer_fs_glsl_end;
+extern char const _binary_debayer_fs_glsl_start;
+#define _binary_debayer_fs_glsl ((void const*const)&_binary_debayer_fs_glsl_start)
+#define _binary_debayer_fs_glsl_size (size_t)(&_binary_debayer_fs_glsl_end - &_binary_debayer_fs_glsl_start)
+
+static GLenum draw_debayer_RGB(GLuint texture)
+{
+#if 1
+/* For the moment, just perform 1st degree unity mapping */
+GLfloat const coef_r[16] = {
+ 1., 0., 0., 0.,
+ 0., 0., 0., 0.,
+ 0., 0., 0., 0.,
+ 0., 0., 0., 0. };
+GLfloat const *const coef_g = coef_r;
+GLfloat const *const coef_b = coef_r;
+
+/* Those coefficients seem to be way off. Need to investigate further. */
+#elif 0
+GLfloat const coef_r[16] = {
+ 4.41898209e-01, -1.08617799e-01, -5.05377382e+00, 9.52185120e+00,
+ 2.05420594e+02, -6.17439055e+02, -2.71301881e+03, 1.26291235e+04,
+ 4.06578493e+03, -9.34105168e+04, 1.33991823e+05, 1.27240329e+05,
+ -5.67983982e+05, 6.77352564e+05, -3.70453281e+05, 7.96913561e+04 };
+
+GLfloat const coef_g[16] = {
+ 1.75630153e-01, 1.77970251e-01, 3.93921859e+00, -3.83900750e+01,
+ -8.50868580e+01, 1.39497351e+03, -1.50332837e+03, -1.85003596e+04,
+ 6.53562460e+04, -1.00909757e+04, -3.92586418e+05, 1.06909568e+06,
+ -1.42125035e+06, 1.07029531e+06, -4.38315295e+05, 7.62246706e+04 };
+
+GLfloat const coef_b[16] = {
+ 2.44126153e-01, 1.71659546e-01, 5.35993954e-01, -8.10729044e+00,
+ -7.11102399e+00, 1.24421866e+02, 1.22255723e-02, -8.74245656e+02,
+ 4.72294698e+02, 2.99248407e+03, -2.85191269e+03, -4.54081854e+03,
+ 6.46045466e+03, 1.41007972e+03, -5.12551110e+03, 1.94825904e+03 };
+#else
+GLfloat const coef_r[16] = {
+ 2.43242336e+00, 1.29527225e+01, -1.67053874e+02, -1.03264624e+03,
+ 3.89533658e+04, -3.79431617e+05, 2.04443576e+06, -7.12156509e+06,
+ 1.71198465e+07, -2.92874193e+07, 3.60398126e+07, -3.17297224e+07,
+ 1.95237028e+07, -7.98040387e+06, 1.94790465e+06, -2.14928147e+05 };
+
+GLfloat const coef_g[16] = {
+ 5.24353947e+00, 2.96584994e+01, -1.73865606e+02, -1.93045987e+03,
+ 1.85513793e+04, -4.48754651e+04, -1.32006487e+05, 1.24471739e+06,
+ -4.25234811e+06, 8.83297659e+06, -1.23501912e+07, 1.19377860e+07,
+ -7.90632536e+06, 3.43589308e+06, -8.84463954e+05, 1.02356618e+05 };
+
+GLfloat const coef_b[16] = {
+ 4.04493164e+00, -1.46245348e+01, 1.71095269e+02, 2.06498987e+03,
+ -4.93803069e+04, 3.95481675e+05, -1.82105431e+06, 5.53531911e+06,
+ -1.17786850e+07, 1.80202854e+07, -1.99760831e+07, 1.59213215e+07,
+ -8.89476364e+06, 3.30494584e+06, -7.32930586e+05, 7.33188936e+04 };
+#endif
+
+ static GLuint prog = 0, a_position, u_sampler, u_coef_r, u_coef_g, u_coef_b;
+ if( !prog ){
+ char const *src_vs[] = { _binary_debayer_vs_glsl, NULL };
+ GLint const sz_vs[] = { _binary_debayer_vs_glsl_size, 0 };
+ char const *src_fs[] = { _binary_debayer_fs_glsl, NULL };
+ GLint const sz_fs[] = { _binary_debayer_fs_glsl_size, 0 };
+ prog = load_gl_program_from_sources(src_vs, sz_vs, src_fs, sz_fs);
+ if( !prog ){ _Exit(1); return GL_INVALID_VALUE; }
+ a_position = glGetAttribLocation(prog, "a_position");
+ u_sampler = glGetUniformLocation(prog, "u_sampler");
+ u_coef_r = glGetUniformLocation(prog, "u_coef_r");
+ u_coef_g = glGetUniformLocation(prog, "u_coef_g");
+ u_coef_b = glGetUniformLocation(prog, "u_coef_b");
+ }
+
+ static GLuint vbo_xy = 0;
+ if( !vbo_xy ){
+ // a triangle that fully covers the rectangle
+ // ((0,0),(1,1)) in counter clock vertex order.
+ GLfloat const data[] = {
+ 0.0, 0.0,
+ 2.0, 0.0,
+ 0.0, 2.0
+ };
+ glGenBuffers(1, &vbo_xy);
+ if( !vbo_xy ){ return glGetError(); }
+ glBindBuffer(GL_ARRAY_BUFFER, vbo_xy);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ }
+
+ static GLuint tex;
+ if( !tex ){
+ glGenTextures(1, &tex);
+ if( !tex ){ return glGetError(); }
+ glBindTexture(GL_TEXTURE_2D, tex);
+ if( !(_binary_pixels_raw_width % 4) ){
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
+ } else
+ if( !(_binary_pixels_raw_width % 2) ){
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 2);
+ } else {
+ glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+ }
+ /* when loading the pixels into the texture, each two consecutive
+ * primitive pixels are coalesced into a single 2-channel texel.
+ * Furthermore, since primitives order are distinct between
+ * even and odd rows, a texture half the primitive height is created
+ * but at the primitive picture width. Hence we will end up a
+ * texture that contains two pictures side by side, even rows
+ * on the left and odd rows on the right. Since the subpictures
+ * have uniform primitives arrangement each, we can use built-in
+ * mipmap generation to downsample the picture for mipmapping
+ * without impeding the de-Bayering process. */
+ glTexImage2D(
+ /* target = */ GL_TEXTURE_2D,
+ /* level = */ 0,
+ /* The 2 component image format of OpenGL-ES 2
+ * is GL_LUMINANCE_ALPHA; on later versions you'd
+ * use GL_RG; internalformat = */ GL_LUMINANCE_ALPHA,
+ /* width = */ _binary_pixels_raw_width,
+ /* height = */ _binary_pixels_raw_height/2,
+ /* border = */ 0,
+ /* format = */ GL_LUMINANCE_ALPHA,
+ /* type = */ GL_UNSIGNED_BYTE,
+ /* data = */ _binary_pixels_raw );
+ glGenerateMipmap(GL_TEXTURE_2D);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ }
+
+ glUseProgram(prog);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, tex);
+ glUniform1i(u_sampler, 0);
+ glUniformMatrix4fv(u_coef_r, 1, GL_FALSE, coef_r);
+ glUniformMatrix4fv(u_coef_g, 1, GL_FALSE, coef_g);
+ glUniformMatrix4fv(u_coef_b, 1, GL_FALSE, coef_b);
+ glBindBuffer(GL_ARRAY_BUFFER, vbo_xy);
+ glEnableVertexAttribArray(a_position);
+ glVertexAttribPointer(a_position, 2, GL_FLOAT, GL_FALSE, 0, NULL);
+ glDrawArrays(GL_TRIANGLES, 0, 3);
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glBindTexture(GL_TEXTURE_2D, 0);
+ glUseProgram(0);
+
+ return glGetError();
+}
+
+int main()
+{
+ int screenwidth = _binary_pixels_raw_width;
+ int screenheight = _binary_pixels_raw_height;
+
+ EGLint numConfigs, majorVersion, minorVersion;
+ SDL_Window *window = SDL_CreateWindow(
+ "GLESv2 / fragment shader de-Bayer", 0, 0,
+ screenwidth, screenheight, SDL_WINDOW_OPENGL);
+
+ EGLConfig config = {0};
+ EGLint const egl_config_attr[] = {
+ EGL_BUFFER_SIZE, 24,
+ EGL_DEPTH_SIZE, 24,
+ EGL_STENCIL_SIZE, 8,
+ EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
+ EGL_NONE
+ };
+ EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ eglInitialize(display, &majorVersion, &minorVersion);
+ eglChooseConfig(display, egl_config_attr, &config, 1, &numConfigs);
+
+ SDL_SysWMinfo sysInfo;
+ SDL_VERSION(&sysInfo.version);
+ SDL_GetWindowWMInfo(window, &sysInfo);
+
+ EGLint const egl_context_attr[] = {
+ EGL_CONTEXT_MAJOR_VERSION, 2,
+ EGL_NONE
+ };
+ EGLContext context = eglCreateContext(display, config, EGL_NO_CONTEXT, egl_context_attr);
+ EGLSurface surface = eglCreateWindowSurface(display, config, (EGLNativeWindowType)sysInfo.info.x11.window, 0); // X11?
+ eglMakeCurrent(display, surface, surface, context);
+ eglSwapInterval(display, 1);
+
+ for( SDL_Event event = {0}
+ ; SDL_QUIT != event.type
+ ; SDL_PollEvent(&event)
+ ){
+ glViewport(0, 0, screenwidth, screenheight);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+ draw_debayer_RGB(0);
+ eglSwapBuffers(display, surface);
+ }
+
+ eglDestroySurface(display, surface);
+ eglDestroyContext(display, context);
+ eglTerminate(display);
+ SDL_DestroyWindow(window);
+ return 0;
+}
diff --git a/linpoly.py b/linpoly.py
new file mode 100755
index 0000000..3419a07
--- /dev/null
+++ b/linpoly.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env -S python3
+import numpy
+from numpy.polynomial import Polynomial, Chebyshev
+from matplotlib.pyplot import *
+from scipy.signal import windows
+from scipy import interpolate
+
+measured = numpy.asarray([
+ [1.0000, 0.9241, 0.9978, 0.9802],
+ [0.9666, 0.9209, 0.9966, 0.9774],
+ [0.9330, 0.9174, 0.9952, 0.9746],
+ [0.9000, 0.9135, 0.9936, 0.9715],
+ [0.8666, 0.9098, 0.9916, 0.9680],
+ [0.8333, 0.9057, 0.9893, 0.9646],
+ [0.8000, 0.9011, 0.9867, 0.9609],
+ [0.7666, 0.8968, 0.9834, 0.9569],
+ [0.7333, 0.8920, 0.9801, 0.9528],
+ [0.7000, 0.8869, 0.9764, 0.9483],
+ [0.6666, 0.8813, 0.9724, 0.9433],
+ [0.6333, 0.8753, 0.9681, 0.9384],
+ [0.6000, 0.8691, 0.9632, 0.9325],
+ [0.5666, 0.8622, 0.9580, 0.9264],
+ [0.5333, 0.8551, 0.9522, 0.9199],
+ [0.5000, 0.8471, 0.9464, 0.9129],
+ [0.4666, 0.8370, 0.9393, 0.9051],
+ [0.4333, 0.8234, 0.9321, 0.8964],
+ [0.4000, 0.8053, 0.9239, 0.8871],
+ [0.3666, 0.7829, 0.9143, 0.8763],
+ [0.3333, 0.7557, 0.9038, 0.8641],
+ [0.3000, 0.7223, 0.8917, 0.8500],
+ [0.2666, 0.6789, 0.8777, 0.8295],
+ [0.2333, 0.6224, 0.8610, 0.7973],
+ [0.2000, 0.5434, 0.8399, 0.7504],
+ [0.1666, 0.4629, 0.7992, 0.6823],
+ [0.1333, 0.3816, 0.7318, 0.5687],
+ [0.1000, 0.3009, 0.6064, 0.4406],
+ [0.0666, 0.2207, 0.4530, 0.3140],
+ [0.0333, 0.1400, 0.2403, 0.1872],
+ [0.0000, 0.0625, 0.0649, 0.0629],
+])
+
+xs = measured[:,0]
+xs_ = numpy.linspace(-0.10, 1.15, 100)
+
+tukey = windows.tukey(measured.shape[0]);
+
+int_r = interpolate.PchipInterpolator(numpy.flip(xs), numpy.flip(measured[:,1]), extrapolate=True)
+int_g = interpolate.PchipInterpolator(numpy.flip(xs), numpy.flip(measured[:,2]), extrapolate=True)
+int_b = interpolate.PchipInterpolator(numpy.flip(xs), numpy.flip(measured[:,3]), extrapolate=True)
+
+off_r = int_r(0)
+off_g = int_g(0)
+off_b = int_b(0)
+
+k = 1./max( int_r(1)-off_r, int_g(1)-off_g, int_b(1)-off_b )
+
+fit_r = Polynomial.fit(xs_, k*(int_r(xs_)-off_r), deg=16, domain=[0,1], window=[0,1], w=windows.tukey(xs_.shape[0]))
+fit_g = Polynomial.fit(xs_, k*(int_g(xs_)-off_g), deg=16, domain=[0,1], window=[0,1], w=windows.tukey(xs_.shape[0]))
+fit_b = Polynomial.fit(xs_, k*(int_b(xs_)-off_b), deg=16, domain=[0,1], window=[0,1], w=windows.tukey(xs_.shape[0]))
+
+def c_arr_print(label, arr):
+ print( label+"[] = {" + ', '.join([f'\n\t{v:e}' if 0 == i%4 else f'{v:e}' for i,v in enumerate(arr)]) + "};\n" )
+
+c_arr_print('GLfloat const coef_r', fit_r.convert().coef[1:])
+c_arr_print('GLfloat const coef_g', fit_g.convert().coef[1:])
+c_arr_print('GLfloat const coef_b', fit_b.convert().coef[1:])
+
+def polyeval(c, xs):
+ C_ = numpy.flip(c)
+ ys = numpy.ones(xs.size)*C_[0]
+ for c in C_[1:]:
+ ys = ys*xs + c
+ return ys*xs
+
+if True:
+ plot(xs, measured[:,1], '+')
+ plot(xs_, fit_r(xs_))
+ plot(xs_, polyeval(fit_r.convert().coef[1:], xs_), '*')
+
+ plot(xs, measured[:,2], '+')
+ plot(xs_, fit_g(xs_))
+ plot(xs_, polyeval(fit_g.convert().coef[1:], xs_), '*')
+
+ plot(xs, measured[:,3], '+')
+ plot(xs_, fit_b(xs_))
+ plot(xs_, polyeval(fit_b.convert().coef[1:], xs_), '*')
+
+ xlim(0,1)
+ ylim(0,1)
+ show()
diff --git a/pixels.png b/pixels.png
new file mode 100644
index 0000000..d96c1dd
--- /dev/null
+++ b/pixels.png
Binary files differ