Foros Club Delphi

Foros Club Delphi (https://www.clubdelphi.com/foros/index.php)
-   Gráficos (https://www.clubdelphi.com/foros/forumdisplay.php?f=8)
-   -   Necesito ayuda para un prgrama que enumere los colores (https://www.clubdelphi.com/foros/showthread.php?t=43743)

Kandorf 18-05-2007 22:46:02

Necesito ayuda para un prgrama que enumere los colores
 
Hola, llevo cosa de un mes con delphi 6 y sé lo básico, les estaría muy agradecido si me ayudaran a hacer un programa que he pensado diciéndome qué objetos me convendría usar y cómo:
Al programa se le pasaría una imagen muy pixelada, y el programa escribiría un número encima de cada color para identificarlo.
Esta sería una de las imágenes que se les podría pasar: http://www.smashboards.com/fzero/emblems/dk_l.jpg esta imagen utiliza pocos colores y se diferencian, pero otras utilizan colores muy parecidos y es casi imposible diferenciarlos.
Os estaría muy agradecido si me pudiérais ayudar.
Gracias por vuestro tiempo. Un saludo.

seoane 18-05-2007 23:39:02

No entiendo muy bien lo que necesitas. Entiendo que quieres dibujar un número sobre cada color, pero, eso quiere decir un numero sobre cada cuadradito identificando el color, o acaso quieres identificar las zonas que ocupa cada color y numerar cada una de ellas. Lo primero es sencillo, pero lo veo poco practico, lo segundo requiere de unos algoritmos bastante complejos para analizar la imagen.

Yo vería mas practico un programa, que creara una lista con todos los colores de la imagen, y luego seleccionando el color de la lista permitiera resaltar ese color, combiandolo por otro color mas llamativo. Por ejemplo, en la imagen que pusiste, si queremos resaltar uno de los marrones le podriamos indicar al programa que lo cambiase por azul, diferenciandolo así de los demás colores.

No se cual es la finalidad de tu programa, si la explicaras podríamos intentar buscar unas solución mas adecuada.

roman 18-05-2007 23:52:26

Usar un número para cada cuadrito también lo veo bastante impráctico. Quizá lo que le convenga es un gotero, como los programas de dibujo, que se pasa el ratón sobre la imagen y nos dice en un hint o un cuadro aparte, el color bajo el cursor.

// Saludos

Kandorf 19-05-2007 00:04:34

Sí, seoane, es lo primero que has dicho, escribir en cada cuadrito un número para identificarlo, el cometido del programa es poder diferenciar cada color de otro, para posteriormente volcarme la imagen al móvil y copiarla(a mano) a un juego(la consola me pilla bastante lejos del ordenador) que tiene un editor de imágenes, cada cuadradito de un color sería un píxel en la imagen del juego. El problema que se me planteaba era que si me pongo a hacerlo sin nada que identifique a cada píxel del dibujo, no sé distinguir entre un color y otro muy parecido.
Muchas gracias por vuestra ayuda y siento molestaros por un antojo tal.

Edición 1:
Para roman: Vale, acabo de pillar lo del gotero, pues sí, sería muy buena idea, para coger el color y poder asignarle el número que sea. Lo que veo un problema es que al ser una imagen jpg y no un mapa de bits, cada cuadrito no será monocromo, sino que habrá muchos subtonos y puede que no coja bien el color...
Muchas gracias a los dos.

Edición 2:
He estado intentándolo desde entonces y he progresado algo, he conseguido que me cargue la imagen en un objeto Image (No sé si es el más apropiado) utilizando un botón (antes no me dejaba, me decía que no soportaba la extensión sin motivo aparente).
Lo otro que he hecho ha sido intentar crear un cuentagotas (gotero) que sabría hacerlo bien si no tuviera que utilizar la propiedad Stretch, pero la imagen es demasiado grande, además, sólo funciona con las imágenes Bitmap (*.bmp), ya que no me deja utilizar la propiedad Pixels en una imagen JPEG. Os paso el código del gotero que he creado para que lo veáis.
Código Delphi [-]
procedure TForm1.ImagenMouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
  if SeleccionColor=True then
  begin
    PosX:=X div (Imagen.Height div Imagen.Picture.Height);    //Esta operación tampoco me deja hacerla cuando uso un JPEG
    PosY:=Y div (Imagen.Width div Imagen.Picture.Width);      //Me da el error: Division by Zero
    Label1.Caption:='X='+IntToStr(PosX)+' Y='+IntToStr(PosY);   //Esta etiqueta la tengo puesta sólo para saber los valores
  end;
end;

procedure TForm1.ImagenClick(Sender: TObject);
begin
  if SeleccionColor=True then
    EdtColor.Color:=Imagen.Canvas.Pixels[PosX,PosY];  //El color lo pongo en un edit con Enabled:=False, no sé hacerlo mejor :$
end;                                                  //que tampoco me deja hacerlo, por la propiedad Pixels de un JPEG
Y por si queréis saber el por qué de la comprobación "SeleccionColor", aquí está el código que la activa y desactiva:
Código Delphi [-]
procedure TForm1.BtnSeleccionarColorClick(Sender: TObject);
begin
  SeleccionColor:=True;
end;

procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if SeleccionColor=True then
    if Key=Vk_Escape then
      SeleccionColor:=False;
end;

Edición 3:
Sigo progresando, finalmente he conseguido hacer el cuentagotas, tan simple como quitar el "Imagen" de delante del "Imagen.Canvas", no hace falta ni la fórmula esa que me inventé para calcular la proporción, que me daba error de división por cero porque la imagen JPEG era más grande que el tamaño del objeto Image y la división daba 0 (al ser división entera) y por eso protestaba... Estoy aprendiendo mucho.
Bueno, ahora que tengo el gotero hecho y una versión beta de un desplegable que me va numerando los colores que voy eligiendo, me queda poner el número de cada color en la imagen, que si no fuera porque el JPEG no me permite usar la propiedad Pixels, creo que sabría hacerlo, puede que acabe cambiando las imágenes de formato. Cuando termine el programa lo pondré a disposición de todo el mundo, aunque no creo que lo queráis para nada xD.
Un saludo.

seoane 20-05-2007 03:03:45

Hola Kandorf, efectivamente para manipular una imagen en condiciones, tienes que convertirla primero en un bitmap.

Pero eso no es problema. Supongo que ahora estas usando algo como esto para cargar la imagen:
Código Delphi [-]
  Image1.Picture.LoadFromFile('imagen.jpg');

Pues cambia esa linea por este otro código:
Código Delphi [-]
var
  Picture: TPicture;
  Bitmap: TBitmap;
begin
  Picture:= TPicture.Create;
  try
    Bitmap:= TBitmap.Create;
    try
      Picture.LoadFromFile('imagen.jpg');
      Bitmap.Width:= Picture.Width;
      Bitmap.Height:= Picture.Height;
      Bitmap.Canvas.Draw(0,0,Picture.Graphic);
      Image1.Picture.Assign(Bitmap);
    finally
      Bitmap.Free;
    end;
  finally
    Picture.Free;
  end;
end;

En cuanto a hacer coincidir las coordenadas del ratón con las coordenadas de la imagen, si la propiedad Streach esta a TRUE. Puede utilizar estas funciones:
Código Delphi [-]
function RatonAImagenX(Imagen: TImage; x: integer): integer;
begin
  Result:= (x * Imagen.Picture.Width) div Imagen.Width;
end;

function RatonAImagenY(Imagen: TImage; y: integer): integer;
begin
  Result:= (y * Imagen.Picture.Height) div Imagen.Height;
end;

No se si me queda lago por ahí ... de todas formas, un consejo, si vas haciendo progresos o tienes nuevas dudas es mejor que escribas otra respuesta nueva en vez de editar tu ultima respuesta. Si no llego a entrar aquí por casualidad no me hubiera enterado de tus 3 ultimas "ediciones"

Kandorf 20-05-2007 19:58:54

Genial, muchas gracias por los códigos, el de cargar la imagen me va perfecto, los otros no tanto, pero no pasa nada, me va con asignarle directamente el valor de X e Y, supongo que es porque uso el Canvas del formulario y no el de la imagen.
Ahora el problema que tengo es que no sé guardar la imagen como jpg, no sé cambiarla de formato, he probado mil cosas modificando el código que me pasaste de cargar pero nada, casi ni sabía o que hacía, finalmente he acabado por poner "Imagen.Picture.Graphic.SaveToFile('.\Imagen.jpg');" que lo guarda, pero como bitmap, que ocupa 3 megas y pico...
He probado también a modificar la imagen simplemente "Imagen.Canvas.Pixels[7,7]:=clBlue;" en un botón para ver cómo va, y lo hace y lo guarda bien, supongo que es la manera de la que haré los números, aunque si supiérais decirme una manera más profesional de hacerlo poniendo números de verdad sería genial.
Por lo pronto me opndré a diseñar los algoritmos que dibujen los números sobre la cuadrícula y el diseño del programa, cuando haya terminado me pondré a ver si consigo que me lo guarde en JPEG, que por lo pronto va bien con el bitmap y no me puedo quejar tanto...

Editaba el post para no apropiarme del primer puesto del foto cada vez que me daba por relatar algo xD pero bueno, a partir de ahora crearé una respuesta nueva.
Un saludo y muchas gracias por toda la ayuda y por vuestro tiempo.

Delphius 20-05-2007 23:15:57

Como el procesamiento se realiza sobre un mapa de bits, al intentar guardarlo como jpg fallará. Para hacer esto deberás convertir tu mapa de bits a jpg empleando la clase TJPEGImage:

Código Delphi [-]
               procedure TForm1.Button1Click(Sender: TObject);
               var 
                 MyJPEG : TJPEGImage;
                 MyBMP  : TBitmap;
               begin 
                 MyBMP := TBitmap.Create;
                 with MyBMP do 
                   try
                     {Cargamos el BMP}
                     {Load the BMP} 
                     LoadFromFile('YourBmpHere.BMP');
                     MyJPEG := TJPEGImage.Create;
                     with MyJPEG do begin 
                       Assign(MyBMP);
                       {Grabamos el JPG}
                       {Save the JPG}
                       SaveToFile('YourJpegHere.JPEG');
                       Free;
                     end; 
                   finally
                     Free;
                   end; 
               end;
Este código lo saqué de trucomania (Truco n° 147).

He visto que empleas la orden Pixels[] para que de acuerdo a una coordenada puedas conocer el valor. La misma ayuda de Delphi aclara que emplear este método es lento. Que es recomendable usar ScanLine().

Lo malo de emplear scanline es que es un poquito más complicado. Ya que hay que ir corriendo el puntero...

Las coordenadas la obtienes como dice seoane:
Código Delphi [-]
function RatonAImagenX(Imagen: TImage; x: integer): integer;
begin
  Result:= (x * Imagen.Picture.Width) div Imagen.Width;
end;

function RatonAImagenY(Imagen: TImage; y: integer): integer;
begin
  Result:= (y * Imagen.Picture.Height) div Imagen.Height;
end;
Ahora para acceder al pixel debemos hacer esto:

1. Tener un puntero de tipo array para acceder a los canales RGB de dicho pixel:
Código Delphi [-]
type
TRGB = array[1..3] of byte;
PRGB = ^TGRB;

2. Accedemos la la posición de memoria en la coordenada Y:
Código Delphi [-]
var RGB = PRGB;
....
RGB := Picture.Bitmap.ScanLine[Y];

3. Una vez ubicados en la posición Y, nos movemos sobre el eje horizontal X veces:
Código Delphi [-]
for pos := 0 to X do
     inc(RGB);

4. Ahora, en RGB quedó registrado el valor de cada canal (Azul = 1, Verde = 2, Rojo = 3). Como sabrás, dicho valor está comprendido en el rango: [0,255]

Código Delphi [-]
Rojo := RGB[3];
Verde := RGB[2];
Azul := RGB[1];

Tiene un poco más de código, pero el acceso mediante scanline es más rápido que el por Pixels. Sobre todo si es que la imagen es grande.

Yo hice una pruebas (del tipo filtrado) "Pixels vs Scanline" y la verdad es que puede llegar a ser perceptible la lentitud.

Espero que te sirva.
Saludos,

Kandorf 21-05-2007 19:56:11

Muchas gracias, ahora ya guarda, he modificado un poco el código, porque yo uso un Dialog de guardado para seleccionar la ruta, por si queréis ver cómo ha quedado, lo pongo aquí:
Código Delphi [-]
procedure TFormulario.ActGuardarExecute(Sender: TObject);
var
  FotoJPEG : TJPEGImage;
  FotoBMP  : TBitmap;
begin
    if DiaGuardar.Execute then
    begin
      FotoBMP := TBitmap.Create;
      try
        FotoBMP:=Imagen.Picture.Bitmap;
        FotoJPEG:=TJPEGImage.Create;
        with FotoJPEG do
        begin
          Assign(FotoBMP);
          SaveToFile(DiaGuardar.FileName);
          Free;
        end;
      finally
        FotoBMP.Free;
    end;
end;

La manera que me has dado de manejar los pixels con el ScanLine ahora mismo no la entiendo, entre otras cosas, nunca he utilizado punteros en Pascal, así que por ahora voy a hacerlo con la propiedad pixels, que ya me manejo bien con ella, y cuando termine el programa estudiaré el código que me has pasado y lo modificaré y usando el Scanline, que con Pixels va bastante lento el programa, ya que las imagenes que se le pasan son grandísimas.

Ahora estoy con que me escriba los números en cada casilla. Mi profesor me ha dicho hoy una manera muy fácil de hacerlo, usando la propiedad Imagen.Canvas.TextOut, pero el fondo del número me lo pinta en blanco y no me gusta el formato del número, así que me he creado un BMP con los números dibujados usando como base el tamaño de los pixels de la imagen, me queda hacer que funcione bien.
Un saludo, muchas gracias por toda la ayuda que me estáis prestando, si no fuera por vosotros no habría podido manejar bien las imágenes.

seoane 21-05-2007 20:08:30

:D Eres un hombre de recursos, si no conoces una función o una propiedad te las arreglas para seguir adelante. Eso esta muy bien :)

Para que no que no pinte el fondo de blanco al escribir utiliza esto primero:
Código Delphi [-]
  Imagen.Canvas.Brush.Style:= bsClear;

Kandorf 21-05-2007 20:12:44

Jajajaja vale, muchas gracias. Es que yo he aprendido a programar con C en modo texto, y se aprende mucho así, con decirte que programé mi "Pong modo texto para 2 players" xD.
Axias ^_^.

Kandorf 22-05-2007 18:46:02

Bueno, la aplicación, si se trata con cariño, ya es funcional desde ayer, iba a decirlo en un post, pero me dio un error el firefox y se cerró, después me dio pereza volver a escribirlo todo.
seoane, en relación a tu comentario también quería comentarte una función graciosa de mi programa, el problema que se me planteaba era que al comprobar los colores de cada pixel de la cuadrícula de la imagen sólo comprobaba el primero, y al ser JPG, es muy posible que no sea el color exacto buscado, así que lo hice a lo cafre, comprobando hasta encontrar algún pixel del color buscado o comprobarlos todos, que esa es otra cosa, en C, que es el lenguaje en el que estoy acostumbrado a programar, poniendo un "return TalCosa" la función termina y devuelve el valor, en Pascal con el result símplemente le asigna el valor y sigue ejecutando la función, así que apañé la función de esta manera:
Código Delphi [-]
function ComprobarPixel(ColorBuscado: TColor; X, Y: Integer):Boolean; //X e Y son las coordenadas desde la que empieza a buscar el color
var
  i,j: Integer;
  Encontrado: Boolean; //Esta variable es para salir rapidante de la función si encuentra el valor
begin
  with Formulario do
    for j:=0 to 11 do
    begin
      for i:=0 to 11 do
        if Imagen.Canvas.Pixels[X+i,Y+j]=EdtColor.Color then
        begin
          Encontrado:=True;
          break;
        end;
      if Encontrado=True then
        break;
    end;
    if Encontrado=True then
      Result := True
    else
      Result := False;
end;
EDIT:
Buahh! Que se me olvidaba... quería preguntaros por unas funciones que obtengan el color rojo, verde y azul de un RGB, es para crearme un rango al comprobar que un color es el blanco, por el problema de los JPG. He estado buscando en la ayuda y en el foro y no he encontrado nada...

Saludos.

seoane 22-05-2007 19:01:36

Todo es conocer el lenguaje:
Código Delphi [-]
        if Imagen.Canvas.Pixels[X+i,Y+j]=EdtColor.Color then
        begin
            Result:= True;
            Exit; //<-- Igual al return; de C
        end;
Pocas cosas podrás hacer en C que no puedas hacer en pascal. Yo hasta ahora no he encontrado ninguna ...

Para obtener los valores de rojo, verde y azul utiliza GetRValue, GetGValue y GetBValue.

Kandorf 22-05-2007 19:11:34

Eso ^^ las había visto por ahí alguna vez, pero he estado buscándolas durante más de media hora y no las he encontrado.
Uno, eso del exit es un apaño de lo que hace C xD pero está genial, yo para que me resulte más agradable a la vista lo pondré de esta manera xD:
Código Delphi [-]
if Imagen.Canvas.Pixels[X+i,Y+j]=EdtColor.Color then begin
    Result:= True; Exit; end;
Es por el royo psicológico xD, como se suele hacer con el "fflush(stdin);" en C, después de pedir un valor por teclado. En fin...
Una cosa que no hace Pascal (Bueno, también lo puede hacer, con apaño) es que el "for" de C tiene una comprobación de verdad, en Pascal sólo incrementa una variable ^^.
Un saludo, muchas gracias por la gran ayuda con la que me estás obsequiando.

Kandorf 05-06-2007 19:01:15

Me acerco al foro con vergüenza después de tanto tiempo.
Al programa le he ido dedicando unos días más tiempo y otros menos, ahora está casi listo, pero me surgen 3 problemas:
Cuando el pixel era demasiado oscuro, yo quería escribir el número en color blanco, y lo hacía de esta manera:
Código Delphi [-]
if (((GetRValue(Color)>50) and (GetGValue(Color)>50)) and (GetBValue(Color)>50)) then
  ColorPixel:=CLBlack     //ColorPixel es una variable en la que guardo el color del que voy a pintar el número
else
  ColorPixel:=CLWhite;
Pero sólo me tiene en cuenta la última comprobación de verdad, y un número que tenga mucho rojo y los demás tonos bajos (por ejemplo, el rojo:rolleyes:), me lo pinta de color blanco. Hasta ahora no me había preocupado por este problema, porque tampoco pasa nada si el número me lo pinta de color blanco, pero es que esas 3 comprovaciones de verdad son un pilar básico en mi programa, ya que, al ser imágenes JPG las que utiliza, yo me creo un rango que comprueba el color para saber si es el mismo.
Mi pregunta es si hay alguna manera de hacer 3 comprobaciones de verdad en un "if" o si hay alguna manera distinta para conseguir el mismo resultado.

El otro problema que me ha surgido es que al guardar la imagen con el código que me dísteis al principio funcionaba perfectamente, pero ahora me da un error de violación de posición de memoria al terminar de ejecutar la función. Esta es la función:
Código Delphi [-]
procedure TFormulario.ActGuardarExecute(Sender: TObject);
var
  FotoJPEG : TJPEGImage;
  FotoBMP  : TBitmap;
begin
  if DiaGuardar.Execute then
  begin
    FotoBMP := TBitmap.Create;
    try
      FotoBMP:=Imagen.Picture.Bitmap;
      FotoJPEG:=TJPEGImage.Create;
      with FotoJPEG do
      begin
        Assign(FotoBMP);
        SaveToFile(DiaGuardar.FileName);
        Free;
      end;
    finally
      FotoBMP.Free;
    end;
  end;
end;
Que yo recuerde no he modificado la función desde que la hice y ahora me viene dándome problemas -_- No sé dónde está el cambio... ¿Sabríais decirme vosotros dónde está el problema?

EDIT: Se me olvidaba, no conozco la propiedad que cambia el color de la fuente del "Imagen.Canvas.Brush", lo he intentado con "Imagen.Canvas.Brush.Color:=clWhite;", pero lo que me cambia de color es el fondo (como si lo marcara con subrayador).

Después de solucionar estas tres cosas ya sólo me queda utilizar la propiedad que me pasásteis más rápida que pixels y poco más ^^.
Un Saludo y muchas gracias por vuestro tiempo.


La franja horaria es GMT +2. Ahora son las 02:24:11.

Powered by vBulletin® Version 3.6.8
Copyright ©2000 - 2026, Jelsoft Enterprises Ltd.
Traducción al castellano por el equipo de moderadores del Club Delphi