// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
// Copyright 2007, Daniel Fontijne, University of Amsterdam -- fontijne@science.uva.nl
#ifdef WIN32
#include <windows.h>
#endif
#include <GL/gl.h>
#include <GL/glut.h>
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <libgasandbox/e3ga.h>
#include <libgasandbox/e3ga_draw.h>
#include <libgasandbox/e3ga_util.h>
#include <libgasandbox/gl_util.h>
#include <libgasandbox/glut_util.h>
using namespace e3ga;
const char *WINDOW_TITLE = "Geometric Algebra, Chapter 3, Example 4: Color Space Conversion";
// note that image name is hard coded!!!
const char *IMAGE_NAME_1 = "image2.raw";
const char *IMAGE_NAME_2 = "image3.raw";
// dimension of images
unsigned int g_imageWidth, g_imageHeight;
// original image (read from file)
std::vector<unsigned char>g_sourceImage;
// color-converted image
std::vector<unsigned char>g_destImage;
// GLUT state information
int g_viewportWidth = 800;
int g_viewportHeight = 600;
int g_GLUTmenu;
// the color 'frame'
e3ga::vector g_IFcolors[3] = {
_vector(0.82f*e1 + 0.08f*e2 + 0.08f*e3), // 'red'
_vector(0.01f*e1 + 0.54f*e2 + 0.35f*e3), // 'green'
_vector(0.18f*e2 + 0.37f*e3) // 'blue'
};
// the reciprocal color 'frame'
e3ga::vector g_RFcolors[3] = {
_vector(e1), // reciprocal of 'red'
_vector(e2), // reciprocal of 'green'
_vector(e3) // reciprocal of 'blue'
};
// the index [0, 1, 2] of the color that will be modified by sampleColorAt()
int g_sampleColorIdx = 0;
/**
Converts colors in 'source' images to 'dest' image, according
to the color frame 'IFcolors'
*/
void colorSpaceConvert(
const unsigned char *source,
unsigned char *dest,
unsigned int width, unsigned int height,
const e3ga::vector *IFcolors,
e3ga::vector *RFcolors) {
// compute reciprocal frame
try {
reciprocalFrame(IFcolors, RFcolors, 3);
} catch (std::string &str) {
fprintf(stderr, "Error: %s\n", str.c_str());
g_RFcolors[0].set();
g_RFcolors[1].set();
g_RFcolors[2].set();
}
for (unsigned int i = 0; i < (width * height) * 3; i += 3) {
// convert RGB pixel to vector:
e3ga::vector c(vector_e1_e2_e3, (float)source[i + 0], (float)source[i + 1], (float)source[i + 2]);
// compute colors in in destination image:
float red = _Float(c << g_RFcolors[0]);
float green = _Float(c << g_RFcolors[1]);
float blue = _Float(c << g_RFcolors[2]);
// clip colors:
if (red < 0.0f) red = 0.0f;
else if (red > 255.0f) red = 255.0f;
if (green < 0.0f) green = 0.0f;
else if (green > 255.0f) green = 255.0f;
if (blue < 0.0f) blue = 0.0f;
else if (blue > 255.0f) blue = 255.0f;
// set colors in destination image
dest[i + 0] = (unsigned char)(red + 0.5f); // +0.5f for correct rounding
dest[i + 1] = (unsigned char)(green + 0.5f);
dest[i + 2] = (unsigned char)(blue + 0.5f);
}
}
void drawRectangle(int x, int y, int w, int h) {
glBegin(GL_QUADS);
glVertex2i(x + 0, y + 0);
glVertex2i(x + 0, y + h);
glVertex2i(x + w, y + h);
glVertex2i(x + w, y + 0);
glEnd();
}
void drawArrow(int x, int y, int w, int h) {
glBegin(GL_LINES);
glVertex2i(x, y);
glVertex2i(x + w, y);
glVertex2i(x + w, y);
glVertex2i(x + w - h, y - h);
glVertex2i(x + w, y);
glVertex2i(x + w - h, y + h);
glEnd();
}
void display() {
doIntelWarning(); // warn for possible problems with pciking on Intel graphics chipsets
glViewport(0, 0, g_viewportWidth, g_viewportHeight);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, g_viewportWidth, 0, g_viewportHeight, -100.0, 100.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glDisable(GL_LIGHTING);
// draw color bar
// get 'white' vector:
e3ga::vector white = _vector(unit_e(e1 + e2 +e3));
// Get two vectors, orthogonal to white:
// factorizeBlade() find two vectors such that
// dual(white) == O[1] ^ O[2]
e3ga::vector O[2];
factorizeBlade(dual(white), O);
const float PI2 = (float)(2.0f * M_PI);
const float STEP = 0.025f;
float YT = (float)g_viewportHeight;
float YB = (float)g_viewportHeight - 20;
// alpha runs from 0 to 2 PI
for (float angle = 0.0f; angle < PI2; angle += STEP) {
// generate all fully saturated colors:
e3ga::vector C = _vector(white + cos(angle) * O[0] + sin(angle) * O[1]);
// alternative method (using exp() and geometric product)
// rotor R = exp(_bivector(0.5f * alpha * (O[0] ^ O[1])));
// e3ga::vector C = _vector(R * (white + O[0]) * inverse(R));
// set current color:
glColor3fv(C.getC(vector_e1_e2_e3));
// draw small patch in the current color:
float xl = (angle / PI2) * g_viewportWidth;
float xr = ((angle + STEP) / PI2) * g_viewportWidth;
glBegin(GL_QUADS);
glVertex2f(xl, YB);
glVertex2f(xr, YB);
glVertex2f(xr, YT);
glVertex2f(xl, YT);
glEnd();
}
{
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glPixelZoom(1.0f, -1.0f);
glRasterPos2i(0, g_viewportHeight - 20);
GLsizei width = g_imageWidth;
GLsizei height = g_imageHeight;
GLenum format = GL_RGB;
GLenum type = GL_UNSIGNED_BYTE;
const GLvoid *pixels = &(g_sourceImage[0]);
glDrawPixels(width, height, format, type, pixels);
glRasterPos2i(g_viewportWidth / 2, g_viewportHeight - 20);
pixels = &(g_destImage[0]);
glDrawPixels(width, height, format, type, pixels);
}
// draw small squares that show the change in color space:
{
const int arrowWidth = 20;
int width = 80;
int left = g_viewportWidth / 2 - width - arrowWidth;
int height = (g_viewportHeight - g_imageHeight - 20) / 3;
int top = height * 2;
// input:
for (int i = 0; i < 3; i++) {
glColor3fv(g_IFcolors[i].getC(vector_e1_e2_e3));
drawRectangle(left, top - i * height, width, height);
}
// arrows:
left = g_viewportWidth / 2 - arrowWidth/2;
glColor3f(0.0, 0.0, 0.0);
for (int i = 0; i < 3; i++)
drawArrow(left, top - i * height + height/2, arrowWidth, 8);
// goes to:
left = g_viewportWidth / 2 + arrowWidth;
for (int i = 0; i < 3; i++) {
glColor3f((i == 0) ? 1.0f : 0.0f, (i == 1) ? 1.0f : 0.0f, (i == 2) ? 1.0f : 0.0f);
drawRectangle(left, top - i * height, width, height);
}
// active:
left = g_viewportWidth / 2 - width - arrowWidth;
width = 80 * 2 + arrowWidth * 2;
height = (g_viewportHeight - g_imageHeight - 20) / 3;
top = height * (2 - g_sampleColorIdx);
glColor3f(0.0f, 0.0f, 0.0f);
glLineWidth(3.0f);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
drawRectangle(left, top, width, height);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glLineWidth(1.0f);
}
glColor3f(0.0f, 0.0f, 0.0f);
void *font = GLUT_BITMAP_HELVETICA_12;
renderBitmapString(20, 40, font, "-use left mouse button to 'sample' colors");
renderBitmapString(20, 20, font, "-use other mouse buttons for popup menu");
glutSwapBuffers();
}
void reshape(GLint width, GLint height) {
g_viewportWidth = width;
g_viewportHeight = height;
// redraw viewport
glutPostRedisplay();
}
void sampleColorAt(int x, int y) {
// sample color at x, y
unsigned char rgb[3];
glReadPixels(x, y, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, rgb);
printf("Color: %d %d %d\n", rgb[0], rgb[1], rgb[2]);
e3ga::vector newColor = e3ga::vector(vector_e1_e2_e3, (float)rgb[0] / 255.0f, (float)rgb[1] / 255.0f, (float)rgb[2] / 255.0f);
// set new value
if (_Float(norm_e2(g_IFcolors[g_sampleColorIdx] - newColor))) {
g_IFcolors[g_sampleColorIdx] = newColor;
// convert image
colorSpaceConvert(&(g_sourceImage[0]), &(g_destImage[0]),
g_imageWidth, g_imageHeight, g_IFcolors, g_RFcolors);
// redraw viewport
glutPostRedisplay();
}
}
void MouseButton(int button, int state, int x, int y) {
sampleColorAt(x, g_viewportHeight - y);
}
void MouseMotion(int x, int y) {
sampleColorAt(x, g_viewportHeight - y);
}
void loadRawImage(const char *filename) {
FILE *F = fopen(filename, "rb");
if (F == NULL) {
char altName[1024];
sprintf(altName, "../chap3/ex4/%s", filename);
F = fopen(altName, "rb");
if (F == NULL) {
fprintf(stderr, "Could not open '%s'. This file should be in the current directory for this example to work!\n", filename);
exit(-1);
}
}
unsigned char buf[4];
if (fread(buf, 4, 1, F) != 1) {
fprintf(stderr, "Could not read '%s'.\n", filename);
exit(-1);
}
g_imageWidth = (buf[0] << 8) + buf[1];
g_imageHeight = (buf[2] << 8) + buf[3];
printf("Image is %d X %d\n", g_imageWidth, g_imageHeight);
g_sourceImage.resize(g_imageWidth*g_imageHeight*3);
g_destImage.resize(g_imageWidth*g_imageHeight*3);
if (fread(&(g_sourceImage[0]), 1, g_sourceImage.size(), F) != g_sourceImage.size()) {
fprintf(stderr, "Could not read '%s'.\n", filename);
exit(-1);
}
fclose(F);
colorSpaceConvert(&(g_sourceImage[0]), &(g_destImage[0]),
g_imageWidth, g_imageHeight, g_IFcolors, g_RFcolors);
}
void menuCallback(int value) {
if ((value >= 0) && (value < 3))
g_sampleColorIdx = value;
else if (value == -1) {
loadRawImage(IMAGE_NAME_1);
}
else if (value == -2) {
loadRawImage(IMAGE_NAME_2);
}
glutPostRedisplay();
}
int main(int argc, char*argv[]) {
e3ga::g2Profiling::init();
// load the raw image:
loadRawImage(IMAGE_NAME_1);
// GLUT Window Initialization:
glutInit (&argc, argv);
glutInitWindowSize(g_viewportWidth, g_viewportHeight);
glutInitDisplayMode( GLUT_RGB | GLUT_ALPHA | GLUT_DOUBLE | GLUT_DEPTH);
glutCreateWindow(WINDOW_TITLE);
// Register callbacks:
glutDisplayFunc(display);
glutReshapeFunc(reshape);
glutMouseFunc(MouseButton);
glutMotionFunc(MouseMotion);
// create menu for color selection
g_GLUTmenu = glutCreateMenu(menuCallback);
glutAddMenuEntry("sample red", 0);
glutAddMenuEntry("sample green", 1);
glutAddMenuEntry("sample blue", 2);
glutAddMenuEntry("-------------------------", -3);
glutAddMenuEntry("Image: computer parts", -1);
glutAddMenuEntry("Image: soda cans", -2);
glutAttachMenu(GLUT_MIDDLE_BUTTON);
glutAttachMenu(GLUT_RIGHT_BUTTON);
glutMainLoop();
return 0;
}