Paul Miller wrote:
> Graeme Gill wrote:
>
>>> Has anyone done any work with building a 3D LUT out of an lcms transform
>>> and then doing the conversion using a 3D texture lookup?
>>>
>> See "Using Lookup Tables to Accelerate Color Transformation"
>> by Jeremy Selan, GPU Gems 2 pp 381 Addison-Wesley for more details.
>>
>> Such an approach is used by many of the 3D animation studios
>> to do color managed previews of animation's. The example OpenEXR
>> viewer is such an instance, and the source code is available.
>>
>
> Yes, I've done that kind of stuff myself. I was just wondering if there
> were any recipes for getting the 3D LUT out of lcms.
>
Some time ago I played a little bit with this kind stuff too. Attached
is a simple OpenGL program for viewing a ppm image (sRGB), doing the
color transformation to the supplied display profile with a GLSL shader.
The program uses LCMS to create the color transformation texture (device
link).
Regard,
Gerhard
#include "GLee.h"
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <lcms.h>
#define GRIDPOINTS 64
static int WindowWidth;
static int WindowHeight;
static int ImageWidth;
static int ImageHeight;
static GLushort *Image;
static int TexWidth;
static int TexHeight;
static GLushort clut[GRIDPOINTS][GRIDPOINTS][GRIDPOINTS][3];
static GLuint img_texture;
static GLuint clut_texture;
static GLfloat clut_scale;
static GLfloat clut_offset;
static GLuint cmm_prog;
static GLuint cmm_shader;
const char *cmm_shader_source =
"uniform sampler2D image; \n"
"uniform sampler3D clut; \n"
"uniform float scale; \n"
"uniform float offset; \n"
" \n"
"void main() \n"
"{ \n"
" vec3 img = texture2D(image, gl_TexCoord[0].xy).rgb; \n"
" \n"
" // interpolate CLUT \n"
" img = img * scale + offset; \n"
" gl_FragColor = texture3D(clut, img); \n"
" \n"
" // w/o color management \n"
" // gl_FragColor = vec4(img, 1.0); \n"
"} \n"
;
void
clear_error (void)
{
GLenum err;
while ((err = glGetError ()) != GL_NO_ERROR)
;
}
void
check_error (const char *text)
{
GLenum err = glGetError ();
if (err != GL_NO_ERROR) {
if (text)
fprintf (stderr, "%s: ", text);
while (err != GL_NO_ERROR) {
fprintf (stderr, "GL error %#x\n", (int) err);
err = glGetError ();
}
exit (1);
}
}
void
make_image_texture (void)
{
int w = 8;
int h = 8;
while (w < ImageWidth)
w += w;
while (h < ImageHeight)
h += h;
TexWidth = w;
TexHeight = h;
glGenTextures (1, &img_texture);
glBindTexture (GL_TEXTURE_2D, img_texture);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB16F_ARB, w, h,
// glTexImage2D (GL_TEXTURE_2D, 0, GL_LUMINANCE8, w, h,
glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB16, w, h,
0, GL_RGB, GL_UNSIGNED_SHORT, NULL);
/* size may be too big */
check_error("glTexImage2D failed (image too large?)");
glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, ImageWidth, ImageHeight,
GL_RGB, GL_UNSIGNED_SHORT, Image);
}
void
make_clut_texture (const char *profile_name)
{
int r, g, b, n = GRIDPOINTS;
cmsHPROFILE sRGB, monitor;
cmsHTRANSFORM xform;
sRGB = cmsCreate_sRGBProfile ();
monitor = cmsOpenProfileFromFile (profile_name, "r");
if (monitor == NULL) {
fprintf(stderr, "Cannot open display profile %s\n", profile_name);
exit(1);
}
xform = cmsCreateTransform (sRGB, TYPE_RGB_16, monitor, TYPE_RGB_16,
INTENT_PERCEPTUAL, cmsFLAGS_NOTPRECALC);
if (xform == NULL) {
fprintf(stderr, "Failed to create transformation\n");
exit(1);
}
clut_scale = (double) (n - 1) / n;
clut_offset = 1.0 / (2 * n);
for (r = 0; r < n; r++) {
for (g = 0; g < n; g++) {
for (b = 0; b < n; b++) {
unsigned short in[3];
in[0] = floor ((double) r / (n - 1) * 65535.0 + 0.5);
in[1] = floor ((double) g / (n - 1) * 65535.0 + 0.5);
in[2] = floor ((double) b / (n - 1) * 65535.0 + 0.5);
cmsDoTransform (xform, in, clut[b][g][r], 1);
}
}
}
cmsDeleteTransform (xform);
cmsCloseProfile (monitor);
cmsCloseProfile (sRGB);
glGenTextures (1, &clut_texture);
/* texture 1 = clut */
glActiveTextureARB (GL_TEXTURE0_ARB + 1);
glBindTexture (GL_TEXTURE_3D, clut_texture);
glTexParameteri (GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri (GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexParameteri (GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP);
glTexParameteri (GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri (GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
/*
* Don't use FP luts, but only 16-bit integer ones, since the ATI card
* does not support GL_LINEAR for GL_RGB32F_ARB, but ony GL_NEAREST
*/
glTexImage3D (GL_TEXTURE_3D, 0, GL_RGB16, n, n, n,
0, GL_RGB, GL_UNSIGNED_SHORT, clut);
/* back to texture 0 */
glActiveTextureARB (GL_TEXTURE0_ARB);
}
void
initialize (void)
{
glClearColor (0.0, 0.0, 0.0, 0.0);
glShadeModel (GL_FLAT);
glDisable (GL_BLEND);
glDisable (GL_DEPTH_TEST);
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();
make_image_texture ();
}
void
print_log (GLhandleARB obj)
{
int len = 0;
int nwritten = 0;
char *log;
glGetObjectParameterivARB (obj, GL_OBJECT_INFO_LOG_LENGTH_ARB, &len);
if (len > 0) {
log = malloc (len);
glGetInfoLogARB (obj, len, &nwritten, log);
printf ("%s\n", log);
free (log);
}
}
void
init_shaders (void)
{
GLint loc;
/* compile shader program */
cmm_shader = glCreateShaderObjectARB (GL_FRAGMENT_SHADER_ARB);
glShaderSourceARB (cmm_shader, 1, &cmm_shader_source, NULL);
glCompileShaderARB (cmm_shader);
print_log (cmm_shader);
cmm_prog = glCreateProgramObjectARB ();
glAttachObjectARB (cmm_prog, cmm_shader);
glLinkProgramARB (cmm_prog);
print_log (cmm_prog);
glUseProgramObjectARB (cmm_prog);
loc = glGetUniformLocation (cmm_prog, "scale");
glUniform1fARB (loc, clut_scale);
loc = glGetUniformLocation (cmm_prog, "offset");
glUniform1fARB (loc, clut_offset);
/* texture 1 = clut */
glActiveTextureARB (GL_TEXTURE0_ARB + 1);
glBindTexture (GL_TEXTURE_3D, clut_texture);
loc = glGetUniformLocation (cmm_prog, "clut");
glUniform1iARB (loc, 1);
/* back to texture 0 (image) */
glActiveTextureARB (GL_TEXTURE0_ARB);
glBindTexture (GL_TEXTURE_2D, img_texture);
loc = glGetUniformLocation (cmm_prog, "image");
glUniform1iARB (loc, 0);
}
void
draw (void)
{
float tw = (double) ImageWidth / TexWidth;
float th = (double) ImageHeight / TexHeight;
glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glEnable (GL_TEXTURE_2D);
glBegin (GL_QUADS);
glTexCoord2f (0.0, th); glVertex2f (-1.0, -1.0);
glTexCoord2f (0.0, 0.0); glVertex2f (-1.0, 1.0);
glTexCoord2f (tw, 0.0); glVertex2f ( 1.0, 1.0);
glTexCoord2f (tw, th); glVertex2f ( 1.0, -1.0);
glEnd ();
glFlush ();
glDisable (GL_TEXTURE_2D);
// glutSwapBuffers();
}
void
resize (int w, int h)
{
WindowWidth = w;
WindowHeight = h;
fprintf(stderr, "resize %d %d\n", w, h);
glViewport (0, 0, w, h);
}
void
keyboard (unsigned char key, int x, int y)
{
double t;
int i, n = 200;
struct timeval start, end;
switch (key) {
case '1':
case '2':
case '3':
gettimeofday (&start, NULL);
for (i = 0; i < n; i++) {
if (key == '1' || key == '3')
glTexSubImage2D (GL_TEXTURE_2D, 0,
0, 0, ImageWidth, ImageHeight,
GL_RGB, GL_UNSIGNED_SHORT, Image);
if (key == '2' || key == '3')
draw ();
}
glFinish();
gettimeofday (&end, NULL);
if (end.tv_usec < start.tv_usec) {
end.tv_usec += 1000000;
end.tv_sec--;
}
end.tv_sec -= start.tv_sec;
end.tv_usec -= start.tv_usec;
t = end.tv_sec + end.tv_usec * 1e-6;
printf ("%f frames/s\n", n / t);
printf ("%f Mpixel/s\n",
n / t * ImageWidth * ImageHeight * 1e-6);
printf ("%f Screen Mpixel/s\n",
n / t * WindowWidth * WindowHeight * 1e-6);
break;
case 27:
case 'q':
exit (0);
break;
default:
break;
}
}
int
main (int argc, char **argv)
{
FILE *in = stdin;
int i, w, h, maxval;
char line[256];
if (argc >= 2) {
if ((in = fopen (argv[1], "r")) == NULL) {
fprintf (stderr, "Cannot open %s\n", argv[1]);
exit (1);
}
}
/* read image */
for (i = 0; i < 3;) {
fgets (line, 256, in);
if (i == 0 && (line[0] != 'P' || line[1] != '6')) {
fprintf(stderr, "Image is not a raw ppm image\n");
exit(1);
}
if (line[0] == '#')
continue;
if (i == 1)
sscanf (line, "%d %d", &w, &h);
if (i == 2)
sscanf (line, "%d", &maxval);
i++;
}
if (maxval != 255) {
fprintf (stderr, "Only 8-bit raw ppm images are supported\n");
return 1;
}
ImageWidth = w;
ImageHeight = h;
Image = malloc (sizeof (*Image) * w * h * 3);
for (i = 0; i < w * h * 3; i++) {
Image[i] = getc (in) * 257;
}
glutInit (&argc, argv);
// glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB);
glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB);
glutInitWindowSize (w, h);
glutInitWindowPosition (100, 100);
glutCreateWindow (argv[0]);
glClearColor (0.0, 0.0, 0.0, 0.0);
glShadeModel (GL_FLAT);
glDisable (GL_DITHER);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
make_image_texture ();
if (argc >= 3
&& GLEE_ARB_fragment_shader
&& GLEE_ARB_shading_language_100)
{
make_clut_texture (argv[2]);
init_shaders ();
}
glutDisplayFunc (draw);
glutReshapeFunc (resize);
glutKeyboardFunc (keyboard);
glutMainLoop ();
return 0;
}
-------------------------------------------------------------------------
This SF.net email is sponsored by: Microsoft
Defy all challenges. Microsoft(R) Visual Studio 2008.
http://clk.atdmt.com/MRT/go/vse0120000070mrt/direct/01/
_______________________________________________
Lcms-user mailing list
Lcms-user@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/lcms-user