Un poco do it yourself y friki a la vez es este articulo. En él, vamos a cargar una imagen jpg y vamos a cambiar el brillo y el contraste de la foto que hay en el encabezado de la página, todo desde nuestro programa, simplemente utilizando libjpeg ( $sudo apt-get install libjpeg8-dev ) para cargar y guardar de nuevo la foto. El efecto lo aplicaremos con una función que trabaje con los pixels de la imagen.
El código para cargar y salvar jpegs, lo he sacado de un ejemplo que hay en la red, ligeramente modificado, y el algoritmo para el brillo y contraste es parecido al que usa GIMP, sólo que los colores en GIMP van de 0 a 1 y aquí van de 0 a 255. Los valores del brillo y el contraste están comprendidos entre -1 y 1 por lo que 0 es el punto medio y debe dejar la imagen sin modificaciones.
Por otra parte, aunque lo lógico es modificar el brillo y el contraste de las tres componentes (rojo, verde y azul) al mismo tiempo, yo planteo modificarlas por separado para tener más libertad a la hora de crear efectos, resaltar colores, etc; por tanto la función byc() toma como parámetros la imagen a modificar, el valor de brillo para rojo, verde y azul y el de contraste rojo, verde y azul. El algoritmo es el mismo para cada componente, pero para esta primera versión he preferido dejarlo así:
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 | #include <stdio.h> #include <jpeglib.h> #include <stdlib.h> #include <math.h> typedef struct TImage { int width; int height; int bytes_per_pixel; int color_space; int size; unsigned char *data; } TImage; int read_jpeg_file( char *filename, TImage *img ) { /* these are standard libjpeg structures for reading(decompression) */ struct jpeg_decompress_struct cinfo; struct jpeg_error_mgr jerr; /* libjpeg data structure for storing one row, that is, scanline of an image */ JSAMPROW row_pointer[1]; FILE *infile = fopen( filename, "rb" ); unsigned long location = 0; int i = 0; if ( !infile ) { printf("Error opening jpeg file %s\n!", filename ); return -1; } /* here we set up the standard libjpeg error handler */ cinfo.err = jpeg_std_error( &jerr ); /* setup decompression process and source, then read JPEG header */ jpeg_create_decompress( &cinfo ); /* this makes the library read from infile */ jpeg_stdio_src( &cinfo, infile ); /* reading the image header which contains image information */ jpeg_read_header( &cinfo, TRUE ); /* Uncomment the following to output image information, if needed. */ img->width=cinfo.image_width; img->height=cinfo.image_height; img->bytes_per_pixel=cinfo.num_components; img->color_space=cinfo.jpeg_color_space; /* Start decompression jpeg here */ jpeg_start_decompress( &cinfo ); /* allocate memory to hold the uncompressed image */ img->size=cinfo.output_width*cinfo.output_height*cinfo.num_components; img->data = (unsigned char*)malloc( img->size ); /* now actually read the jpeg into the raw buffer */ row_pointer[0] = (unsigned char *)malloc( cinfo.output_width*cinfo.num_components ); /* read one scan line at a time */ while( cinfo.output_scanline < cinfo.image_height ) { jpeg_read_scanlines( &cinfo, row_pointer, 1 ); for( i=0; i<cinfo.image_width*cinfo.num_components;i++) { img->data[location++] = row_pointer[0][i]; } } /* wrap up decompression, destroy objects, free pointers and close open files */ jpeg_finish_decompress( &cinfo ); jpeg_destroy_decompress( &cinfo ); free( row_pointer[0] ); fclose( infile ); /* yup, we succeeded! */ return 1; } /** * write_jpeg_file Writes the raw image data stored in the raw_image buffer * to a jpeg image with default compression and smoothing options in the file * specified by *filename. * * \returns positive integer if successful, -1 otherwise * \param *filename char string specifying the file name to save to * */ int write_jpeg_file( char *filename, TImage *img ) { struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; /* this is a pointer to one row of image data */ JSAMPROW row_pointer[1]; FILE *outfile = fopen( filename, "wb" ); if ( !outfile ) { printf("Error opening output jpeg file %s\n!", filename ); return -1; } cinfo.err = jpeg_std_error( &jerr ); jpeg_create_compress(&cinfo); jpeg_stdio_dest(&cinfo, outfile); /* Setting the parameters of the output file here */ cinfo.image_width = img->width; cinfo.image_height = img->height; cinfo.input_components = img->bytes_per_pixel; cinfo.in_color_space = JCS_RGB; //img->color_space; /* default compression parameters, we shouldn't be worried about these */ jpeg_set_defaults( &cinfo ); /* Now do the compression .. */ jpeg_start_compress( &cinfo, TRUE ); /* like reading a file, this time write one row at a time */ while( cinfo.next_scanline < cinfo.image_height ) { row_pointer[0] = &img->data[ cinfo.next_scanline * cinfo.image_width * cinfo.input_components]; jpeg_write_scanlines( &cinfo, row_pointer, 1 ); } /* similar to read file, clean up after we're done compressing */ jpeg_finish_compress( &cinfo ); jpeg_destroy_compress( &cinfo ); fclose( outfile ); /* success code is 1! */ return 1; } float byc(TImage *img, double bred, double bgreen, double bblue, double cred, double cgreen, double cblue) { int i; double tanred, tangreen, tanblue; int v; tanred=tan ((cred + 1.0) * 0.78539816); tangreen=tan ((cgreen + 1.0) * 0.78539816); tanblue=tan ((cblue + 1.0) * 0.78539816); for (i=0; i<img->size; i+=3) { if (bred<0) img->data[i]=round(img->data[i]*(1+bred)); else img->data[i]+=round((255-img->data[i])*bred); if (bgreen<0) img->data[i+1]=round(img->data[i+1]*(1+bgreen)); else img->data[i+1]+=round((255-img->data[i+1])*bgreen); if (bblue<0) img->data[i+2]=round(img->data[i+2]*(1+bblue)); else img->data[i+2]+=round((255-img->data[i+2])*bblue); v=round( (img->data[i] - 128) * tanred) + 128; if (v>0 && v<255) img->data[i]=v; else if (v<0) img->data[i]=0; else img->data[i]=255; v=round( (img->data[i+1] - 128) * tangreen) + 128; if (v>0 && v<255) img->data[i+1]=v; else if (v<0) img->data[i+1]=0; else img->data[i+1]=255; v=round( (img->data[i+2] - 128) * tanblue) + 128; if (v>0 && v<255) img->data[i+2]=v; else if (v<0) img->data[i+2]=0; else img->data[i+2]=255; } } int main() { char *infilename = "windmill_flickr.jpg"; TImage img; /* Try opening a jpeg*/ if( read_jpeg_file( infilename, &img ) > 0 ) { byc(&img, 0.1, 0.1, 0.2,0.1, -0.1, 0.8); write_jpeg_file("windmill.jpg", &img); } return 0; } |
Hay que prestar atención a que tan ( (contraste+1) * pi/4) lo calculamos antes del bucle, ya que será un valor constante para toda la imagen, no merece la pena calcularlo a cada iteración, perderíamos mucho tiempo, ya que es el cálculo más pesado del algoritmo. Además, para el contraste, hacemos las operaciones en un entero y luego lo pasamos a unsigned char (tipo de dato de nuestras componentes), eso es debido a que cuando calculamos el contraste la variable puede desbordarse por arriba o por abajo, y puede producir efectos curiosos (podemos eliminar las condiciones de comprobación para ver si es mayor que 255 o menor que 0 y ver efectos curiosos).
Ahora estaría bien simplificarlo un poco, hacerlo más legible y más flexible, primero vamos a crear una función para el brillo y otra para el contraste, que llamaremos una vez por cada componente:
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 | void brillo(unsigned char *comp, double nivel) { if (nivel<0) *comp=round(*comp*(1+nivel)); else *comp=*comp+round((255-*comp)*nivel); } void contraste(unsigned char *comp, double tang) { int v; v=round( (*comp - 128) * tang) + 128; if (v>0 && v<255) *comp=v; else if (v<0) *comp=0; else *comp=255; } float byc(TImage *img, double bred, double bgreen, double bblue, double cred, double cgreen, double cblue) { int i; double tanred, tangreen, tanblue; int v; tanred=tan ((cred + 1.0) * 0.78539816); tangreen=tan ((cgreen + 1.0) * 0.78539816); tanblue=tan ((cblue + 1.0) * 0.78539816); for (i=0; i<img->size; i+=3) { brillo(&img->data[i], bred); brillo(&img->data[i+1], bgreen); brillo(&img->data[i+2], bblue); contraste(&img->data[i], tanred); contraste(&img->data[i+1], tangreen); contraste(&img->data[i+2], tanblue); } } |
Este método está bien, el código del brillo y el contraste está sólo una vez, aunque estamos haciéndolo con funciones, y esto implica saltos en el programa y paso de parámetros que no serían problema si no es porque se hace muchas veces, si mi foto es de 500×375 (como la del ejemplo) estaríamos haciendo 500x375x6 = 1125000 llamadas a funciones que aunque no aumenta mucho la carga cuando lo hacemos, si aumentamos las dimensiones de la foto a 4288×3216 (como foto de una cámara digital) serían 4288x3216x6=82741248 lo cual empieza a ser significativo y si cronometramos el código puede que lo notemos. Si queremos optimizar un poco más en ese sentido podemos utilizar macros, como muestro a continuación:
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 | #define BRILLO(comp, nivel) { \ if (nivel<0) \ comp=round(comp*(1+nivel)); \ else \ comp=comp+round((255-comp)*nivel); \ } #define CONTRASTE(comp, tang) { \ v=round( (comp - 128) * tang) + 128; \ if (v>0 && v<255) \ comp=v; \ else if (v<0) \ comp=0; \ else \ comp=255; \ } float byc(TImage *img, double bred, double bgreen, double bblue, double cred, double cgreen, double cblue) { int i; double tanred, tangreen, tanblue; int v; tanred=tan ((cred + 1.0) * 0.78539816); tangreen=tan ((cgreen + 1.0) * 0.78539816); tanblue=tan ((cblue + 1.0) * 0.78539816); for (i=0; i<img->size; i+=3) { BRILLO(img->data[i], bred); BRILLO(img->data[i+1], bgreen); BRILLO(img->data[i+2], bblue); CONTRASTE(img->data[i], tanred); CONTRASTE(img->data[i+1], tangreen); CONTRASTE(img->data[i+2], tanblue); } } |
Con esto el tiempo sería igual que en el primer código publicado en el post.
Para compilar el código, tenemos que enlazar la biblioteca libjpeg y la biblioteca matemática:
$ gcc -o jpbc jpeg_bright_contrast.c -ljpeg -lm
Foto original:
Foto: Alter Wolf (Flickr) Creative Commons a día 30/05/2012
Pingback: Bitacoras.com /