Ir al contenido


Foto

[Truco Delphi] Convertir una imagen de Color a Escala de Grises.


  • Por favor identifícate para responder
15 respuestas en este tema

#1 sir.dev.a.lot

sir.dev.a.lot

    Advanced Member

  • Miembros
  • PipPipPip
  • 545 mensajes
  • Location127.0.0.1

Escrito 18 agosto 2016 - 11:57

[Truco Delphi] Convertir una imagen de Color a Escala de Grises.

 

Hay que adicionar la unidad Graphics


delphi
  1. uses Graphics;

Luego las funciones, en este caso 2 funciones


delphi
  1. function ConvertToGrayscale1(Bitmap: TBitmap): TBitmap;
  2. var
  3. i, j: Integer;
  4. Grayshade, Red, Green, Blue: Byte;
  5. PixelColor: Longint;
  6. begin
  7. with Bitmap do
  8. for i := 0 to Width - 1 do
  9. for j := 0 to Height - 1 do
  10. begin
  11. PixelColor := ColorToRGB(Canvas.Pixels[i,j]);
  12. Red := PixelColor;
  13. Green := PixelColor shr 8;
  14. Blue := PixelColor shr 16;
  15. Grayshade := Round(0.3*Red + 0.6*Green + 0.1*Blue);
  16. Canvas.Pixels[i,j] := RGB(Grayshade,Grayshade,Grayshade);
  17. end;
  18. Result := Bitmap;
  19. end;
  20.  
  21.  
  22.  
  23. procedure ConvertToGrayscale2(Bmp: TBitmap);
  24. type
  25. TRGBArray = array[0..32767] of TRGBTriple;
  26. PRGBArray = ^TRGBArray;
  27.  
  28. var
  29. x, y, Gray: Integer;
  30. Row: PRGBArray;
  31. begin
  32. Bmp.PixelFormat := pf24Bit;
  33. for y := 0 to Bmp.Height - 1 do
  34. begin
  35. Row := Bmp.ScanLine[y];
  36. for x := 0 to Bmp.Width - 1 do
  37. begin
  38. Gray := (Row[x].rgbtRed + Row[x].rgbtGreen + Row[x].rgbtBlue) div 3;
  39. Row[x].rgbtRed := Gray;
  40. Row[x].rgbtGreen := Gray;
  41. Row[x].rgbtBlue := Gray;
  42. end;
  43. end;
  44. end;

Saludos!


  • 1

#2 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 18 agosto 2016 - 04:49

Desconozco si en las nuevas versiones ha cambiado en algo las implementaciones de ScanLine[] como de los tipos para almacenar la tercia RGB y el puntero a dicha estructura.

Lo que si recuerdo es que al menos para las versiones anteriores, había que incrementar el puntero para ubicarse en el siguiente pixel:

 

Esto compila en D6, D7, y si no me equivoco al menos hasta D2006 (y quizá hasta XE1):


delphi
  1. const
  2. {* Constantes asociadas a los canales *}
  3. BLUE = 1; // Canal 1 -> Frecuencias Bajas
  4. GREEN = 2; // Canal 2 -> Frecuencias Medianas
  5. RED = 3; // Canal 3 -> Frecuencias Altas


delphi
  1. type
  2. {* Estructuras asociadas al canal *}
  3. TRGBChannel = array[BLUE..RED] of byte;
  4. PTRGBChannel = ^TRGBChannel;
  5. TRGBArray = array[1..3] of byte;


delphi
  1. function RGBtoGray(RGB: TRGBArray; Option: TGrayOption): byte;
  2. {******************************************************************************
  3.  Descripción:
  4.  Calcula el nuevo valor de gris en función del formato a utilizar.
  5.  
  6.  Parámetros:
  7.  - RGB: Estructura del Canal a evaluar
  8.  - Option: Formato de conversión a utilizar.
  9.   * goBrightness: Basado en el brillo.
  10.   * goIntensity: Basada en la intensidad.
  11.  ******************************************************************************}
  12. var i, sum: integer;
  13. begin
  14. sum := 0;
  15. if Option = goBrightness
  16. then result := Round((0.3 * RGB[RED]) + (0.59 * RGB[GREEN]) +
  17. (0.11 * RGB[BLUE]))
  18. else begin
  19. for i:= RED downto BLUE do
  20. sum := sum + RGB[i];
  21. result := Round(sum/3);
  22. end;
  23. end; {* Fin F. RGBtoGray *}
  24.  
  25. function ImageToGray(Image: TPicture; Option: TGrayOption): dword;
  26. {******************************************************************************
  27.  Descripción:
  28.  Convierte una imagen en color a nivel de gris dependiendo del formato de con-
  29.  versión elegido.
  30.  
  31.  La imagen resultante es formateada a mapas de bits, independientemente del for-
  32.  mato inicial.
  33.  
  34.  Parámetros:
  35.  - Image: Imagen a convertir.
  36.  - Option: formato de conversión a utilizar.
  37.   * goBrightness: Basado en el brillo.
  38.   * goIntensity: Basada en la intensidad.
  39.  
  40.  Resultados:
  41.  - CONVERT_TO_GRAY: ante una operación exitosa.
  42.  - NOT_CONVERT_TO_GRAY: ante una operación fallida.
  43.  ******************************************************************************}
  44. var PChannel: ^TRGBChannel;
  45. i, j: integer;
  46. B: byte;
  47. Bmp: TBitmap;
  48. RGB: TRGBArray;
  49. begin
  50. // Damos por supuesto que no se pudo realizar la operación
  51. result := NOT_CONVERT_TO_GRAY;
  52. // Si no es formato de mapa de bits hay que convertirla
  53. if not (Image.Graphic is TBitmap)
  54. then begin
  55. Bmp := TBitmap.Create;
  56. try
  57. Bmp.Width := Image.Width;
  58. Bmp.Height := Image.Height;
  59. Bmp.Canvas.Draw(0,0,Image.Graphic);
  60. Image.Assign(Bmp);
  61. finally
  62. Bmp.Free;
  63. end;
  64. end;
  65. // Asignamos un formato de 24 bits (pero a nivel de gris)
  66. Image.Bitmap.PixelFormat := pf24bit;
  67. for j := 0 to Image.Bitmap.Height - 1 do
  68. begin
  69. PChannel := Image.Bitmap.ScanLine[j];
  70. for i := 0 to Image.Bitmap.Width - 1 do
  71. begin
  72. // guardamos los valores en un vector
  73. RGB[BLUE] := PChannel^[BLUE];
  74. RGB[GREEN] := PChannel^[GREEN];
  75. RGB[RED] := PChannel^[RED];
  76. // obtenemos el nuevo valor según opción
  77. // los tres canales deben tener igual valor para formar gris
  78. B := RGBToGray(RGB,Option);
  79. PChannel^[BLUE] := B;
  80. PChannel^[GREEN] := B;
  81. PChannel^[RED] := B;
  82. inc(PChannel);
  83. end;
  84. end;
  85. // Finalizó la operación en forma exitosa
  86. result := CONVERT_TO_GRAY;
  87. end; {* Fin F. ImageToGray *}

Esta muestra de código es una pequeña parte de mis primeras implementaciones de mi "mini-framework" de tratamiento de imágenes. Tiene sus años, y debo actualizarlo para portarlo a Lazarus. Aunque no se si tendría sentido, ya que la suite BGRABitmap que viene incluída en CodeTyphon ya de base ofrece muchas de las funcionalidades que yo estuve haciendo en mi mini framework.

 

Puede verse que dentro del for width al final se realiza un Inc(). Al avanzar en 1, se consigue que ahora el puntero apunte al siguiente pixel.

 

Ahora desconozco si algo ha cambiado en los nuevos Delphi como para que no se necesite estar desplazando el puntero y lo hace automáticamente.

 

Lo que si sería oportuno mencionar es el tipo de algoritmo que implementa cada versión. Tu ConvertToGrayScale1 se basa en la teoría del brillo (que según los entendidos es el algoritmo más apropiado) mientras que el ConvertToGrayScale2 se basa en la teoría de la intensidad.

En lo posible debe evitarse recorrer los pixels mediante Pixels[]. Es una forma ineficiente. En su lugar se aconseja emplear Scanline[] que trabaja a bajo nivel y está optimizada. No creo que te resulte difícil implementar una versión de ConvertToGrayScale1 utilizando Scanline[] como si lo aplicas en la versión 2. ;)

 

Saludos,


  • 2

#3 sir.dev.a.lot

sir.dev.a.lot

    Advanced Member

  • Miembros
  • PipPipPip
  • 545 mensajes
  • Location127.0.0.1

Escrito 19 agosto 2016 - 09:45

@Delphius... te tengo algo para pensar en los proximos posts que abra, son OPEN SOURCE.

 

Por favor, dales una mirada.   Te van a interesar.

 

Saludos!


  • 0

#4 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 20 agosto 2016 - 12:23

Puedo darles una mirada, y ofrecer mis apreciaciones.
Más es mi deber informarte que no podré asesorarte con código Delphi ya que yo utilizo CodeTyphon. Habrá cosas que pueden andar y otras que no.

Saludos,
  • 0

#5 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 22 agosto 2016 - 12:55

Voy a proponer otra versión. En este caso se convierte el Bitmap a escala de grises y devuelve un nuevo Bitmap estilo 8 bits por pixel y paleta de 256 grises con lo que el resultado final ocupará mucho menos espacio en caso de guardarse en un archivo. La tecnica usada es con ScanLine que es mucho más rápida.


delphi
  1. function BitmapToGrayscale(Bitmap: TBitmap): TBitmap;
  2. var
  3. TempBitmap: TBitmap;
  4. X,Y,i: Integer;
  5. Gray: Byte;
  6. Color: DWORD;
  7. Pixel: PRGBTRIPLE;
  8. D: PBYTE;
  9. Palette: PLOGPALETTE;
  10. begin
  11. // creando un bitmap temporal de 24 bits
  12. TempBitmap:= TBitmap.Create;
  13. TempBitmap.Assign(Bitmap);
  14. TempBitmap.PixelFormat:= pf24bit;
  15.  
  16. // pasando a escala de grises
  17. for Y := 0 to TempBitmap.Height - 1 do
  18. begin
  19. Pixel := TempBitmap.ScanLine[Y];
  20. for X := 0 to TempBitmap.Width - 1 do
  21. begin
  22. Gray := Round((0.299 * Pixel.rgbtRed) + (0.587 * Pixel.rgbtBlue) + (0.114 * Pixel.rgbtGreen));
  23. Pixel.rgbtRed := Gray;
  24. Pixel.rgbtGreen := Gray;
  25. Pixel.rgbtBlue := Gray;
  26. Inc(Pixel);
  27. end;
  28. end;
  29.  
  30. // Creando una paleta de grises
  31. GetMem(Palette, sizeof(LOGPALETTE) + 255*sizeof(DWORD));
  32. Palette.palVersion:= $300;
  33. Palette.palNumEntries:= 256;
  34. for i:= 0 to 255 do
  35. begin
  36. Color:= $FF000000 or i or (i shl 8) or (i shl 16);
  37. Palette.palPalEntry[i]:= PPALETTEENTRY(@Color)^;
  38. end;
  39.  
  40. // Creando un Bitmap de salida de 8 bits y aplicando la paleta
  41. Result:= TBitmap.Create;
  42. Result.PixelFormat:= pf8bit;
  43. Result.Width:= TempBitmap.Width;
  44. Result.Height:= TempBitmap.Height;
  45. Result.Palette:= CreatePalette(Palette^);
  46.  
  47. // Transformando los pixels
  48. for Y := 0 to Bitmap.Height - 1 do
  49. begin
  50. Pixel := TempBitmap.ScanLine[Y];
  51. D:= Result.ScanLine[Y];
  52. for X := 0 to TempBitmap.Width - 1 do
  53. begin
  54. for i:= 0 to 255 do
  55. if (Pixel.rgbtRed = Palette.palPalEntry[i].peRed) and (Pixel.rgbtGreen = Palette.palPalEntry[i].peGreen) and (Pixel.rgbtBlue = Palette.palPalEntry[i].peBlue) then break;
  56. D^:= i;
  57. Inc(Pixel); Inc(D);
  58. end;
  59. end;
  60.  
  61. // Limpiando
  62. FreeMem(Palette);
  63. TempBitmap.Free;
  64. end;

El Bitmap devuelto deberá ser destruido por el usuario cuando precise.

 

Aprovecho para comentar que he estado experimentando con el nuevo C++ Builder Rad Studio 10.1 starter edition para compilar GDI plus versión 1.1 y probar la clase Effect y derivadas, en concreto HueSaturationLightness. los resultados son muy buenos.

 

 

Saludos.


  • 1

#6 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 22 agosto 2016 - 01:47

El truco de usar la paleta lo había mencionado hace un tiempo yo en este hilo.

 

Con el esquema de la paleta no hace falta explorar siquiera los pixels. Basta con crear la paleta y asociarsela a nuestro BMP temporal. Luego aplicar un Draw() del original sobre nuestro nuevo "lienzo" y por último guardarla. Gracias a la paleta se hace responder a cada RGB el tono de gris de forma automática.

 

El proceso es directo y no es necesario calcular el gris, armar la paleta y repintar nuevamente como el que que aplicas.

 

Saludos,


  • 0

#7 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 22 agosto 2016 - 03:07

Pues los resultados no son los mismos. He utilizado la técnica que comentas y la que expongo, esto es lo que sucede:
 
Imagen original:
Archivo adjunto  image.bmp   147,32KB   1 descargas
Archivo adjunto  image.jpg   5,9KB   0 descargas
 
Imagen sin modificar los Pixels:
Archivo adjunto  prueba1.bmp   50,14KB   1 descargas
Archivo adjunto  prueba1.jpg   16,19KB   0 descargas
 
Imagen modificando los Pixels:
Archivo adjunto  prueba.bmp   50,14KB   2 descargas
Archivo adjunto  prueba.jpg   11,16KB   0 descargas
 
La imagen sin modificar pixels la he creado primero sin crear una paleta de grises y luego haciéndolo. El resultado no varía y la fotografía tiene unos tonos en escala de grises irreales.
 
 
Saludos.
  • 0

#8 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 22 agosto 2016 - 03:57

Vaya...

Pues entonces mi truco tiene sus fallas y fue de casualidad, por las imágenes con las que había probado, que no se notaba esas diferencias. :o  :(

 

A mi lo que me llama mucho la atención es la última parte:


delphi
  1. Result:= TBitmap.Create;
  2. Result.PixelFormat:= pf8bit;
  3. Result.Width:= TempBitmap.Width;
  4. Result.Height:= TempBitmap.Height;
  5. Result.Palette:= CreatePalette(Palette^);
  6.  
  7. // Transformando los pixels
  8. for Y := 0 to Bitmap.Height - 1 do
  9. begin
  10. Pixel := TempBitmap.ScanLine[Y];
  11. D:= Result.ScanLine[Y];
  12. for X := 0 to TempBitmap.Width - 1 do
  13. begin
  14. for i:= 0 to 255 do
  15. if (Pixel.rgbtRed = Palette.palPalEntry[i].peRed) and (Pixel.rgbtGreen = Palette.palPalEntry[i].peGreen) and (Pixel.rgbtBlue = Palette.palPalEntry[i].peBlue) then break;
  16. D^:= i;
  17. Inc(Pixel); Inc(D);
  18. end;
  19. end;

¿Esa es la parte que corrige el defecto?

¿Que sucede si directamente haces:

 

1. Crear la paleta

2. Asociar la paleta a nuestro temporal y establecer el temporal a formato 8bits.

3. Leer la imagen original y calcular el valor de gris de cada pixel

3.1. Asignar el valor calculado al pixel correspondiente de temporal

4. Guardar/devolver el temporal

 

Es decir, proceder a calcular los grises y pintas sobre el temporal directamente ya con la paleta y no estar aplicando esa 2da "pasada". No termino de comprender esa etapa.

 

Desafortunadamente mi truco era cuando aún disponía de Delphi. Y ya no estoy en condiciones de probarlo.

 

Saludos,


  • 0

#9 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 22 agosto 2016 - 04:19

Vaya...
Pues entonces mi truco tiene sus fallas y fue de casualidad, por las imágenes con las que había probado, que no se notaba esas diferencias. :o  :(
 
A mi lo que me llama mucho la atención es la última parte:


delphi
  1. Result:= TBitmap.Create;
  2. Result.PixelFormat:= pf8bit;
  3. Result.Width:= TempBitmap.Width;
  4. Result.Height:= TempBitmap.Height;
  5. Result.Palette:= CreatePalette(Palette^);
  6.  
  7. // Transformando los pixels
  8. for Y := 0 to Bitmap.Height - 1 do
  9. begin
  10. Pixel := TempBitmap.ScanLine[Y];
  11. D:= Result.ScanLine[Y];
  12. for X := 0 to TempBitmap.Width - 1 do
  13. begin
  14. for i:= 0 to 255 do
  15. if (Pixel.rgbtRed = Palette.palPalEntry[i].peRed) and (Pixel.rgbtGreen = Palette.palPalEntry[i].peGreen) and (Pixel.rgbtBlue = Palette.palPalEntry[i].peBlue) then break;
  16. D^:= i;
  17. Inc(Pixel); Inc(D);
  18. end;
  19. end;

¿Esa es la parte que corrige el defecto?
¿Que sucede si directamente haces:
 
1. Crear la paleta
2. Asociar la paleta a nuestro temporal y establecer el temporal a formato 8bits.
3. Leer la imagen original y calcular el valor de gris de cada pixel
3.1. Asignar el valor calculado al pixel correspondiente de temporal
4. Guardar/devolver el temporal
 
Es decir, proceder a calcular los grises y pintas sobre el temporal directamente ya con la paleta y no estar aplicando esa 2da "pasada". No termino de comprender esa etapa.
 
Desafortunadamente mi truco era cuando aún disponía de Delphi. Y ya no estoy en condiciones de probarlo.
 
Saludos,

 

Efectivamente esa el la parte que corrige el defecto. Sencillamente escaneo el bitmap y busco cada color en la paleta creada para sustituirlo por su índice.

El proceso se puede hacer de una sola pasada como dices, pero quería independizar las dos partes del código para que se vea bien la conversión al formato de 8 bits, también pensé en hacerlo a bajo nivel con la API pero no me pareció el lugar oportuno.

Lo que propones sería algo como esto:


delphi
  1. function BitmapToGrayscale2(Bitmap: TBitmap): TBitmap;
  2. var
  3. TempBitmap: TBitmap;
  4. X,Y,i: Integer;
  5. Gray: Byte;
  6. Color: DWORD;
  7. Pixel: PRGBTRIPLE;
  8. D: PBYTE;
  9. Palette: PLOGPALETTE;
  10. begin
  11. // creando un bitmap temporal de 24 bits
  12. TempBitmap:= TBitmap.Create;
  13. TempBitmap.Assign(Bitmap);
  14. TempBitmap.PixelFormat:= pf24bit;
  15.  
  16. // Creando una paleta de grises
  17. GetMem(Palette, sizeof(LOGPALETTE) + 255*sizeof(DWORD));
  18. Palette.palVersion:= $300;
  19. Palette.palNumEntries:= 256;
  20. for i:= 0 to 255 do
  21. begin
  22. Color:= $FF000000 or i or (i shl 8) or (i shl 16);
  23. Palette.palPalEntry[i]:= PPALETTEENTRY(@Color)^;
  24. end;
  25.  
  26. // Creando un Bitmap de salida de 8 bits y aplicando la paleta
  27. Result:= TBitmap.Create;
  28. Result.PixelFormat:= pf8bit;
  29. Result.Width:= TempBitmap.Width;
  30. Result.Height:= TempBitmap.Height;
  31. Result.Palette:= CreatePalette(Palette^);
  32.  
  33. // Pasando en Result la imegan en escala de grises
  34. for Y := 0 to TempBitmap.Height - 1 do
  35. begin
  36. Pixel := TempBitmap.ScanLine[Y];
  37. D:= Result.ScanLine[Y];
  38. for X := 0 to Bitmap.Width - 1 do
  39. begin
  40. Gray := Round((0.299 * Pixel.rgbtRed) + (0.587 * Pixel.rgbtBlue) + (0.114 * Pixel.rgbtGreen));
  41. for i:= 0 to 255 do
  42. if (Gray = Palette.palPalEntry[i].peRed) and (Gray = Palette.palPalEntry[i].peGreen) and (Gray = Palette.palPalEntry[i].peBlue) then break;
  43. D^:= i;
  44. Inc(Pixel); Inc(D);
  45. end;
  46. end;
  47.  
  48. // Limpiando
  49. FreeMem(Palette);
  50. TempBitmap.Free;
  51. end;


Saludos.


  • 1

#10 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 22 agosto 2016 - 04:41

Entonces, para ser más claros, ¿Se puede prescindir de ese for-if y directamente reemplazar el valor del pixel en temporal (ya previamente configurado a 8bits y con la paleta) por el gris calculado en base al original?

 

Porque en lo que estoy pensando es que o bien yo he armado y definido mal la paleta o bien Draw() en D6 tiene sus jugarretas.

 

Saludos,


  • 0

#11 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 22 agosto 2016 - 04:47

Entonces, para ser más claros, ¿Se puede prescindir de ese for-if y directamente reemplazar el valor del pixel en temporal (ya previamente configurado a 8bits y con la paleta) por el gris calculado en base al original?

 

No, ese código sustituye el color encontrado en la paleta por su índice. Recuerda que los formatos con paleta son indexados y no guardan en el pixel el color, sino el índice del mismo en la paleta.

 

Saludos.


  • 0

#12 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 22 agosto 2016 - 04:53

Ahora me queda claro.

 

¡Entonces yo hice cualquier cosa con la paleta en mi truco!

Se ve que no entendí bien como es el uso de paleta. :

 

Voy a añadir una nota aclaratoria en mi hilo sobre el error y poner un enlace a este tema para que tengan una solución. Es lo mínimo que debo hacer.

 

Saludos,


  • 0

#13 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 22 agosto 2016 - 11:37

En realidad ese truco funciona pero los resultados pueden mejorarse.   :)

 

Saludos.


  • 0

#14 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 23 agosto 2016 - 05:33

En realidad ese truco funciona pero los resultados pueden mejorarse.   :)

 

Saludos.

 

De que funciona, se me hace que es de pura casualidad porque estamos justo en 256 tonos y justo cada canal tiene esa cantidad de valores posibles.

Viendo las fotos que tu subiste me doy cuenta de que tiene un problema. Defecto sería si pasara desapercibido, y en esas fotos se ve bien claro.

Imaginate usar el sistema de paleta para una de 24bits o 16bits y en donde hay más entradas de colores y tonos. Con mi forma de implementarlo los colores serían cualquier cosa.

 

Saludos,


  • 0

#15 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.107 mensajes
  • LocationMadrid - España

Escrito 23 agosto 2016 - 09:58

Los formatos indexados admiten como máximo una paleta de 256 colores, así que los defectos en la imagen no van a ir mas allá de esto.

 

 

Saludos.


  • 0

#16 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 23 agosto 2016 - 11:06

Los formatos indexados admiten como máximo una paleta de 256 colores, así que los defectos en la imagen no van a ir mas allá de esto.

 

 

Saludos.

 

Vaya. Aprendí algo nuevo.

Creía que es posible definir una paleta de más de 256 colores. Recuerdo haber visto en algunos programas de diseño que ofrecen paletas bastante extensas.

 

Saludos,


  • 0




IP.Board spam blocked by CleanTalk.