Spaces:
Paused
Paused
Suprhimp
commited on
Commit
·
3450e6b
1
Parent(s):
2262b5b
first commit
Browse files- Dockerfile +11 -0
- main.py +42 -0
- opengl.py +412 -0
- requirments.txt +7 -0
- utils/ColorMatrix.py +234 -0
- utils/dto.py +17 -0
- utils/s3.py +14 -0
- utils/settings.py +77 -0
Dockerfile
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.10
|
| 2 |
+
|
| 3 |
+
WORKDIR /code
|
| 4 |
+
|
| 5 |
+
COPY ./requirements.txt /code/requirements.txt
|
| 6 |
+
|
| 7 |
+
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
| 8 |
+
|
| 9 |
+
COPY . .
|
| 10 |
+
|
| 11 |
+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860", "--worker", "2"]
|
main.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Annotated
|
| 2 |
+
|
| 3 |
+
from fastapi import FastAPI, File, HTTPException
|
| 4 |
+
from fastapi.responses import StreamingResponse
|
| 5 |
+
from starlette.middleware.cors import CORSMiddleware
|
| 6 |
+
from opengl import image_enhance
|
| 7 |
+
|
| 8 |
+
from utils.dto import ToneUpData
|
| 9 |
+
from utils.s3 import get_image_from_s3
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
app = FastAPI()
|
| 13 |
+
cors_options = {
|
| 14 |
+
"allow_methods": ["*"],
|
| 15 |
+
"allow_headers": ["*"],
|
| 16 |
+
"allow_credentials": True,
|
| 17 |
+
"allow_origins": [
|
| 18 |
+
"https://www.photio.io",
|
| 19 |
+
"https://dev.photio.io",
|
| 20 |
+
"http://localhost:3000",
|
| 21 |
+
"http://localhost",
|
| 22 |
+
"http://172.30.1.10:3000",
|
| 23 |
+
],
|
| 24 |
+
}
|
| 25 |
+
app.add_middleware(CORSMiddleware, **cors_options)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
@app.get("/")
|
| 29 |
+
def read_root():
|
| 30 |
+
return {"Hello": "World!"}
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
@app.post("/tone-up")
|
| 34 |
+
async def remove_background(items:ToneUpData):
|
| 35 |
+
try:
|
| 36 |
+
image = get_image_from_s3(items.image_id)
|
| 37 |
+
out_image = image_enhance(image,items.exposure,items.saturation,items.contrast,items.brightness,items.gamma,items.shadows,items.highlights,items.whites,items.blacks,items.clarity,items.temperature,items.sharpness)
|
| 38 |
+
# csv_to_r2(output_image,image_id)
|
| 39 |
+
return {"result": out_image}
|
| 40 |
+
except RuntimeError as e:
|
| 41 |
+
raise HTTPException(status_code=422, detail=f"error occur: {e}") from e
|
| 42 |
+
|
opengl.py
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
|
| 3 |
+
"""
|
| 4 |
+
The OpenGL specification doesn't allow you to create a context without a window,
|
| 5 |
+
since it needs the pixel format that you set into the device context.
|
| 6 |
+
Actually, it is necessary to have a window handler to create a "traditional" rendering context.
|
| 7 |
+
It is used to fetch OpenGL information and extensions availability.
|
| 8 |
+
Once you got that information, you can destroy the render context and release the "dummy" window.
|
| 9 |
+
So, in this code, the window is created, the context is set to this window,
|
| 10 |
+
the image result is saved to an output image file and, then, this window is released.
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
import glfw
|
| 15 |
+
from OpenGL.GL import *
|
| 16 |
+
import OpenGL.GL.shaders
|
| 17 |
+
import numpy
|
| 18 |
+
from PIL import Image
|
| 19 |
+
import base64
|
| 20 |
+
from io import BytesIO
|
| 21 |
+
from utils.settings import set_options
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def image_enhance(image, exposure, saturation, contrast, brightness, gamma, shadows, highlights, whites, blacks,
|
| 25 |
+
clarity, temperature, sharpness):
|
| 26 |
+
# Initialize glfw
|
| 27 |
+
if not glfw.init():
|
| 28 |
+
print('error in init')
|
| 29 |
+
return
|
| 30 |
+
|
| 31 |
+
# Create window
|
| 32 |
+
# Size (1, 1) for show nothing in window
|
| 33 |
+
window = glfw.create_window(1, 1, "My OpenGL window", None, None)
|
| 34 |
+
# window = glfw.create_window(800, 600, "My OpenGL window", None, None)
|
| 35 |
+
|
| 36 |
+
# Terminate if any issue
|
| 37 |
+
if not window:
|
| 38 |
+
print('error in window')
|
| 39 |
+
glfw.terminate()
|
| 40 |
+
return
|
| 41 |
+
|
| 42 |
+
# Set context to window
|
| 43 |
+
glfw.make_context_current(window)
|
| 44 |
+
|
| 45 |
+
#
|
| 46 |
+
|
| 47 |
+
# Initial data
|
| 48 |
+
# Positions, colors, texture coordinates
|
| 49 |
+
'''
|
| 50 |
+
# positions colors texture coords
|
| 51 |
+
quad = [ -0.5, -0.5, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0,
|
| 52 |
+
0.5, -0.5, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0,
|
| 53 |
+
0.5, 0.5, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0,
|
| 54 |
+
-0.5, 0.5, 0.0, 1.0, 1.0, 1.0, 0.0, 1.0]
|
| 55 |
+
'''
|
| 56 |
+
# positions colors texture coords
|
| 57 |
+
quad = [-1., -1., 0., 0.,
|
| 58 |
+
1., -1., 1., 0.,
|
| 59 |
+
1., 1., 1., 1.,
|
| 60 |
+
-1., 1., 0., 1.]
|
| 61 |
+
quad = numpy.array(quad, dtype=numpy.float32)
|
| 62 |
+
|
| 63 |
+
# Vertices indices order
|
| 64 |
+
indices = [0, 1, 2,
|
| 65 |
+
2, 3, 0]
|
| 66 |
+
indices = numpy.array(indices, dtype=numpy.uint32)
|
| 67 |
+
|
| 68 |
+
# print(quad.itemsize * len(quad))
|
| 69 |
+
# print(indices.itemsize * len(indices))
|
| 70 |
+
# print(quad.itemsize * 8)
|
| 71 |
+
|
| 72 |
+
#
|
| 73 |
+
|
| 74 |
+
# Vertex shader
|
| 75 |
+
vertex_shader = """
|
| 76 |
+
attribute vec4 a_position;
|
| 77 |
+
attribute vec4 a_color;
|
| 78 |
+
attribute vec2 a_texCoord;
|
| 79 |
+
varying vec2 v_texCoord;
|
| 80 |
+
varying vec4 v_color;
|
| 81 |
+
|
| 82 |
+
void main() {
|
| 83 |
+
gl_Position = a_position;
|
| 84 |
+
v_texCoord = a_texCoord;
|
| 85 |
+
v_color = vec4(a_color.rgb * a_color.a, a_color.a);
|
| 86 |
+
}
|
| 87 |
+
"""
|
| 88 |
+
|
| 89 |
+
# Fragment shader
|
| 90 |
+
fragment_shader = """
|
| 91 |
+
varying vec2 v_texCoord;
|
| 92 |
+
uniform sampler2D u_image;
|
| 93 |
+
|
| 94 |
+
uniform float u_gamma;
|
| 95 |
+
uniform float u_shadows;
|
| 96 |
+
uniform float u_highlights;
|
| 97 |
+
uniform float u_whites;
|
| 98 |
+
uniform float u_blacks;
|
| 99 |
+
|
| 100 |
+
uniform float u_clarity;
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
uniform mat4 u_colorMatrix;
|
| 104 |
+
uniform vec4 u_colorOffset;
|
| 105 |
+
|
| 106 |
+
uniform vec2 u_pixelDimension;
|
| 107 |
+
|
| 108 |
+
uniform mat4 u_clarityMatrix;
|
| 109 |
+
uniform vec4 u_clarityOffset;
|
| 110 |
+
|
| 111 |
+
uniform float u_temperature;
|
| 112 |
+
uniform float u_sharpness;
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
const vec3 warmFilter = vec3(0.93, 0.54, 0.0);
|
| 116 |
+
|
| 117 |
+
const mat3 RGBtoYIQ = mat3(0.299, 0.587, 0.114, 0.596, -0.274, -0.322, 0.212, -0.523, 0.311);
|
| 118 |
+
const mat3 YIQtoRGB = mat3(1.0, 0.956, 0.621, 1.0, -0.272, -0.647, 1.0, -1.105, 1.702);
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
const float EPSILON = 0.0000001;
|
| 122 |
+
|
| 123 |
+
vec4 unpremultiply(vec4 col) {
|
| 124 |
+
col.rgb /= max(col.a, EPSILON);
|
| 125 |
+
return col;
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
float calculateLuminance(vec3 rgb) {
|
| 129 |
+
// This is the luminance calculation part of the RGB to HSL formular.
|
| 130 |
+
vec4 p = mix(
|
| 131 |
+
vec4(rgb.gb, 0.0, -1.0 / 3.0),
|
| 132 |
+
vec4(rgb.bg, -1.0, 2.0 / 3.0),
|
| 133 |
+
vec4(rgb.g < rgb.b)
|
| 134 |
+
);
|
| 135 |
+
|
| 136 |
+
vec4 q = mix(
|
| 137 |
+
vec4(rgb.r, p.yzx),
|
| 138 |
+
vec4(p.xyw, rgb.r),
|
| 139 |
+
vec4(rgb.r < p.x)
|
| 140 |
+
);
|
| 141 |
+
|
| 142 |
+
float croma = q.x - min(q.w, q.y);
|
| 143 |
+
float luminance = q.x - croma * 0.5;
|
| 144 |
+
return luminance;
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
vec3 map(vec3 x, float in_min, float in_max, float out_min, float out_max){
|
| 148 |
+
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
void main() {
|
| 152 |
+
|
| 153 |
+
vec4 color = clamp(texture2D(u_image, v_texCoord), 0.0, 1.0);
|
| 154 |
+
color.rgb /= max(color.a, EPSILON); // Revert premultiplied alpha
|
| 155 |
+
|
| 156 |
+
// Apply gamma
|
| 157 |
+
if (u_gamma != 1.0) {
|
| 158 |
+
color.rgb = pow(color.rgb, vec3(1.0 / max(u_gamma, EPSILON)));
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
// Apply shadows and highlights
|
| 162 |
+
float luminance = calculateLuminance(color.rgb);
|
| 163 |
+
|
| 164 |
+
float shadow = u_shadows >= 0.0
|
| 165 |
+
? clamp(
|
| 166 |
+
pow(luminance, 1.0 / (u_shadows + 1.0))
|
| 167 |
+
+ pow(luminance, 2.0 / (u_shadows + 1.0)) * -0.76
|
| 168 |
+
- luminance
|
| 169 |
+
, 0.0, max(u_shadows, 1.0))
|
| 170 |
+
: -clamp(
|
| 171 |
+
pow(luminance, 1.0 / (-u_shadows + 1.0))
|
| 172 |
+
+ pow(luminance, 2.0 / (-u_shadows + 1.0)) * -0.76
|
| 173 |
+
- luminance
|
| 174 |
+
, 0.0, max(-u_shadows, 1.0));
|
| 175 |
+
|
| 176 |
+
float highlight = u_highlights < 0.0
|
| 177 |
+
? clamp(
|
| 178 |
+
1.0
|
| 179 |
+
- pow(1.0 - luminance, 1.0 / (1.0 - u_highlights))
|
| 180 |
+
- pow(1.0 - luminance, 2.0 / (1.0 - u_highlights)) * -0.8
|
| 181 |
+
- luminance
|
| 182 |
+
, -1.0, 0.0)
|
| 183 |
+
: -clamp(
|
| 184 |
+
1.0
|
| 185 |
+
- pow(1.0 - luminance, 1.0 / (1.0 + u_highlights))
|
| 186 |
+
- pow(1.0 - luminance, 2.0 / (1.0 + u_highlights)) * -0.8
|
| 187 |
+
- luminance
|
| 188 |
+
, -1.0, 0.0);
|
| 189 |
+
|
| 190 |
+
// Bright color need more contrast and dark color need more brightness.
|
| 191 |
+
// This is to keep saturatation because the color information of a dark colors is lost.
|
| 192 |
+
float shadowContrast = shadow * luminance * luminance;
|
| 193 |
+
float shadowBrightness = shadow - shadowContrast;
|
| 194 |
+
|
| 195 |
+
float offset = luminance + shadowContrast + highlight;
|
| 196 |
+
color.rgb = clamp(offset * ((color.rgb + shadowBrightness) / max(luminance, EPSILON)), 0.0, 1.0);
|
| 197 |
+
|
| 198 |
+
// Apply Color Matrix
|
| 199 |
+
color.rgb = clamp(color * u_colorMatrix + u_colorOffset, 0.0, 1.0).rgb;
|
| 200 |
+
color.rgb = map(color.rgb, 0.0, 1.0, u_blacks / 2.0, 1.0 + u_whites / 2.0);
|
| 201 |
+
color = clamp(color, 0.0, 1.0);
|
| 202 |
+
color.rgb *= color.a; // Reset premultiplied alpha
|
| 203 |
+
|
| 204 |
+
if (u_clarity != 0.0) {
|
| 205 |
+
color = unpremultiply(color);
|
| 206 |
+
|
| 207 |
+
// L = Left, R = Right, C = Center, T = Top, B = Bottom
|
| 208 |
+
vec4 colLB = texture2D(u_image, v_texCoord + vec2(-u_pixelDimension.x, -u_pixelDimension.y));
|
| 209 |
+
vec4 colLC = texture2D(u_image, v_texCoord + vec2(-u_pixelDimension.x, 0.0));
|
| 210 |
+
vec4 colLT = texture2D(u_image, v_texCoord + vec2(-u_pixelDimension.x, u_pixelDimension.y));
|
| 211 |
+
|
| 212 |
+
vec4 colCL = texture2D(u_image, v_texCoord + vec2( 0.0, -u_pixelDimension.y));
|
| 213 |
+
vec4 colCR = texture2D(u_image, v_texCoord + vec2( 0.0, u_pixelDimension.y));
|
| 214 |
+
|
| 215 |
+
vec4 colRB = texture2D(u_image, v_texCoord + vec2( u_pixelDimension.x, -u_pixelDimension.y));
|
| 216 |
+
vec4 colRC = texture2D(u_image, v_texCoord + vec2( u_pixelDimension.x, 0.0));
|
| 217 |
+
vec4 colRT = texture2D(u_image, v_texCoord + vec2( u_pixelDimension.x, u_pixelDimension.y));
|
| 218 |
+
|
| 219 |
+
vec4 mergedColor = color;
|
| 220 |
+
mergedColor.rgb += unpremultiply(colLB).rgb + unpremultiply(colLC).rgb + unpremultiply(colLT).rgb;
|
| 221 |
+
mergedColor.rgb += unpremultiply(colCL).rgb + unpremultiply(colCR).rgb;
|
| 222 |
+
mergedColor.rgb += unpremultiply(colRB).rgb + unpremultiply(colRC).rgb + unpremultiply(colRT).rgb;
|
| 223 |
+
|
| 224 |
+
mergedColor /= 9.0;
|
| 225 |
+
|
| 226 |
+
float grayValue = clamp(color.r * 0.3 + color.g * 0.59 + color.b * 0.1, 0.111111, 0.999999);
|
| 227 |
+
// 1.0 and 0.0 result in white not black, therefore we clamp
|
| 228 |
+
|
| 229 |
+
// Here we create a function that will map values below 0.1 to 0. Values above 0.2 will be mapped to 1,
|
| 230 |
+
// and for values between 0.1 and 0.2 it will produce a gradient.
|
| 231 |
+
// The funtion is mirror at 0.5, meaning values between 0.8 and 0.9 will result in a decending gradient.
|
| 232 |
+
// And values above 0.9 will be mapped to 0.
|
| 233 |
+
float frequenceFactor = min(smoothstep(1.0 - grayValue, 0.0, 0.11), smoothstep(grayValue, 0.0, 0.11));
|
| 234 |
+
|
| 235 |
+
// here we apply the high pass filter. Its strength is determined by the uniform ,
|
| 236 |
+
// and the frequence factor. That means the only the mid tones are affected by this filter.
|
| 237 |
+
// Clarity input is ranging from -1 to 1. But we want to strengthen the effect.
|
| 238 |
+
// Therefore we see this little magic number '3.7'.
|
| 239 |
+
color.rgb = clamp(color + clamp((color - mergedColor) * u_clarity * 3.7 * frequenceFactor, 0.0, 10.0), 0.0, 1.0).rgb;
|
| 240 |
+
|
| 241 |
+
// apply exposure but only to the mid tones.
|
| 242 |
+
color.rgb = color.rgb * pow(2.0, u_clarity * 0.27 * frequenceFactor);
|
| 243 |
+
|
| 244 |
+
// apply contrast and desaturation matrix
|
| 245 |
+
color.rgb = clamp(color * u_colorMatrix + u_colorOffset, 0.0, 1.0).rgb;
|
| 246 |
+
|
| 247 |
+
color.rgb *= color.a; // Premultiply alpha
|
| 248 |
+
color = clamp(color, 0.0, 1.0);
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
if(u_temperature != 0.0){
|
| 253 |
+
float temperature = u_temperature;
|
| 254 |
+
const float tint = 0.0;
|
| 255 |
+
vec4 source = color;
|
| 256 |
+
|
| 257 |
+
source.rgb /= max(source.a, EPSILON); // Revert premultiplied alpha
|
| 258 |
+
|
| 259 |
+
vec3 yiq = RGBtoYIQ * source.rgb;
|
| 260 |
+
yiq.b = clamp(yiq.b + tint*0.5226*0.1, -0.5226, 0.5226);
|
| 261 |
+
vec3 rgb = YIQtoRGB * yiq;
|
| 262 |
+
|
| 263 |
+
vec3 processed = mix(
|
| 264 |
+
(1.0 - 2.0 * (1.0 - rgb) * (1.0 - warmFilter)),
|
| 265 |
+
(2.0 * rgb * warmFilter),
|
| 266 |
+
vec3(rgb.r < 0.5, rgb.g < 0.5, rgb.b < 0.5)
|
| 267 |
+
);
|
| 268 |
+
|
| 269 |
+
color = vec4(mix(rgb, processed, temperature), source.a);
|
| 270 |
+
|
| 271 |
+
color.rgb *= color.a; // Premultiply alpha again
|
| 272 |
+
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
if (u_sharpness != 0.0){
|
| 276 |
+
float factor = mix(0.2, -1.0, float(u_sharpness > 0.0));
|
| 277 |
+
vec4 sharpenedColor = mix(0.2, 5.0, float(u_sharpness > 0.0)) * color;
|
| 278 |
+
|
| 279 |
+
sharpenedColor += factor * clamp(texture2D(u_image, v_texCoord + u_pixelDimension * vec2(-1.0, 0.0)), 0.0, 1.0);
|
| 280 |
+
sharpenedColor += factor * clamp(texture2D(u_image, v_texCoord + u_pixelDimension * vec2( 0.0, -1.0)), 0.0, 1.0);
|
| 281 |
+
sharpenedColor += factor * clamp(texture2D(u_image, v_texCoord + u_pixelDimension * vec2( 0.0, 1.0)), 0.0, 1.0);
|
| 282 |
+
sharpenedColor += factor * clamp(texture2D(u_image, v_texCoord + u_pixelDimension * vec2( 1.0, 0.0)), 0.0, 1.0);
|
| 283 |
+
|
| 284 |
+
color.rgb /= max(color.a, EPSILON); // unpremultiply
|
| 285 |
+
sharpenedColor.rgb /= max(sharpenedColor.a, EPSILON); // unpremultiply
|
| 286 |
+
|
| 287 |
+
sharpenedColor = clamp(sharpenedColor, 0.0, 1.0);
|
| 288 |
+
|
| 289 |
+
color = clamp(mix(color, sharpenedColor, abs(u_sharpness)), 0.0, 1.0);
|
| 290 |
+
|
| 291 |
+
color = vec4(color.rgb * color.a, color.a); // premultiply
|
| 292 |
+
|
| 293 |
+
}
|
| 294 |
+
|
| 295 |
+
|
| 296 |
+
gl_FragColor = color;
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
"""
|
| 300 |
+
|
| 301 |
+
#
|
| 302 |
+
|
| 303 |
+
# Compile shaders
|
| 304 |
+
shader = OpenGL.GL.shaders.compileProgram(OpenGL.GL.shaders.compileShader(vertex_shader, GL_VERTEX_SHADER),
|
| 305 |
+
OpenGL.GL.shaders.compileShader(fragment_shader, GL_FRAGMENT_SHADER))
|
| 306 |
+
|
| 307 |
+
# VBO
|
| 308 |
+
v_b_o = glGenBuffers(1)
|
| 309 |
+
glBindBuffer(GL_ARRAY_BUFFER, v_b_o)
|
| 310 |
+
glBufferData(GL_ARRAY_BUFFER, quad.itemsize *
|
| 311 |
+
len(quad), quad, GL_STATIC_DRAW)
|
| 312 |
+
|
| 313 |
+
# EBO
|
| 314 |
+
e_b_o = glGenBuffers(1)
|
| 315 |
+
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, e_b_o)
|
| 316 |
+
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.itemsize *
|
| 317 |
+
len(indices), indices, GL_STATIC_DRAW)
|
| 318 |
+
|
| 319 |
+
# Configure positions of initial data
|
| 320 |
+
# Configure positions of initial data
|
| 321 |
+
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 *
|
| 322 |
+
sizeof(GLfloat), ctypes.c_void_p(0))
|
| 323 |
+
glEnableVertexAttribArray(0)
|
| 324 |
+
|
| 325 |
+
# Configure texture coordinates of initial data
|
| 326 |
+
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 *
|
| 327 |
+
sizeof(GLfloat), ctypes.c_void_p(8))
|
| 328 |
+
glEnableVertexAttribArray(1)
|
| 329 |
+
|
| 330 |
+
# Texture
|
| 331 |
+
texture = glGenTextures(1)
|
| 332 |
+
# Bind texture
|
| 333 |
+
glBindTexture(GL_TEXTURE_2D, texture)
|
| 334 |
+
# Texture wrapping params
|
| 335 |
+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
|
| 336 |
+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
|
| 337 |
+
# Texture filtering params
|
| 338 |
+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
|
| 339 |
+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
|
| 340 |
+
|
| 341 |
+
#
|
| 342 |
+
|
| 343 |
+
# Open image
|
| 344 |
+
|
| 345 |
+
#
|
| 346 |
+
# img_data = numpy.array(list(image.getdata()), numpy.uint8)
|
| 347 |
+
#
|
| 348 |
+
# flipped_image = image.transpose(Image.FLIP_TOP_BOTTOM)
|
| 349 |
+
# img_data = flipped_image.convert("RGBA").tobytes()
|
| 350 |
+
#
|
| 351 |
+
img_data = image.convert("RGBA").tobytes()
|
| 352 |
+
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width,
|
| 353 |
+
image.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img_data)
|
| 354 |
+
# print(image.width, image.height)
|
| 355 |
+
|
| 356 |
+
#
|
| 357 |
+
|
| 358 |
+
# Create render buffer with size (image.width x image.height)
|
| 359 |
+
rb_obj = glGenRenderbuffers(1)
|
| 360 |
+
glBindRenderbuffer(GL_RENDERBUFFER, rb_obj)
|
| 361 |
+
glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, image.width, image.height)
|
| 362 |
+
|
| 363 |
+
# Create frame buffer
|
| 364 |
+
fb_obj = glGenFramebuffers(1)
|
| 365 |
+
glBindFramebuffer(GL_FRAMEBUFFER, fb_obj)
|
| 366 |
+
glFramebufferRenderbuffer(
|
| 367 |
+
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rb_obj)
|
| 368 |
+
|
| 369 |
+
# Check frame buffer (that simple buffer should not be an issue)
|
| 370 |
+
status = glCheckFramebufferStatus(GL_FRAMEBUFFER)
|
| 371 |
+
if status != GL_FRAMEBUFFER_COMPLETE:
|
| 372 |
+
print("incomplete framebuffer object")
|
| 373 |
+
|
| 374 |
+
#
|
| 375 |
+
|
| 376 |
+
# Install program
|
| 377 |
+
glUseProgram(shader)
|
| 378 |
+
|
| 379 |
+
set_options(exposure, saturation, contrast, brightness, gamma, shadows, highlights, whites, blacks,
|
| 380 |
+
clarity, temperature, sharpness, shader, image.width, image.height)
|
| 381 |
+
|
| 382 |
+
# Bind framebuffer and set viewport size
|
| 383 |
+
glBindFramebuffer(GL_FRAMEBUFFER, fb_obj)
|
| 384 |
+
glViewport(0, 0, image.width, image.height)
|
| 385 |
+
|
| 386 |
+
# Draw the quad which covers the entire viewport
|
| 387 |
+
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, None)
|
| 388 |
+
|
| 389 |
+
#
|
| 390 |
+
|
| 391 |
+
# PNG
|
| 392 |
+
# Read the data and create the image
|
| 393 |
+
image_buffer = glReadPixels(
|
| 394 |
+
0, 0, image.width, image.height, GL_RGBA, GL_UNSIGNED_BYTE)
|
| 395 |
+
image_out = numpy.frombuffer(image_buffer, dtype=numpy.uint8)
|
| 396 |
+
image_out = image_out.reshape(image.height, image.width, 4)
|
| 397 |
+
glfw.terminate()
|
| 398 |
+
img = Image.fromarray(image_out, 'RGB')
|
| 399 |
+
|
| 400 |
+
|
| 401 |
+
buffered = BytesIO()
|
| 402 |
+
img.save(buffered, format="PNG")
|
| 403 |
+
img_str = base64.b64encode(buffered.getvalue())
|
| 404 |
+
|
| 405 |
+
return img_str
|
| 406 |
+
|
| 407 |
+
|
| 408 |
+
|
| 409 |
+
if __name__ == "__main__":
|
| 410 |
+
image = Image.open("/Users/planningo/Downloads/download.jpeg")
|
| 411 |
+
image_enhance(image, exposure=0, saturation=0, contrast=0, brightness=0, gamma=1, shadows=0,
|
| 412 |
+
highlights=0, blacks=0, whites=0, clarity=0, temperature=-1, sharpness=1)
|
requirments.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
glfw
|
| 2 |
+
PyOpenGL
|
| 3 |
+
numpy
|
| 4 |
+
Pillow
|
| 5 |
+
fastapi==0.104.1
|
| 6 |
+
uvicorn
|
| 7 |
+
pydantic
|
utils/ColorMatrix.py
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class ColorMatrix:
|
| 5 |
+
def __init__(self, a=1, b=0, c=0, d=0, e=0, f=0, g=1, h=0, i=0, j=0, k=0, l=0, m=1, n=0, o=0, p=0, q=0, r=0, s=1, t=0):
|
| 6 |
+
self.a = a
|
| 7 |
+
self.b = b
|
| 8 |
+
self.c = c
|
| 9 |
+
self.d = d
|
| 10 |
+
self.e = e
|
| 11 |
+
self.f = f
|
| 12 |
+
self.g = g
|
| 13 |
+
self.h = h
|
| 14 |
+
self.i = i
|
| 15 |
+
self.j = j
|
| 16 |
+
self.k = k
|
| 17 |
+
self.l = l
|
| 18 |
+
self.m = m
|
| 19 |
+
self.n = n
|
| 20 |
+
self.o = o
|
| 21 |
+
self.p = p
|
| 22 |
+
self.q = q
|
| 23 |
+
self.r = r
|
| 24 |
+
self.s = s
|
| 25 |
+
self.t = t
|
| 26 |
+
|
| 27 |
+
self.matrix = np.array([
|
| 28 |
+
[a, b, c, d, e],
|
| 29 |
+
[f, g, h, i, j],
|
| 30 |
+
[k, l, m, n, o],
|
| 31 |
+
[p, q, r, s, t],
|
| 32 |
+
[0, 0, 0, 0, 1]
|
| 33 |
+
])
|
| 34 |
+
|
| 35 |
+
def apply(self, color):
|
| 36 |
+
r = self.a * color.r + self.b * color.g + \
|
| 37 |
+
self.c * color.b + self.d * color.a + self.e
|
| 38 |
+
g = self.f * color.r + self.g * color.g + \
|
| 39 |
+
self.h * color.b + self.i * color.a + self.j
|
| 40 |
+
b = self.k * color.r + self.l * color.g + \
|
| 41 |
+
self.m * color.b + self.n * color.a + self.o
|
| 42 |
+
a = self.p * color.r + self.q * color.g + \
|
| 43 |
+
self.r * color.b + self.s * color.a + self.t
|
| 44 |
+
return Color(r, g, b, a)
|
| 45 |
+
|
| 46 |
+
def reset(self):
|
| 47 |
+
self.a = 1
|
| 48 |
+
self.b = 0
|
| 49 |
+
self.c = 0
|
| 50 |
+
self.d = 0
|
| 51 |
+
self.e = 0
|
| 52 |
+
self.f = 0
|
| 53 |
+
self.g = 1
|
| 54 |
+
self.h = 0
|
| 55 |
+
self.i = 0
|
| 56 |
+
self.j = 0
|
| 57 |
+
self.k = 0
|
| 58 |
+
self.l = 0
|
| 59 |
+
self.m = 1
|
| 60 |
+
self.n = 0
|
| 61 |
+
self.o = 0
|
| 62 |
+
self.p = 0
|
| 63 |
+
self.q = 0
|
| 64 |
+
self.r = 0
|
| 65 |
+
self.s = 1
|
| 66 |
+
self.t = 0
|
| 67 |
+
self.matrix = np.array([
|
| 68 |
+
[1, 0, 0, 0, 0],
|
| 69 |
+
[0, 1, 0, 0, 0],
|
| 70 |
+
[0, 0, 1, 0, 0],
|
| 71 |
+
[0, 0, 0, 1, 0],
|
| 72 |
+
[0, 0, 0, 0, 1]
|
| 73 |
+
])
|
| 74 |
+
|
| 75 |
+
def multiply(self, other):
|
| 76 |
+
E = other
|
| 77 |
+
R = self
|
| 78 |
+
t = E.a * R.a + E.b * R.f + E.c * R.k + E.d * R.p
|
| 79 |
+
i = E.a * R.b + E.b * R.g + E.c * R.l + E.d * R.q
|
| 80 |
+
o = E.a * R.c + E.b * R.h + E.c * R.m + E.d * R.r
|
| 81 |
+
n = E.a * R.d + E.b * R.i + E.c * R.n + E.d * R.s
|
| 82 |
+
a = E.f * R.a + E.g * R.f + E.h * R.k + E.i * R.p
|
| 83 |
+
l = E.f * R.b + E.g * R.g + E.h * R.l + E.i * R.q
|
| 84 |
+
u = E.f * R.c + E.g * R.h + E.h * R.m + E.i * R.r
|
| 85 |
+
c = E.f * R.d + E.g * R.i + E.h * R.n + E.i * R.s
|
| 86 |
+
m = E.k * R.a + E.l * R.f + E.m * R.k + E.n * R.p
|
| 87 |
+
h = E.k * R.b + E.l * R.g + E.m * R.l + E.n * R.q
|
| 88 |
+
f = E.k * R.c + E.l * R.h + E.m * R.m + E.n * R.r
|
| 89 |
+
b = E.k * R.d + E.l * R.i + E.m * R.n + E.n * R.s
|
| 90 |
+
y = E.p * R.a + E.q * R.f + E.r * R.k + E.s * R.p
|
| 91 |
+
T = E.p * R.b + E.q * R.g + E.r * R.l + E.s * R.q
|
| 92 |
+
w = E.p * R.c + E.q * R.h + E.r * R.m + E.s * R.r
|
| 93 |
+
k = E.p * R.d + E.q * R.i + E.r * R.n + E.s * R.s
|
| 94 |
+
s = E.a * R.e + E.b * R.j + E.c * R.o + E.d * R.t + E.e
|
| 95 |
+
d = E.f * R.e + E.g * R.j + E.h * R.o + E.i * R.t + E.j
|
| 96 |
+
_ = E.k * R.e + E.l * R.j + E.m * R.o + E.n * R.t + E.o
|
| 97 |
+
F = E.p * R.e + E.q * R.j + E.r * R.o + E.s * R.t + E.t
|
| 98 |
+
self.a = t
|
| 99 |
+
self.b = i
|
| 100 |
+
self.c = o
|
| 101 |
+
self.d = n
|
| 102 |
+
self.e = s
|
| 103 |
+
self.f = a
|
| 104 |
+
self.g = l
|
| 105 |
+
self.h = u
|
| 106 |
+
self.i = c
|
| 107 |
+
self.j = d
|
| 108 |
+
self.k = m
|
| 109 |
+
self.l = h
|
| 110 |
+
self.m = f
|
| 111 |
+
self.n = b
|
| 112 |
+
self.o = _
|
| 113 |
+
self.p = y
|
| 114 |
+
self.q = T
|
| 115 |
+
self.r = w
|
| 116 |
+
self.s = k
|
| 117 |
+
self.t = F
|
| 118 |
+
return self
|
| 119 |
+
|
| 120 |
+
def clone(self):
|
| 121 |
+
newMatrix = ColorMatrix()
|
| 122 |
+
newMatrix.setMatrix(
|
| 123 |
+
self.a,
|
| 124 |
+
self.b,
|
| 125 |
+
self.c,
|
| 126 |
+
self.d,
|
| 127 |
+
self.e,
|
| 128 |
+
self.f,
|
| 129 |
+
self.g,
|
| 130 |
+
self.h,
|
| 131 |
+
self.i,
|
| 132 |
+
self.j,
|
| 133 |
+
self.k,
|
| 134 |
+
self.l,
|
| 135 |
+
self.m,
|
| 136 |
+
self.n,
|
| 137 |
+
self.o,
|
| 138 |
+
self.p,
|
| 139 |
+
self.q,
|
| 140 |
+
self.r,
|
| 141 |
+
self.s,
|
| 142 |
+
self.t
|
| 143 |
+
)
|
| 144 |
+
return newMatrix
|
| 145 |
+
|
| 146 |
+
def equals(self, other):
|
| 147 |
+
return np.array_equal(self.matrix, other.matrix)
|
| 148 |
+
|
| 149 |
+
def get_offsets(self):
|
| 150 |
+
return [self.e, self.j, self.o, self.t]
|
| 151 |
+
|
| 152 |
+
def __str__(self):
|
| 153 |
+
return str(self.matrix)
|
| 154 |
+
|
| 155 |
+
def to_array(self):
|
| 156 |
+
components = [
|
| 157 |
+
self.a,
|
| 158 |
+
self.b,
|
| 159 |
+
self.c,
|
| 160 |
+
self.d,
|
| 161 |
+
self.e,
|
| 162 |
+
self.f,
|
| 163 |
+
self.g,
|
| 164 |
+
self.h,
|
| 165 |
+
self.i,
|
| 166 |
+
self.j,
|
| 167 |
+
self.k,
|
| 168 |
+
self.l,
|
| 169 |
+
self.m,
|
| 170 |
+
self.n,
|
| 171 |
+
self.o,
|
| 172 |
+
self.p,
|
| 173 |
+
self.q,
|
| 174 |
+
self.r,
|
| 175 |
+
self.s,
|
| 176 |
+
self.t,
|
| 177 |
+
]
|
| 178 |
+
return components
|
| 179 |
+
|
| 180 |
+
@staticmethod
|
| 181 |
+
def create_brightness_matrix(value):
|
| 182 |
+
matrix = ColorMatrix()
|
| 183 |
+
matrix.e = value
|
| 184 |
+
matrix.j = value
|
| 185 |
+
matrix.o = value
|
| 186 |
+
return matrix
|
| 187 |
+
|
| 188 |
+
@staticmethod
|
| 189 |
+
def create_contrast_matrix(value):
|
| 190 |
+
matrix = ColorMatrix()
|
| 191 |
+
i = (1 - value) / 2
|
| 192 |
+
matrix.a = matrix.g = matrix.m = value
|
| 193 |
+
matrix.e = matrix.j = matrix.o = i
|
| 194 |
+
return matrix
|
| 195 |
+
|
| 196 |
+
@staticmethod
|
| 197 |
+
def create_saturation_matrix(value=1):
|
| 198 |
+
matrix = ColorMatrix()
|
| 199 |
+
i = 1 - value
|
| 200 |
+
o = 0.213 * i
|
| 201 |
+
n = 0.715 * i
|
| 202 |
+
s = 0.072 * i
|
| 203 |
+
matrix.a = o + value
|
| 204 |
+
matrix.b = n
|
| 205 |
+
matrix.c = s
|
| 206 |
+
matrix.f = o
|
| 207 |
+
matrix.g = n + value
|
| 208 |
+
matrix.h = s
|
| 209 |
+
matrix.k = o
|
| 210 |
+
matrix.l = n
|
| 211 |
+
matrix.m = s + value
|
| 212 |
+
return matrix
|
| 213 |
+
|
| 214 |
+
@staticmethod
|
| 215 |
+
def create_exposure_matrix(value=0):
|
| 216 |
+
exposure = 2**value
|
| 217 |
+
return ColorMatrix(exposure, 0, 0, 0, 0, 0, exposure, 0, 0, 0, 0, 0, exposure, 0, 0, 0, 0, 0, 1, 0)
|
| 218 |
+
|
| 219 |
+
@staticmethod
|
| 220 |
+
def createLinearMatrix(value=1, offset=0):
|
| 221 |
+
matrix = ColorMatrix()
|
| 222 |
+
matrix.a = matrix.g = matrix.m = value
|
| 223 |
+
matrix.e = matrix.j = matrix.o = offset
|
| 224 |
+
return matrix
|
| 225 |
+
|
| 226 |
+
# Define the Color class if not already defined
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
class Color:
|
| 230 |
+
def __init__(self, r, g, b, a):
|
| 231 |
+
self.r = r
|
| 232 |
+
self.g = g
|
| 233 |
+
self.b = b
|
| 234 |
+
self.a = a
|
utils/dto.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
class ToneUpData(BaseModel):
|
| 5 |
+
image_id : str
|
| 6 |
+
exposure : float
|
| 7 |
+
saturation : float
|
| 8 |
+
contrast : float
|
| 9 |
+
brightness : float
|
| 10 |
+
gamma : float
|
| 11 |
+
shadows : float
|
| 12 |
+
highlights : float
|
| 13 |
+
whites : float
|
| 14 |
+
blacks : float
|
| 15 |
+
clarity : float
|
| 16 |
+
temperature : float
|
| 17 |
+
sharpness : float
|
utils/s3.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import boto3
|
| 2 |
+
from PIL import Image
|
| 3 |
+
from io import BytesIO
|
| 4 |
+
|
| 5 |
+
BUCKET_NAME = 'planningo-public'
|
| 6 |
+
DIR_NAME = 'photio/'
|
| 7 |
+
|
| 8 |
+
s3 = boto3.client('s3', aws_access_key_id='AKIAZK42H2NEXBFJWUTD',
|
| 9 |
+
aws_secret_access_key='ME9BuygsJeGOCZy3kFPPiqnXTQbV9PY2Lto95bxP')
|
| 10 |
+
|
| 11 |
+
def get_image_from_s3(image_id):
|
| 12 |
+
base_image_data = s3.get_object(
|
| 13 |
+
Bucket=BUCKET_NAME, Key=f'{DIR_NAME}{image_id}.png')['Body'].read()
|
| 14 |
+
return Image.open(BytesIO(base_image_data))
|
utils/settings.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
from OpenGL.GL import *
|
| 3 |
+
|
| 4 |
+
from ColorMatrix import ColorMatrix
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def set_uniform(uniform_name, value, type, shader_program):
|
| 8 |
+
uniform_location = glGetUniformLocation(
|
| 9 |
+
shader_program, f"u_{uniform_name}")
|
| 10 |
+
if type == '1f':
|
| 11 |
+
glUniform1f(uniform_location, value)
|
| 12 |
+
elif type == '2fv':
|
| 13 |
+
glUniform2fv(uniform_location, 1, (GLfloat * len(value))(*value))
|
| 14 |
+
elif type == '4fv':
|
| 15 |
+
glUniform4fv(uniform_location, 1, (GLfloat * len(value))(*value))
|
| 16 |
+
elif type == 'mat4':
|
| 17 |
+
glUniformMatrix4fv(uniform_location, 1, GL_FALSE,
|
| 18 |
+
(GLfloat * len(value))(*value))
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def set_color_matrix_uniform(color_matrix, val_name, shader_program):
|
| 22 |
+
t = color_matrix
|
| 23 |
+
|
| 24 |
+
set_uniform(
|
| 25 |
+
f"{val_name}Matrix",
|
| 26 |
+
[t.a, t.b, t.c, t.d, t.f, t.g, t.h, t.i,
|
| 27 |
+
t.k, t.l, t.m, t.n, t.p, t.q, t.r, t.s],
|
| 28 |
+
'mat4',
|
| 29 |
+
shader_program
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
set_uniform(f"{val_name}Matrix_vec", [
|
| 33 |
+
t.e, t.j, t.o, t.t], '4fv', shader_program)
|
| 34 |
+
set_uniform(f"{val_name}Offset", color_matrix.get_offsets(),
|
| 35 |
+
'4fv', shader_program)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def set_options(exposure, saturation, contrast, brightness, gamma, shadows, highlights, whites, blacks,
|
| 39 |
+
clarity, temperature, sharpness, shader_program, canvas_width, canvas_height):
|
| 40 |
+
if not shader_program:
|
| 41 |
+
print('Shader program not initialized')
|
| 42 |
+
return
|
| 43 |
+
|
| 44 |
+
set_uniform('gamma', gamma, '1f', shader_program)
|
| 45 |
+
set_uniform('shadows', shadows, '1f', shader_program)
|
| 46 |
+
set_uniform('highlights', highlights, '1f', shader_program)
|
| 47 |
+
set_uniform('whites', whites, '1f', shader_program)
|
| 48 |
+
set_uniform('blacks', blacks, '1f', shader_program)
|
| 49 |
+
set_uniform('clarity', clarity, '1f', shader_program)
|
| 50 |
+
set_uniform('temperature', temperature, '1f', shader_program)
|
| 51 |
+
set_uniform('sharpness', sharpness, '1f', shader_program)
|
| 52 |
+
|
| 53 |
+
color_matrix = ColorMatrix()
|
| 54 |
+
color_matrix.multiply(ColorMatrix.create_exposure_matrix(exposure))
|
| 55 |
+
print(vars(color_matrix))
|
| 56 |
+
color_matrix.multiply(ColorMatrix.create_saturation_matrix(saturation + 1))
|
| 57 |
+
print(vars(color_matrix))
|
| 58 |
+
if contrast > 0:
|
| 59 |
+
contrast = contrast * 2
|
| 60 |
+
|
| 61 |
+
color_matrix.multiply(ColorMatrix.create_contrast_matrix(
|
| 62 |
+
contrast + 1))
|
| 63 |
+
print(vars(color_matrix))
|
| 64 |
+
color_matrix.multiply(ColorMatrix.create_brightness_matrix(brightness))
|
| 65 |
+
print(vars(color_matrix))
|
| 66 |
+
set_color_matrix_uniform(color_matrix, 'color', shader_program)
|
| 67 |
+
|
| 68 |
+
clarity_matrix = ColorMatrix()
|
| 69 |
+
clarity_matrix.multiply(
|
| 70 |
+
ColorMatrix.create_saturation_matrix(-0.3 * clarity + 1))
|
| 71 |
+
clarity_matrix.multiply(
|
| 72 |
+
ColorMatrix.create_contrast_matrix(0.1 * clarity + 1))
|
| 73 |
+
set_color_matrix_uniform(clarity_matrix, 'clarity', shader_program)
|
| 74 |
+
print('pixelDimension', [
|
| 75 |
+
1 / canvas_width, 1 / canvas_height])
|
| 76 |
+
set_uniform('pixelDimension', [
|
| 77 |
+
1 / canvas_width, 1 / canvas_height], '2fv', shader_program)
|