Archivo

Entradas Etiquetadas ‘color’

Coloreando código con GeSHi

Sábado, 4 de Febrero de 2012 Gaspar Fernández Sin comentarios

geshiCuando queremos publicar código fuente por Internet (como en esta web), debemos hacer que los usuarios se sientan bien con el código y que sea agradable de leer. Además del indentado, es de agradecer la introducción de colores, que separen palabras clave, identifiquen cadenas, funciones de biblioteca, funciones propias, números y demás elementos que encontramos en un fragmento de código.

Por Internet, podemos encontrar una biblioteca muy útil para este propósito: GeSHi. Vamos a intentar hacer algo sencillo con ella.

Para instalarla simplemente tenemos que descargarla y descomprimirla en uno de los directorios de nuestra web (no tiene por qué ser el principal), a continuación vamos a hacer un pequeño programa que mostrará el código fuente de un fichero PHP. Suponemos que geshi.php está situado en el mismo directorio que este ejemplo:

1
2
3
4
5
6
7
8
9
10
<?php
// Basado en los ejemplos de la página oficial
require_once("geshi.php");

$codigo=file_get_contents('mi_programa.php');

$ges=new GeSHi($codigo, 'php');

echo $ges->parse_code();
?>

Con este pequeño ejemplo, se mostrará de forma coloreada el código seleccionado, aunque es conveniente el uso de cachés, es decir, es una buena idea almacenar el código coloreado, es decir, el contenido que devuelve $ges->parse_code() en un archivo aparte y cargar directamente este archivo en la siguiente petición de la página. Por ejemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
include_once 'geshi.php';

function color_source_file($source_file, $language)
{
  $source_cache='cache/'.basename($source_file).'_'.$language.'.cache';
  if (@filemtime($source_cache)<filemtime($source_file))
    {
      $geshi= new GeSHi(file_get_contents($source_file), $language);
      $highlighted_source=$geshi->parse_code();
      file_put_contents($source_cache, $highlighted_source);
      return $highlighted_source;
    }
  return file_get_contents($source_cache);
}

$source = 'mi_programa.php';

echo color_source_file($source, 'php');
?>

Este código es capaz de guardar en un fichero el html generado por GeSHi en un archivo de caché (se guardará en un directorio llamado cache, el nombre del fichero será el mismo que el espeficado y su formato será [nombre]_[lenguaje].cache . La decisión se toma en base a las fechas de modificación de los archivos. Si el archivo de caché no existe (por eso la @ para que no devuelva fallos PHP) o su fecha de modificación es anterior a la fecha de modificación del archivo fuente, se generará un nuevo archivo de caché y se devolverá el código coloreado. Si no, se devolverá el código coloreado leyendo directamente del archivo de caché.

Activar números de línea

Lo podemos hacer así:

1
$ges->enable_line_numbers(GESHI_FANCY_LINE_NUMBERS);

Si ponemos GESHI_FANCY_LINE_NUMBERS cada 5 líneas saldrá el número en negrita, si ponemos GESHI_NORMAL_LINE_NUMBERS no sucederá así.

Resaltar líneas

Para resaltar líneas, debemos crear un array con los números de línea que queremos resaltar:

1
$lineas=array(5,6,7,43,53,54)

Y luego escribir:

1
$ges->hightlight_lines_extra($lineas);

Personalizando los estilos

Para ello podemos utilizar CSS. Por defecto GeSHi personaliza el style=”" de cada etiqueta, aunque genera mucho código html que podemos ahorrar. Para ello, podemos utilizar lo siguiente:

1
2
$ges->enable_classes();
file_put_contents('codigo.css', $ges->get_stylesheet(false));

Así crearemos un archivo .css que contendrá todos los estilos que puede tener un código fuente en un determinado lenguaje (en el cual hemos inicializado la clase), luego podemos incluir ese CSS ya generado en nuestro html resultante, y cambiar los colores, tipos de letra, etc.

GeSHi tiene muchísimas más opciones, para saber más, basta con un vistazo a la documentación oficial (en inglés) para descubrir todas las posibilidades de esta clase.

Salvando archivos de imagen BMP en C

Jueves, 21 de Julio de 2011 Gaspar Fernández Sin comentarios

linux_detergenteHace tiempo hablé de la lectura de archivos BMP en C y puse algún ejemplo. Pero falta lo más importante, poder guardar de nuevo las imágenes, tras aplicar un filtro o generar una imagen desde cero y exportarla. Para ello he creado la siguiente función:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void SaveBMP(char *filename, bmpInfoHeader *info, unsigned char *imgdata)
{
  bmpFileHeader header;
  FILE *f;
  uint16_t type;
 
  f=fopen(filename, "w+");
  header.size=info->imgsize+sizeof(bmpFileHeader)+sizeof(bmpInfoHeader);
  /* header.resv1=0; */
  /* header.resv2=1; */
  /* El offset será el tamaño de las dos cabeceras + 2 (información de fichero)*/
  header.offset=sizeof(bmpFileHeader)+sizeof(bmpInfoHeader)+2;
  /* Escribimos la identificación del archivo */
  type=0x4D42;
  fwrite(&type, sizeof(type),1,f);
  /* Escribimos la cabecera de fichero */
  fwrite(&header, sizeof(bmpFileHeader),1,f);
  /* Escribimos la información básica de la imagen */
  fwrite(info, sizeof(bmpInfoHeader),1,f);
  /* Escribimos la imagen */
  fwrite(imgdata, info->imgsize, 1, f);
  fclose(f);
}

En el siguiente ejemplo, utilizando la carga de un archivo de imagen (por ejemplo la que ilustra el post convertida a BMP), podemos hacer un sencillo algoritmo para pasarla a blanco y negro:

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
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <math.h>       /* round() */

typedef struct bmpFileHeader
{
  uint32_t size;
  uint16_t resv1;
  uint16_t resv2;
  uint32_t offset;
} bmpFileHeader;

typedef struct bmpInfoHeader
{
  uint32_t headersize;      /* DIB header size */
  uint32_t width;
  uint32_t height;
  uint16_t planes;         /* color planes */
  uint16_t bpp;            /* bits per pixel */
  uint32_t compress;
  uint32_t imgsize;    
  uint32_t bpmx;        /* X bits per meter */
  uint32_t bpmy;        /* Y bits per meter */
  uint32_t colors;      /* colors used */
  uint32_t imxtcolors;      /* important colors */
} bmpInfoHeader;

void SaveBMP(char *filename, bmpInfoHeader *info, unsigned char *imgdata);
unsigned char calculaColorMedio(unsigned char *pixel);
unsigned char *LoadBMP(char *filename, bmpInfoHeader *bInfoHeader);
bmpInfoHeader *createInfoHeader(unsigned w, unsigned h, unsigned ppp);

int main()
{
  bmpInfoHeader info;  
  unsigned char *img;
  unsigned char color[3];
  unsigned char media;
  int i, j;
  img=LoadBMP("Linux_Detergente.bmp", &info);

  for (i=0; i<info.height; i++)
    {
      for (j=0; j<info.width; j++)
    {
      media=calculaColorMedio(&img[3*(j+i*info.width)]);
      img[3*(j+i*info.width)]=media;
      img[3*(j+i*info.width)+1]=media;
      img[3*(j+i*info.width)+2]=media;

    }
    }
  SaveBMP("res3.bmp", &info, img);

  free(img);
 
}

unsigned char calculaColorMedio(unsigned char *pixel)
{
  unsigned media = (*pixel + *(pixel+1) + *(pixel+2)) / 3;

  return (unsigned char) media;
}

unsigned char *LoadBMP(char *filename, bmpInfoHeader *bInfoHeader)
{

  FILE *f;
  bmpFileHeader header;
  unsigned char *imgdata;
  uint16_t type;
  f=fopen (filename, "r");
  /* handle open error */
  fread(&type, sizeof(uint16_t), 1, f);
  if (type !=0x4D42)
    {
      fclose(f);
      return NULL;
    }
  fread(&header, sizeof(bmpFileHeader), 1, f);

  printf ("size: %u\n", header.size);
  printf ("offs: %u\n", header.offset);
  fread(bInfoHeader, sizeof(bmpInfoHeader), 1, f);
  printf ("header size:      %d\n", bInfoHeader->headersize);
  printf ("image width:      %d\n", bInfoHeader->width);
  printf ("image height:     %d\n", bInfoHeader->height);
  printf ("colour planes:    %d\n", bInfoHeader->planes);
  printf ("bpp:              %d\n", bInfoHeader->bpp);
  printf ("compress:         %d\n", bInfoHeader->compress);
  printf ("imgage size:      %d\n", bInfoHeader->imgsize);
  printf ("bpmx:             %d\n", bInfoHeader->bpmx);
  printf ("bpmy:             %d\n", bInfoHeader->bpmy);
  printf ("colors:           %d\n", bInfoHeader->colors);
  printf ("important colors: %d\n", bInfoHeader->imxtcolors);
  imgdata=(unsigned char*)malloc(bInfoHeader->imgsize);
  fseek(f, header.offset, SEEK_SET);
  printf("leido: %d\n", fread(imgdata, bInfoHeader->imgsize,1, f));
  fclose(f);

  return imgdata;
}

bmpInfoHeader *createInfoHeader(unsigned w, unsigned h, unsigned ppp)
{
  bmpInfoHeader *ih = malloc(sizeof(bmpInfoHeader));

  ih->headersize=sizeof(bmpInfoHeader);
  ih->width=w;
  ih->height=h;
  ih->planes=1;
  ih->bpp=24;
  ih->compress=0;
  ih->imgsize=w*h*3;        /* 3 bytes por pixel w*h pixels */
  ih->bpmx=(unsigned)round((double)ppp*100/2.54);
  ih->bpmy=ih->bpmx;        /* Misma resolución vertical y horiontal */
  ih->colors=0;
  ih->imxtcolors=0;

  return ih;
}

void SaveBMP(char *filename, bmpInfoHeader *info, unsigned char *imgdata)
{
  bmpFileHeader header;
  FILE *f;
  uint16_t type;
 
  f=fopen(filename, "w+");
  header.size=info->imgsize+sizeof(bmpFileHeader)+sizeof(bmpInfoHeader);
  /* header.resv1=0; */
  /* header.resv2=1; */
  /* El offset será el tamaño de las dos cabeceras + 2 (información de fichero)*/
  header.offset=sizeof(bmpFileHeader)+sizeof(bmpInfoHeader)+2;
  /* Escribimos la identificación del archivo */
  type=0x4D42;
  fwrite(&type, sizeof(type),1,f);
  /* Escribimos la cabecera de fichero */
  fwrite(&header, sizeof(bmpFileHeader),1,f);
  /* Escribimos la información básica de la imagen */
  fwrite(info, sizeof(bmpInfoHeader),1,f);
  /* Escribimos la imagen */
  fwrite(imgdata, info->imgsize, 1, f);
  fclose(f);
}

Tendremos que compilarlo con -lm para incluir la biblioteca matemática (usada para hacer round() con los colores), debe generar un fichero res.bmp con la imagen en blanco y negro.

Nota: El algoritmo utilizado para hacer la imagen en blanco y negro es la media de las 3 componentes, hay muchos algoritmos para hacer esto, ya iré mostrando algunos en el futuro.

Nota2: He incluido la etiqueta #tuentiContest aquí aunque no tenga mucho que ver por el reto 14, donde teníamos que leer un archivo BMP.

Foto: Jacob Köhler

Leyendo archivos de imagen en formato BMP en C

Domingo, 26 de Junio de 2011 Gaspar Fernández Sin comentarios

n1214332197_30461081_3311160

Hoy vamos a practicar a leer una imagen desde un archivo BMP desde C. Aunque existen muchas APIs disponibles que son capaces de hacerlo, y mucho mejor que lo que voy a plantear (puesto que nos limitaremos a BMPs sin compresión y a 24bits por pixel), es un buen ejercicio para leer archivos con un formato especificado y documentado.

Para este tipo de archivos, tendremos dos cabeceras disponibles, la primera será la cabecera de fichero, y la segunda, la cabecera de información de imagen, que las definimos aquí:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
typedef struct bmpFileHeader
{
  /* 2 bytes de identificación */
  uint32_t size;        /* Tamaño del archivo */
  uint16_t resv1;       /* Reservado */
  uint16_t resv2;       /* Reservado */
  uint32_t offset;      /* Offset hasta hasta los datos de imagen */
} bmpFileHeader;

typedef struct bmpInfoHeader
{
  uint32_t headersize;      /* Tamaño de la cabecera */
  uint32_t width;               /* Ancho */
  uint32_t height;          /* Alto */
  uint16_t planes;                  /* Planos de color (Siempre 1) */
  uint16_t bpp;             /* bits por pixel */
  uint32_t compress;        /* compresión */
  uint32_t imgsize;     /* tamaño de los datos de imagen */
  uint32_t bpmx;                /* Resolución X en bits por metro */
  uint32_t bpmy;                /* Resolución Y en bits por metro */
  uint32_t colors;              /* colors used en la paleta */
  uint32_t imxtcolors;      /* Colores importantes. 0 si son todos */
} bmpInfoHeader;

Todos los campos tienen un comentario que los explica, pero los detallaré algo más:

  • De primeras nos encontramos dos bytes de identificación que no he introducido en el registro, y es así por la alineación de variables. Es mejor leerlos a parte y luego leer el struct que os presento.
  • size (en FileHeader) es el tamaño del archivo completo, lo podremos usar para comprobar si hay más datos al final del archivo, o para ver si el archivo está cortado, es decir, como comprobación de errores
  • offset (en FileHeader), es la distancia en bytes desde que termina la cabecera de información hasta que empiezan los datos. No le vamos a hacer mucho caso a esto, sólo que puede ser que el archivo tenga más información entre la cabecera y los datos.
  • headersize (en InfoHeader), será el tamaño de la cabecera, dado que hay varias versiones del formato, ésta puede ser mucho más grande, aunque nosotros sólo leeremos siempre 40bytes.
  • planes (en InfoHeader), son los planos de color. Sólo está documentado el valor 1 de este campo, por tanto será el único que tratemos.
  • bpp (en InfoHeader), son los bits por pixel de la imagen, para el ejemplo, siempre serán 24, ya que no soportamos imágenes con otra profundidad de color.
  • compress (en InfoHeader), para este ejemplo será siempre 0, ya que no trataremos compresión.
  • imgsize (en InfoHeader), será el tamaño de los datos de imagen, al igual que size nos puede servir para control de errores, y para ver cuánta memoria vamos a reservar, aunque este tamaño, para el ejemplo será ancho*alto*3 (24bits por pixel)
  • bpmx, bpmy en (InfoHeader), será la resolución de la imagen en pixels por metro.
  • colors, imxtcolors en (InfoHeader), valdrán 0 en este ejemplo, ya que no usaremos imágenes con paleta de colores.

Ya estamos preparados para leer el archivo de imagen con la siguiente funció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
38
39
40
41
42
43
unsigned char *LoadBMP(char *filename, bmpInfoHeader *bInfoHeader)
{

  FILE *f;
  bmpFileHeader header;     /* cabecera */
  unsigned char *imgdata;   /* datos de imagen */
  uint16_t type;        /* 2 bytes identificativos */

  f=fopen (filename, "r");
  if (!f)
    return NULL;        /* Si no podemos leer, no hay imagen*/

  /* Leemos los dos primeros bytes */
  fread(&type, sizeof(uint16_t), 1, f);
  if (type !=0x4D42)        /* Comprobamos el formato */
    {
      fclose(f);
      return NULL;
    }

  /* Leemos la cabecera de fichero completa */
  fread(&header, sizeof(bmpFileHeader), 1, f);

  /* Leemos la cabecera de información completa */
  fread(bInfoHeader, sizeof(bmpInfoHeader), 1, f);

  /* Reservamos memoria para la imagen, ¿cuánta?
     Tanto como indique imgsize */

  imgdata=(unsigned char*)malloc(bInfoHeader->imgsize);

  /* Nos situamos en el sitio donde empiezan los datos de imagen,
   nos lo indica el offset de la cabecera de fichero*/

  fseek(f, header.offset, SEEK_SET);

  /* Leemos los datos de imagen, tantos bytes como imgsize */
  fread(imgdata, bInfoHeader->imgsize,1, f);

  /* Cerramos */
  fclose(f);

  /* Devolvemos la imagen */
  return imgdata;
}

Esta función devolverá los datos de la imagen como resultado de la función, y a través de un parámetro de entrada/salida, devolveremos también el infoHeader.
Lo que hacemos es simplemente leer las cabeceras, aunque para la primera cabecera, como dijimos antes, leemos primero los 2 bytes identificativos del formato de archivo, éstos tienen que valer 0×4D42 (MB), aunque como los datos están en little-endian (el byte menos significativo primero), lo leeremos al revés (BM), que corresponde a uno de los formatos de imagen que podemos alojar dentro de un BMP.

Por último, vamos a probar todo esto con un ejemplo, para no complicar las cosas con APIs gráficas, o cálculos de histogramas, se me ocurrió hacer una pequeña representación de la imagen en la consola. Para este código, creé una imagen con ImageMagick de la siguiente manera:

$ convert -background red -fill blue -font /usr/share/fonts/truetype/ttf-liberation/LiberationMono-Bold.ttf -pointsize 72 “label:Poesía Binaria” +matte poesia.bmp

+matte nos servirá para eliminar el canal alfa por defecto de la imagen.

Ahora, en nuestro código incluimos los struct y la función anteriores con un par de cosas más:

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
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

typedef struct bmpFileHeader
{
  /* 2 bytes de identificación */
  uint32_t size;        /* Tamaño del archivo */
  uint16_t resv1;       /* Reservado */
  uint16_t resv2;       /* Reservado */
  uint32_t offset;      /* Offset hasta hasta los datos de imagen */
} bmpFileHeader;

typedef struct bmpInfoHeader
{
  uint32_t headersize;      /* Tamaño de la cabecera */
  uint32_t width;       /* Ancho */
  uint32_t height;      /* Alto */
  uint16_t planes;          /* Planos de color (Siempre 1) */
  uint16_t bpp;             /* bits por pixel */
  uint32_t compress;        /* compresión */
  uint32_t imgsize;     /* tamaño de los datos de imagen */
  uint32_t bpmx;        /* Resolución X en bits por metro */
  uint32_t bpmy;        /* Resolución Y en bits por metro */
  uint32_t colors;      /* colors used en la paleta */
  uint32_t imxtcolors;      /* Colores importantes. 0 si son todos */
} bmpInfoHeader;

unsigned char *LoadBMP(char *filename, bmpInfoHeader *bInfoHeader);
void DisplayInfo(bmpInfoHeader *info);
void TextDisplay(bmpInfoHeader *info, unsigned char *img);

int main()
{
  bmpInfoHeader info;
  unsigned char *img;

  img=LoadBMP("poesia.bmp", &info);
  DisplayInfo(&info);
  TextDisplay(&info, img);

  return 0;
}

void TextDisplay(bmpInfoHeader *info, unsigned char *img)
{
  int x, y;
  /* Reducimos la resolución vertical y horizontal para que la imagen entre en pantalla */
  static const int reduccionX=6, reduccionY=4;
  /* Si la componente supera el umbral, el color se marcará como 1. */
  static const int umbral=90;
  /* Asignamos caracteres a los colores en pantalla */
  static unsigned char colores[9]=" bgfrRGB";
  int r,g,b;

  /* Dibujamos la imagen */
  for (y=info->height; y>0; y-=reduccionY)
    {
      for (x=0; x<info->width; x+=reduccionX)
    {
      b=(img[3*(x+y*info->width)]>umbral);
      g=(img[3*(x+y*info->width)+1]>umbral);
      r=(img[3*(x+y*info->width)+2]>umbral);

      printf("%c", colores[b+g*2+r*4]);
    }
      printf("\n");
    }
}

unsigned char *LoadBMP(char *filename, bmpInfoHeader *bInfoHeader)
{

  FILE *f;
  bmpFileHeader header;     /* cabecera */
  unsigned char *imgdata;   /* datos de imagen */
  uint16_t type;        /* 2 bytes identificativos */

  f=fopen (filename, "r");
  if (!f)
    return NULL;        /* Si no podemos leer, no hay imagen*/

  /* Leemos los dos primeros bytes */
  fread(&type, sizeof(uint16_t), 1, f);
  if (type !=0x4D42)        /* Comprobamos el formato */
    {
      fclose(f);
      return NULL;
    }

  /* Leemos la cabecera de fichero completa */
  fread(&header, sizeof(bmpFileHeader), 1, f);

  /* Leemos la cabecera de información completa */
  fread(bInfoHeader, sizeof(bmpInfoHeader), 1, f);

  /* Reservamos memoria para la imagen, ¿cuánta?
     Tanto como indique imgsize */

  imgdata=(unsigned char*)malloc(bInfoHeader->imgsize);

  /* Nos situamos en el sitio donde empiezan los datos de imagen,
   nos lo indica el offset de la cabecera de fichero*/

  fseek(f, header.offset, SEEK_SET);

  /* Leemos los datos de imagen, tantos bytes como imgsize */
  fread(imgdata, bInfoHeader->imgsize,1, f);

  /* Cerramos */
  fclose(f);

  /* Devolvemos la imagen */
  return imgdata;
}

void DisplayInfo(bmpInfoHeader *info)
{
  printf("Tamaño de la cabecera: %u\n", info->headersize);
  printf("Anchura: %d\n", info->width);
  printf("Altura: %d\n", info->height);
  printf("Planos (1): %d\n", info->planes);
  printf("Bits por pixel: %d\n", info->bpp);
  printf("Compresión: %d\n", info->compress);
  printf("Tamaño de datos de imagen: %u\n", info->imgsize);
  printf("Resolucón horizontal: %u\n", info->bpmx);
  printf("Resolucón vertical: %u\n", info->bpmy);
  printf("Colores en paleta: %d\n", info->colors);
  printf("Colores importantes: %d\n", info->imxtcolors);
}

Compilamos el código anterior y nos queda algo como esto:
pbinaria

Lo más raro es la forma de acceder a cada pixel (podremos simplificar más adelante), y es que la imagen está almacenada boca abajo, por lo que la primera línea que leamos será la última que se debe mostrar, por otra parte, los colores están en formato BGR, y, generalmente para acceder a cada pixel lo podremos hacer con:

img[3*(x+y*info->width)+comp]

Teniendo en cuenta, como ya dijimos que la coordenada y va desde la última línea a la primera, y que:

  • Si queremos leer la componente azul, comp=0
  • Si queremos leer la componente verde, comp=1
  • Si queremos leer la componente roja, comp=2

Más adelante, iré publicando algunas aplicaciones prácticas de todo esto, y pronto, una función parecida a LoadBMP que se encargue de salvar los archivos y así empezar a probar filtros gráficos.

Actualizado: 2011/06/26 13:00 : Añadida la información de las componentes de color al final

Jugando con ImageMagick (II): Color y rotación

Miércoles, 1 de Septiembre de 2010 Gaspar Fernández Sin comentarios

Sigo con la serie de posts sobre ImageMagick, anteriormente estuvimos viendo cómo podemos redimensionar, añadir y quitar bordes a la imagen, cambiar y ecualizar el espacio de color, añadir efectos y capturar de la pantalla. Ahora veremos más cosas relacionadas con el color:

Tocando los canales de color

hamburgueson_33Imagina que queremos aplicar un efecto sólo en un canal, por ejemplo, un efecto radial-blur sólo en el canal rojo:

$ convert -channel red -radial-blur 30 hamburgueson.jpg hamburgueson_rojo.jpg

Donde 30 es el radio del emborronamiento (blur) radial.

Pero por ejemplo, si queremos aumentar el nivel de rojo, verde, o azul (o de los tres valores) de una imagen, podemos modificar los niveles de blanco y negro de la propia imagen.

tazasPara el ejemplo superior:

$ convert -level 30,100% taza.jpg taza_oscura.jpg

$ convert -channel blue -level 0,50% taza.jpg taza_azul.jpg

$ convert -channel red -level 30,80% taza.jpg taza_arojo.jpg

De izquierda a derecha.

Podemos también obtener la imagen a dos colores (blanco y negro), obtenido desde un valor umbral (valores inferiores, se representarán con negro y superiores con blanco), aunque, como veremos en el ejemplo, también podemos filtrar según el canal:

tazas1Para este ejemplo, hice lo siguiente:

$ convert -threshold 25% taza1.jpg tazaBN.jpg

$ convert -channel red,blue -threshold 55% taza.jpg taza_naranja_verde.jpg

$ convert -channel green -threshold 75% taza_magenta.jpg

Más efectos de color

taza_3Para conseguir el tono sepia hacemos lo siguiente:

$ convert -sepia-tone [umbral] origen destino

En el ejemplo hice lo siguiente:

$ convert -sepia-tone 90% taza.jpg taza_sepia.jpg

También disponemos del efecto posterizado (si no se aprecia bien en la miniatura, hacer click para ver en grande). Para conseguir el efecto hacemos lo siguiente:

taza_31

$ convert -posterize 4 taza.jpg taza_poster.jpg

Donde 4 es el número de niveles con el que vamos a posterizar.

También tenemos el efecto solarizado con el siguiente comando:

$ convert -solarize 2% taza.jpg taza_sol.jpg

taza_32Donde el 2% es el umbral deseado para el efecto, que vemos en la siguiente imagen.

Para completar los efectos de color, veamos también cómo podemos cambiar brillo, saturación y tono. Para controlarlo tenemos la orden:

$ convert -modulate [brillo],[saturacion],[tono] origen destino

Vemos en el siguiente ejemplo las imágenes con los efectos uno a uno:

tazas2

# En la primera se ha modificado el tono de la imagen:

$ convert -modulate 100,100,142 taza.jpg taza_hue.jpg

# En la segunda se modificó la saturación

$ convert -modulate 100,142,100 taza.jpg taza_sat.jpg

# En la tercera se cambió el brillo:

$ convert -modulate 142,100,100 taza.jpg taza_brillo.jpg

Todos estos parámetros se indican en porcentaje.

Rotación y transposición

Además de estos efectos de color, encontramos también algunas transformaciones de imagen

tazas3

$ convert -flip taza.jpg taza1.jpg

$ convert -flop taza.jpg taza2.jpg

$ convert -transpose taza.jpg taza3.jpg

$ convert -transverse taza.jpg taza4.jpg

taza_2

Para la rotación de la imagen ejecutamos lo siguiente:

$ convert -rotate 40 taza.jpg taza_r.jpg

Donde 40 es la cantidad de grados a rotar.

taza_33Para la rotación hay otra orden muy curiosa: polaroid. La podemos utilizar de la siguiente manera:

$ convert -polaroid 40 taza.jpg taza_p.jpg

Jugando con ImageMagick (I): Dimensiones, captura, color y efectos

Jueves, 26 de Agosto de 2010 Gaspar Fernández Sin comentarios

A menudo es necesario hacer una manipulación básica de imágenes y da mucha pereza ejecutar GIMP u otro editor de imágenes para una tontería; e incluso a veces necesitamos modificar gran cantidad de imágenes y necesitamos automatizar el proceso:

Ejecución

Aunque ImageMagick, que seguro que lo encontráis en los repositorios de vuestra distribución favorita, da mucho más de sí, aquí veremos ejemplos con convert, import y mogrify

Redimensionado de imágenes

hamburgueson_2$ convert -resize [dimensión/porcentaje] origen destino

Por ejemplo para redimensionar con un ancho de 320 pixels:

$ convert -resize 320 hamburgueson.jpg hamburgueson_mini.jpg

De esta forma, si especificamos la altura, se ignorará, ya que convert intentará mantener el aspecto de la imagen.

También podemos imponer la altura y que se calcule automáticamente el ancho:

$ convert -resize x500 hamburgueson.jpg hamburgueson_mini.jpg

O si queremos, podemos también redimensionar con un porcentaje del tamaño original:

$ convert -resize 40% hamburgueson.jpg hamburgueson_mini.jpg

hamburgueson_21También es posible la redimensión sin mantener el aspecto (relación ancho/alto) de la siguiente forma:

$ convert -resize \!600×300 hamburgueson.jpg hamburgueson_deforme.jpg

Y esto mismo, también por porcentaje:

$ convert -resize \!100×30% hamburgueson.jpg hamburgueson_deforme.jpg

Capturando el escritorio

Puede que nuestro entorno preferido de escritorio no venga con esta funcionalidad, o incluso que estemos desde una máquina remota y queramos ver lo que pasa en el escritorio, una captura de pantalla no viene mal. Para ello podemos hacer lo siguiente:

screenshot-26-08-2010-100842

$ import -window root captura.jpg

O si queremos capturar una ventana en especial:

$ import ventana.jpg

Nos aparecerá un puntero especial que nos permitirá seleccionar la ventana a capturar.

O si queremos capturar una ventana en especial sin necesidad de hacer click (muy útil si estamos en un equipo remoto):

$ import -window “Titulo de la ventana” ventana.jpg

También podemos sacar el ID de la ventana con:

$ wmctrl -l

Y poner en lugar del título, el ID. Es necesario que nos encontremos en el mismo escritorio de la ventana para hacer la captura. Cómo podemos hacer eso remotamente ?

$ wmctrl -l # Nos dirá en la segunda columna el escritorio donde está la ventana

$ wmctrl -s [numero] # Saltaremos al escritorio [numero], el primero es el cero.

Espacios de color

Podemos cambiar el formato del color de forma rápida usando colorspaces, dependiendo del formato de salida y la forma en la que la imagen será utilizada, para vídeo, por ejemplo. Aunque también tenemos algunos espacios interesantes como:hamburgueson_3

$ convert -colorspace gray hamburgueson.jpg hamburgueson_gris.jpg

El tono de gris estará calculado para adecuarse al ojo humano: Gris = 0.299*Rojo+0.587*Verde+0.114*Azul

Aunque también podremos transformar a YUV, XYZ, LAB, HSL, etc; también necesitamos que el formato de salida sea compatible con esos espacios de color. Para ver una lista de todos los colorspaces disponibles:

$ covert -list colorspace

hamburgueson_31También podemos ecualizar la imagen con respecto a los diferentes espacios de color de la siguiente forma:

$ convert -colorspace [espacio] -equalize origen destino

En el ejemplo:

$ convert -colorspace hsl -equalice hamburgueson.jpg hamburgueson_hsl.jpg

Y si no queremos decir el colorspace, usaremos el actual de la imagen:

$ convert -equalize hamburgueson.jpg hamburgueson_e.jpg

Recortar imágenes

Es más cómodo hacerlo con el ratón, pero en ocasiones querremos hamburgueson_4automatizar el proceso, y podemos hacerlo de la siguiente forma:

$ convert -crop [ancho]x[alto]+[x]+[y]

Por ejemplo lo siguiente:

$ convert -crop 300×300+100+100

Creará una imagen de 300×300 y empezará a partir del punto 100×100 (desde la izquierda/arriba).

Bordes

hamburgueson_41Para añadir un borde podemos hacer lo siguiente:

$ convert -bordercolor [color] -border [ancho]x[alto] origen destino

En el ejemplo tenemos:

$ convert -bordercolor black -border 10×10 hamburgueson.jpg hamburgueson_borde.jpg

Para especificar el color podemos hacerlo de varias formas:

$ convert -bordercolor black/red/green/blue… # Por su nombre. Si queremos saber los nombres de color disponibles:

$ convert -list color

$ convert -bordercolor “#aacc11″ # Por su equivalente hexadecimal

$ convert -bordercolor “rgb(100,150,200)” # En función de sus valores RGB

$ convert -bordercolor “hsl(100,150,200)” # En función de sus valores HSL

$convert -bordercolor “cmyk(100,200,150,230)” # En función de sus valores CMYK

… etcétera… tantos formatos como espacios de color.

Ahora bien, queremos quitar el reborde añadido:

$ convert -trim hamburgueson_borde.jpg hamburgueson_nuevo.jpg

Pero es posible que por la compresión jpeg no se detecte bien el borde (es una compresión con pérdida de calidad, y se modifica la imagen; para nosotros el borde es negro, pero para el ordenador hay muchos tonos de negro), entonces podemos hacer lo siguiente:

$ convert -fuzz 10% -trim hamburgueson_borde.jpg hamburgueson_nuevo.jpg

Con esto damos una tolerancia de un 10% al color, es decir, si se parecen los tonos de negro en menos de un 10% serán considerados iguales.

hamburgueson_42También podemos añadir un biselado como el del ejemplo de la siguiente manera:

$ convert -mattecolor red -frame 20×20+10+10 hamburgueson.jpg hamburgueson_rojo.jpg

Filtros y efectos

hamburgueson_32Por si fuera poco, podemos aplicar los filtros típicos a las imágenes como blur, motion-blur, blur gaussiano (gaussian-blur), ruido (noise), carboncillo (charcoal).

En el ejemplo, se ha añadido motion-blur de la siguiente forma:

$ convert -motion-blur 10×40+10+0 hamburgueson.jpg hamburgueson_movido.jpg

Visita otras webs de la red