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

Reply via email to