File size: 15,046 Bytes
3450e6b
 
 
 
 
 
 
 
 
 
 
 
 
23963be
 
9e6215a
 
a23ea39
4bf280a
 
3450e6b
 
 
 
 
 
 
4bf280a
3450e6b
 
 
 
4bf280a
3450e6b
4bf280a
 
 
 
 
 
 
 
 
 
501547f
 
4bf280a
 
 
 
 
 
 
 
 
 
3450e6b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23963be
3938fda
3450e6b
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426


"""
    The OpenGL specification doesn't allow you to create a context without a window,
    since it needs the pixel format that you set into the device context.
    Actually, it is necessary to have a window handler to create a "traditional" rendering context.
    It is used to fetch OpenGL information and extensions availability.
    Once you got that information, you can destroy the render context and release the "dummy" window.
    So, in this code, the window is created, the context is set to this window,
    the image result is saved to an output image file and, then, this window is released.
"""


# import glfw
import OpenGL
import ctypes
import ctypes.util
# from lucid.misc.gl.glcontext import create_opengl_context
# from lib.glcontext import create_opengl_context
import glfw
from OpenGL.GL import *
import OpenGL.GL.shaders
import numpy
from PIL import Image
import base64
from io import BytesIO
from utils.settings import set_options
from pyvirtualdisplay import Display


def image_enhance(image, exposure, saturation, contrast, brightness, gamma, shadows, highlights, whites, blacks,
         clarity, temperature, sharpness):
    # create_opengl_context((image.width,image.height))
    # Initialize glfw
    disp = Display()
    disp.start()

    if not glfw.init():
        print('error in init')
        return

    # Create window
    # Size (1, 1) for show nothing in window
    glfw.window_hint(glfw.VISIBLE, False)
    # window = glfw.create_window(1, 1, "My OpenGL window", None, None)
    window = glfw.create_window(image.width,image.height, "My OpenGL window", None, None)

    # Terminate if any issue
    if not window:
        print('error in window')
        glfw.terminate()
        return

    # Set context to window
    glfw.make_context_current(window)
    disp.stop()

    #

    # Initial data
    # Positions, colors, texture coordinates
    '''
    #           positions        colors          texture coords
    quad = [   -0.5, -0.5, 0.0,  1.0, 0.0, 0.0,  0.0, 0.0,
                0.5, -0.5, 0.0,  0.0, 1.0, 0.0,  1.0, 0.0,
                0.5,  0.5, 0.0,  0.0, 0.0, 1.0,  1.0, 1.0,
               -0.5,  0.5, 0.0,  1.0, 1.0, 1.0,  0.0, 1.0]
    '''
    #       positions      colors       texture coords
    quad = [-1., -1.,  0., 0.,
            1., -1.,  1., 0.,
            1.,  1.,  1., 1.,
            -1.,  1.,  0., 1.]
    quad = numpy.array(quad, dtype=numpy.float32)

    # Vertices indices order
    indices = [0, 1, 2,
               2, 3, 0]
    indices = numpy.array(indices, dtype=numpy.uint32)

    # print(quad.itemsize * len(quad))
    # print(indices.itemsize * len(indices))
    # print(quad.itemsize * 8)

    #

    # Vertex shader
    vertex_shader = """
    attribute vec4 a_position;
    attribute vec4 a_color;
    attribute vec2 a_texCoord;
    varying vec2 v_texCoord;
    varying vec4 v_color;

    void main() {
    gl_Position = a_position;
    v_texCoord = a_texCoord;
    v_color = vec4(a_color.rgb * a_color.a, a_color.a);
    }
    """

    # Fragment shader
    fragment_shader = """
    varying vec2 v_texCoord;
    uniform sampler2D u_image;

    uniform float u_gamma;
    uniform float u_shadows;
    uniform float u_highlights;
    uniform float u_whites;
    uniform float u_blacks;

    uniform float u_clarity;


    uniform mat4 u_colorMatrix;
    uniform vec4 u_colorOffset;

    uniform vec2 u_pixelDimension;

    uniform mat4 u_clarityMatrix;
    uniform vec4 u_clarityOffset;

    uniform float u_temperature;
    uniform float u_sharpness;


    const vec3 warmFilter = vec3(0.93, 0.54, 0.0);

    const mat3 RGBtoYIQ = mat3(0.299, 0.587, 0.114, 0.596, -0.274, -0.322, 0.212, -0.523, 0.311);
    const mat3 YIQtoRGB = mat3(1.0, 0.956, 0.621, 1.0, -0.272, -0.647, 1.0, -1.105, 1.702);


    const float EPSILON = 0.0000001;

    vec4 unpremultiply(vec4 col) {
        col.rgb /= max(col.a, EPSILON);
        return col;
    }

    float calculateLuminance(vec3 rgb) {
        // This is the luminance calculation part of the RGB to HSL formular.
        vec4 p = mix(
            vec4(rgb.gb, 0.0, -1.0 / 3.0),
            vec4(rgb.bg, -1.0, 2.0 / 3.0),
            vec4(rgb.g < rgb.b)
        );

        vec4 q = mix(
            vec4(rgb.r, p.yzx),
            vec4(p.xyw, rgb.r),
            vec4(rgb.r < p.x)
        );

        float croma = q.x - min(q.w, q.y);
        float luminance = q.x - croma * 0.5;
        return luminance;
    }

    vec3 map(vec3 x, float in_min, float in_max, float out_min, float out_max){
    return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
    }

    void main() {

    vec4 color = clamp(texture2D(u_image, v_texCoord), 0.0, 1.0);
    color.rgb /= max(color.a, EPSILON); // Revert premultiplied alpha

    // Apply gamma
    if (u_gamma != 1.0) {
        color.rgb = pow(color.rgb, vec3(1.0 / max(u_gamma, EPSILON)));
    }

    // Apply shadows and highlights
    float luminance = calculateLuminance(color.rgb);

    float shadow = u_shadows >= 0.0
        ? clamp(
            pow(luminance, 1.0 / (u_shadows + 1.0))
            + pow(luminance, 2.0 / (u_shadows + 1.0)) * -0.76
            - luminance
        , 0.0, max(u_shadows, 1.0))
        : -clamp(
            pow(luminance, 1.0 / (-u_shadows + 1.0))
            + pow(luminance, 2.0 / (-u_shadows + 1.0)) * -0.76
            - luminance
        , 0.0, max(-u_shadows, 1.0));

    float highlight = u_highlights < 0.0
        ? clamp(
            1.0
            - pow(1.0 - luminance, 1.0 / (1.0 - u_highlights))
            - pow(1.0 - luminance, 2.0 / (1.0 - u_highlights)) * -0.8
            - luminance
        , -1.0, 0.0)
        : -clamp(
            1.0
            - pow(1.0 - luminance, 1.0 / (1.0 + u_highlights))
            - pow(1.0 - luminance, 2.0 / (1.0 + u_highlights)) * -0.8
            - luminance
        , -1.0, 0.0);

    // Bright color need more contrast and dark color need more brightness.
    // This is to keep saturatation because the color information of a dark colors is lost.
    float shadowContrast   = shadow * luminance * luminance;
    float shadowBrightness = shadow - shadowContrast;

    float offset = luminance + shadowContrast + highlight;
    color.rgb = clamp(offset * ((color.rgb + shadowBrightness) / max(luminance, EPSILON)), 0.0, 1.0);
    
    // Apply Color Matrix
    color.rgb = clamp(color * u_colorMatrix + u_colorOffset, 0.0, 1.0).rgb;
    color.rgb = map(color.rgb, 0.0, 1.0, u_blacks / 2.0, 1.0 + u_whites / 2.0);
    color = clamp(color, 0.0, 1.0);
    color.rgb *= color.a; // Reset premultiplied alpha

    if (u_clarity != 0.0) {
        color = unpremultiply(color);

        // L = Left, R = Right, C = Center, T = Top, B = Bottom
        vec4 colLB = texture2D(u_image, v_texCoord + vec2(-u_pixelDimension.x, -u_pixelDimension.y));
        vec4 colLC = texture2D(u_image, v_texCoord + vec2(-u_pixelDimension.x,                 0.0));
        vec4 colLT = texture2D(u_image, v_texCoord + vec2(-u_pixelDimension.x,  u_pixelDimension.y));

        vec4 colCL = texture2D(u_image, v_texCoord + vec2(                0.0, -u_pixelDimension.y));
        vec4 colCR = texture2D(u_image, v_texCoord + vec2(                0.0,  u_pixelDimension.y));
        
        vec4 colRB = texture2D(u_image, v_texCoord + vec2( u_pixelDimension.x, -u_pixelDimension.y));
        vec4 colRC = texture2D(u_image, v_texCoord + vec2( u_pixelDimension.x,                 0.0));
        vec4 colRT = texture2D(u_image, v_texCoord + vec2( u_pixelDimension.x,  u_pixelDimension.y));

        vec4 mergedColor = color;
        mergedColor.rgb += unpremultiply(colLB).rgb + unpremultiply(colLC).rgb + unpremultiply(colLT).rgb;
        mergedColor.rgb += unpremultiply(colCL).rgb + unpremultiply(colCR).rgb;
        mergedColor.rgb += unpremultiply(colRB).rgb + unpremultiply(colRC).rgb + unpremultiply(colRT).rgb;

        mergedColor /= 9.0;

        float grayValue = clamp(color.r * 0.3 + color.g * 0.59 + color.b * 0.1, 0.111111, 0.999999);
        // 1.0 and 0.0 result in white not black, therefore we clamp

        // Here we create a function that will map values below 0.1 to 0. Values above 0.2 will be mapped to 1,
        // and for values between 0.1 and 0.2 it will produce a gradient.
        // The funtion is mirror at 0.5, meaning values between 0.8 and 0.9 will result in a decending gradient.
        // And values above 0.9 will be mapped to 0.
        float frequenceFactor = min(smoothstep(1.0 - grayValue, 0.0, 0.11), smoothstep(grayValue, 0.0, 0.11));

        // here we apply the high pass filter. Its strength is determined by the uniform ,
        // and the frequence factor. That means the only the mid tones are affected by this filter.
        // Clarity input is ranging from -1 to 1. But we want to strengthen the effect.
        // Therefore we see this little magic number '3.7'.
        color.rgb = clamp(color + clamp((color - mergedColor) * u_clarity * 3.7 * frequenceFactor, 0.0, 10.0), 0.0, 1.0).rgb;

        // apply exposure but only to the mid tones.
        color.rgb = color.rgb * pow(2.0, u_clarity * 0.27 * frequenceFactor);

        // apply contrast and desaturation matrix
        color.rgb = clamp(color * u_colorMatrix + u_colorOffset, 0.0, 1.0).rgb;

        color.rgb *= color.a; // Premultiply alpha
        color = clamp(color, 0.0, 1.0);
    }


    if(u_temperature != 0.0){
        float temperature = u_temperature;
        const float tint = 0.0;
        vec4 source = color;

        source.rgb /= max(source.a, EPSILON);   // Revert premultiplied alpha

        vec3 yiq = RGBtoYIQ * source.rgb;
        yiq.b = clamp(yiq.b + tint*0.5226*0.1, -0.5226, 0.5226);
        vec3 rgb = YIQtoRGB * yiq;

        vec3 processed = mix(
        (1.0 - 2.0 * (1.0 - rgb) * (1.0 - warmFilter)),
        (2.0 * rgb * warmFilter),
        vec3(rgb.r < 0.5, rgb.g < 0.5, rgb.b < 0.5)
        );

        color = vec4(mix(rgb, processed, temperature), source.a);

        color.rgb *= color.a;   // Premultiply alpha again

    }

    if (u_sharpness != 0.0){
        float factor        = mix(0.2, -1.0, float(u_sharpness > 0.0));
        vec4 sharpenedColor = mix(0.2,  5.0, float(u_sharpness > 0.0)) * color;
    
        sharpenedColor += factor * clamp(texture2D(u_image, v_texCoord + u_pixelDimension * vec2(-1.0,  0.0)), 0.0, 1.0);
        sharpenedColor += factor * clamp(texture2D(u_image, v_texCoord + u_pixelDimension * vec2( 0.0, -1.0)), 0.0, 1.0);
        sharpenedColor += factor * clamp(texture2D(u_image, v_texCoord + u_pixelDimension * vec2( 0.0,  1.0)), 0.0, 1.0);
        sharpenedColor += factor * clamp(texture2D(u_image, v_texCoord + u_pixelDimension * vec2( 1.0,  0.0)), 0.0, 1.0);
    
        color.rgb /= max(color.a, EPSILON); // unpremultiply
        sharpenedColor.rgb /= max(sharpenedColor.a, EPSILON); // unpremultiply
    
        sharpenedColor = clamp(sharpenedColor, 0.0, 1.0);
    
        color = clamp(mix(color, sharpenedColor, abs(u_sharpness)), 0.0, 1.0);
        
        color = vec4(color.rgb * color.a, color.a); // premultiply

    }


    gl_FragColor = color;
    }

    """

    #

    # Compile shaders
    shader = OpenGL.GL.shaders.compileProgram(OpenGL.GL.shaders.compileShader(vertex_shader, GL_VERTEX_SHADER),
                                              OpenGL.GL.shaders.compileShader(fragment_shader, GL_FRAGMENT_SHADER))

    # VBO
    v_b_o = glGenBuffers(1)
    glBindBuffer(GL_ARRAY_BUFFER, v_b_o)
    glBufferData(GL_ARRAY_BUFFER, quad.itemsize *
                 len(quad), quad, GL_STATIC_DRAW)

    # EBO
    e_b_o = glGenBuffers(1)
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, e_b_o)
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.itemsize *
                 len(indices), indices, GL_STATIC_DRAW)

    # Configure positions of initial data
    # Configure positions of initial data
    glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 *
                          sizeof(GLfloat), ctypes.c_void_p(0))
    glEnableVertexAttribArray(0)

    # Configure texture coordinates of initial data
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 *
                          sizeof(GLfloat), ctypes.c_void_p(8))
    glEnableVertexAttribArray(1)

    # Texture
    texture = glGenTextures(1)
    # Bind texture
    glBindTexture(GL_TEXTURE_2D, texture)
    # Texture wrapping params
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
    # Texture filtering params
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)

    #

    # Open image

    #
    # img_data = numpy.array(list(image.getdata()), numpy.uint8)
    #
    # flipped_image = image.transpose(Image.FLIP_TOP_BOTTOM)
    # img_data = flipped_image.convert("RGBA").tobytes()
    #
    img_data = image.convert("RGBA").tobytes()
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width,
                 image.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img_data)
    # print(image.width, image.height)

    #

    # Create render buffer with size (image.width x image.height)
    rb_obj = glGenRenderbuffers(1)
    glBindRenderbuffer(GL_RENDERBUFFER, rb_obj)
    glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, image.width, image.height)

    # Create frame buffer
    fb_obj = glGenFramebuffers(1)
    glBindFramebuffer(GL_FRAMEBUFFER, fb_obj)
    glFramebufferRenderbuffer(
        GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb_obj)

    # Check frame buffer (that simple buffer should not be an issue)
    status = glCheckFramebufferStatus(GL_FRAMEBUFFER)
    if status != GL_FRAMEBUFFER_COMPLETE:
        print("incomplete framebuffer object")

    #

    # Install program
    glUseProgram(shader)

    set_options(exposure, saturation, contrast, brightness, gamma, shadows, highlights, whites, blacks,
                clarity, temperature, sharpness, shader, image.width, image.height)

    # Bind framebuffer and set viewport size
    glBindFramebuffer(GL_FRAMEBUFFER, fb_obj)
    glViewport(0, 0, image.width, image.height)

    # Draw the quad which covers the entire viewport
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, None)

    #

    # PNG
    # Read the data and create the image
    image_buffer = glReadPixels(
        0, 0, image.width, image.height, GL_RGBA, GL_UNSIGNED_BYTE)
    image_out = numpy.frombuffer(image_buffer, dtype=numpy.uint8)
    image_out = image_out.reshape(image.height, image.width, 4)
    # glfw.terminate()
    img = Image.fromarray(image_out, 'RGBA')


    buffered = BytesIO()
    img.save(buffered, format="PNG")
    img_str = base64.b64encode(buffered.getvalue())

    return img_str



if __name__ == "__main__":
    image = Image.open("/Users/planningo/Downloads/download.jpeg")
    image_enhance(image, exposure=0, saturation=0, contrast=0, brightness=0, gamma=1, shadows=0,
         highlights=0, blacks=0, whites=0, clarity=0, temperature=-1, sharpness=1)