segunda-feira, 28 de dezembro de 2009

IplImage e FXImage: revisitado

Anteriormente, falei aqui como utilizar uma imagem do OpenCV (IplImage) e uma do FOX (FXImage), ambas compartilhando o vetor de pixels. Confira aqui.

Pois bem. Fazendo daquela maneira, há certos problemas: ambos objetos devem permanecer vivos, e o dono dos pixels tem que ser a IplImage.

Isto pode não ser desejado. Por exemplo, vou citar o caso que aconteceu comigo e tive que matar um pouco a cabeça para resolver.

Muitas imagens que eu exibo (com o FOX Toolkit) precisam antes passar por algum processamento (com o OpenCV). Depois disso, eu não preciso mais da IplImage, apenas dos pixels.

Entretanto, seguir os passos explicados no outro tópico levava a um problema seriíssimo, que será explicado no decorrer deste tópico.

Compartilhando pixels: antigamente

Conforme eu havia explicado, criava-se primeiro a IplImage e depois a FXImage, utilizando o vetor de pixels, mais ou menos assim:


IplImage *iplImage = cvCreateImage(cvSize(200, 200), IPL_DEPTH_8U, 4);

FXImage *fxImage = new FXImage(getApp(), iplImage->imageData,
    IMAGE_KEEP|IMAGE_OWNED|IMAGE_SHMP|IMAGE_SHMI,
    iplImage->width, iplImage->height);


Lembrando que era necessário um cast horroroso com reinterpret_cast para transformar os pixels da IplImage de char* para FXColor.

Com isso, eu tenho que minha IplImage "doou" seus pixels para a FXImage, que tomou conta deles. Assim, a IplImage poderia ser dispensada (na verdade, somente o cabeçalho da imagem; o OpenCV permite isso) que a FXImage, quando for liberada, libera também o vetor de pixels.

O problema

Isso causa um problema que eu demorei muito para descobrir. E, depois de descoberto, vê-se que é uma coisa tão simples... é sempre um detalhezinho que cega a gente.

O grande problema é que, como o vetor de pixels é alocado pela IplImage, ele é alocado usando malloc. A FXImage, quando tenta liberar, tenta com delete. Aí não dá certo...

A "solução"

Eu criava as imagens independentemente, cada uma com seu vetor de pixels. Depois de processada, eu fazia uma chamada a memcpy para copiar os pixels. Uma tristeza considerando o contexto.

Compartilhando pixels: a solução de fato

Depois de quase desistir da idéia, eu descobri (dia desses...) o problema. E logo cheguei a uma solução elegante (e óbvia).

Basta criar primeiro a FXImage, que vai apenas "emprestar" os pixels para a IplImage. Assim:


FXImage *fxImage = new FXImage(getApp(), 0,
    IMAGE_KEEP|IMAGE_OWNED|IMAGE_SHMP|IMAGE_SHMI,
    200, 200);


Percebam que o segundo parâmetro, que seria o vetor de pixels, é nulo. Como os pixels são de responsabilidade da FXImage, ela mesma os cria (isso é informado pela opção IMAGE_OWNED).

É necessário também manter os pixels no lado do cliente (opção IMAGE_KEEP). Sem ela, os pixels são liberados depois da imagem criada, impossibilitando o compartilhamento.

Pois bem, agora vamos "emprestar" os pixels da FXImage para a IplImage:


IplImage *iplImage = cvCreateImageHeader(cvSize(200, 200),
    IPL_DEPTH_8U, 4);
  cvSetData(iplImage, fxImage->getData(), CV_AUTOSTEP);


cvCreateImageHeader() aloca todas as informações de uma imagem, com exceção do vetor de pixels. Com isso, a IplImage, que é temporária, apenas pega emprestado os pixels para trabalhar sobre eles. Ao terminar o processamento, podemos liberar o cabeçalho:


cvReleaseImageHeader(&iplImage);


Uma grande vantagem dessa abordagem é que não precisa nem converter a imagem, conforme eu havia feito anteriormente. A imagem já é criada no formato da FXImage.

Conclusão

Conforme havia dito, é uma coisa tão simples e óbvia que fica invisível.

Espero que essa informação seja útil para quem for usar essas duas bibliotecas juntas.

Abraço e até a próxima.

quarta-feira, 23 de dezembro de 2009

Submenus

Dando continuidade à nossa série sobre Menus, falarei agora sobre como adicionar submenus. Vamos lá.

Submenus são menus como quaisquer outros

Isso mesmo. O processo de criar um submenu é praticamente o mesmo de criar um menu "normal".

Para exemplificar o processo, vamos criar um menu "Exibir", com um submenu "Zoom". Neste caso, temos dois painéis, que serão declarados da seguinte maneira:

FXMenuPane *_viewMenu;
FXMenuPane *_zoomMenu;

Lembrando que eles devem estar no escopo de classe, pois precisam ser liberados manualmente.

Percebam que o submenu "Zoom" nada mais é que um FXMenuPanel como todos os outros que já vimos até aqui. Agora vamos criá-los, novamente recuando o código para indicar a hierarquia.

_viewMenu = new FXMenuPane(this);
  _zoomMenu = new FXMenuPane(this);
    new FXMenuCommand(_zoomMenu, "&Aumentar\tCtrl++");
    new FXMenuCommand(_zoomMenu, "&Normal\tCtrl+0");
    new FXMenuCommand(_zoomMenu, "&Diminuir\tCtrl+-");

    new FXMenuSeparator(_zoomMenu);

    new FXMenuCommand(_zoomMenu, "A&justar\tHome");
  new FXMenuCascade(_viewMenu, "&Zoom", 0, _zoomMenu);
new FXMenuTitle(menuBar, "&Exibir", 0, _viewMenu);

Explicando...

Na primeira linha, aloco o menu "Exibir".

Na segunda, aloco o (sub)menu "Zoom". Chamando atenção para o recuo, indicando que _zoomMenu será posicionado "dentro" de _viewMenu;

Nas linhas seguintes, preencho com alguns comandos e um separador. Nada além do trivial com recuo.

Em seguida, criamos a entrada no menu "Exibir" que vai expandir o menu "Zoom". Repetindo:

new FXMenuCascade(_viewMenu, "&Zoom", 0, _zoomMenu);

Os parâmetros são os mesmos de FXMenuTitle (comparem com a última linha):
  • O FXComposite onde ficará localizado o submenu. Neste caso, adicionamos ao painel "Exibir"
  • O título que vai aparecer no painel, com direito a mnemônico
  • Ícone
  • O painel que será exibido ao mover o mouse sobre o título.

Liberando

Como todo bom painel de menu, deve ser liberado manualmente:


delete _viewMenu;
delete _zoomMenu;

Só uma observação: já tratei esse assunto aqui, então é só um lembrete. Quando eu falo em deletar manualmente, é porque estou usando o FOX 1.6. Na versão atual de desenvolvimento (1.7), existe uma classe chamada FXAutoPtr, que é um "ponteiro inteligente". Resumindo a história, ele guarda um ponteiro e automaticamente o libera quando sai de escopo. Veja a descrição aqui (na verdade, uma dúvida enviada à lista de discussão do FOX que eu traduzi e postei aqui).

Resultado

Eis o resultado do nosso submenu:


segunda-feira, 14 de dezembro de 2009

Menus

Neste tópico, ensinarei como adicionar uma barra de menus (com menus, obviamente) ao programa. Neste momento, colocarei menus simples, apenas com comandos simples. Há outros componentes que podem ser adicionados aos menus, mas serão tratados em tópicos futuros.

Vamos lá.


Barra de menus

Tudo começa com a barra de menus. Naturalmente, é lá que ficam os menus. E, naturalmente, fica no topo da janela:



FXMenuBar *menuBar = new FXMenuBar(this, LAYOUT_FILL_X|FRAME_RAISED);


Percebam que ela pode ser criada localmente, o próprio FOX se encarrega de liberá-la depois. Além disso, lembrem-se que a localização padrão é no topo da janela, portanto não precisei passar LAYOUT_SIDE_TOP.

Isso cria o espaço ideal para os menus. Aqui, criarei apenas dois, "Arquivo" e "Editar", apenas para exemplificar. Não é um processo complicado. Pelo contrário, é bem simples.


Adicionando menus

Os menus precisam ser declarados com escopo de classe. Então, na declaração da classe, temos:



protected:
  FXMenuPane *_fileMenu;
  FXMenuPane *_editMenu;


Agora, vamos criar e preencher os menus. Neste primeiro exemplo, do menu "Arquivo", vou colocar e explicar linha por linha. No segundo, o menu "Editar", vou colocar tudo de uma vez só para explicar a forma como eu gosto de criar os menus.

Menu Arquivo




_fileMenu = new FXMenuPane(this);


Cria o painel onde ficarão os itens do menu. Os dois parâmetros do construtor são a janela pai e as opções, que não é necessário informar.




new FXMenuCommand(_fileMenu, "&Carregar imagem...", 0, 0, 0);


Cria um comando de menu. Os principais parâmetros são:

  • Menu onde vai aparecer. Na verdade, pode ficar em um FXComposite qualquer (e, de fato, eu coloquei um sem querer na própria janela, ficou algo bizarro). Como estamos criando menus, é bom que fique em um FXMenuPanel. Neste caso, no menu "Arquivo".
  • Texto do comando. O caractere & indica que a próxima letra ficará sublinhada, para acesso através de Alt+. Neste caso, Alt+C acionará esse comando. É meio Uíndous, mas muito melhor que em Java (um setMnemonic() horroroso).
  • Ícone. FXMenuCommands podem ter um ícone associado, indique aqui. Já falei sobre ícones aqui.
  • Alvo do evento.
  • ID da mensagem. A série sobre Tratamento de Eventos explica essa parte referente a esses dois últimos parâmetros.

O último parâmetro são as opções, novamente desnecessário informar.

E caso não tenha ficado claro pelo nome da classe: FXMenuCommands lançam eventos do tipo SEL_COMMAND. Isso significa que no mapa de mensagens do tratador (seja a própria janela ou seja lá quem for) vai aparecer algo como:



FXMAPFUNC(SEL_COMMAND,
  FoxTutorialMainWindow::ID_LOAD_IMAGE,
  FoxTutorialMainWindow::onCmdLoadImage),



Continuando:



new FXMenuSeparator(_fileMenu);


Apenas um separador. Desenha uma linha para separar os comandos em seções.




new FXMenuCommand(_fileMenu, "&Sair\tCtrl+Q\tEncerrar o aplicativo",
        0, getApp(), FXApp::ID_QUIT);


Mais um comando, para fechar o aplicativo. Desta vez fiz com que esse comando lançasse um evento para exemplificar. Já falei sobre como fechar o aplicativo através de um comando aqui.

A única observação adicional é quanto ao texto do comando. Assim como o texto dos botões, este também é dividido em três seções, separadas pelo caractere de tabulação:

  • A primeira é o texto que será exibido. Natural e praticamente obrigatório.
  • A segunda são as teclas de atalho (aceleradores). Neste caso, coloquei Ctrl+Q para fechar o aplicativo. Opcional.
  • A terceira é o texto que aparecerá na barra de status. Falaremos dela no futuro. Opcional.

Bom, isso preenche o nosso menu "Arquivo". Vamos, agora, adicioná-lo à barra de menus (ou vocês achavam que isso já tinha sido feito?):



new FXMenuTitle(menuBar, "&Arquivo", 0, _fileMenu);


Aqui informamos:

  • A barra de menus onde vai ficar. Novamente, pode ficar em um FXComposite qualquer. Mas não há a menor necessidade de uma bizarrice dessas.
  • O título que vai aparecer. Não preciso mais dizer para que serve o &, preciso?
  • Ícone. Eu, particularmente, nunca vi em nenhum programa (escrito em FOX ou não) um menu com ícone. Mas o FOX Toolkit te dá essa liberdade.
  • O painel que será exibido ao acionar o menu.

Nosso menu Arquivo está pronto. Observemos, agora, o menu Editar.


Menu Editar




_editMenu = new FXMenuPane(this);
  new FXMenuCommand(_editMenu, "&Preferências...", 0, 0, 0);
new FXMenuTitle(menuBar, "&Editar", 0, _editMenu);


Bom, esse aqui é só para mostrar a maneira como eu normalmente crio menus (e, de fato, qualquer objeto composto por uma hierarquia).

Na primeira linha eu sigo o recuo normal do código. Esta é, digamos, a linha de criação (cunhei esse termo agora...).

Em seguida, vêm as linhas de preenchimento (de novo...). Nelas, coloco os objetos que vão "dentro" do objeto principal, com um recuo a mais exatamente para deixar claro essa hierarquia.

No final, volto ao recuo da linha de criação. É a linha de realização ( >D ), pois nela dou um sentido ao meu objeto (afinal, não faz sentido criar um painel sem acesso a ele).

Desenvolvi esse estilo quando comecei a trabalhar com o OpenSceneGraph (não confundam com OpenSG) e precisava definir as características de um modelo, ficava mais ou menos assim:
osg::ref_ptr geode = new osg::Geode();
  osg::Geometry *geometry = new osg::Geometry();
    osg::Vec3Array *vertexArray = new osg::Vec3Array();
      /* Preenche a lista de vértices */
    geometry->setVertexArray(vertexArray);

    osg::DrawElementsUInt *primitiveSet = new osg::DrawElementsUInt(GL_POINTS);
      /* Adiciona as primitivas */
    geometry->addPrimitiveSet(primitiveSet);
  geode->addDrawable(geometry);


Recuar os objetos mais internos me ajudou a entender melhor a hierarquia dos objetos do OpenSceneGraph e acabei aplicando essa técnica ao programar com o FOX Toolkit. Não vou explicar porque está fora do escopo do tópico (e do blog como um todo...).

Bom, é apenas uma sugestão.


Liberando os menus

FXMenuPanels são objetos compartilháveis, assim como ícones. Isso serve, por exemplo, para exibir o mesmo painel pelo menu ou clicando com o botão direito sobre algum objeto.

Sendo assim, devem ser liberados "na mãozona":



delete _fileMenu;
delete _editMenu;



Resultado

Depois disso tudo, o menu Arquivo fica conforme a imagem a seguir. Em destaque, a barra de status mostrando o texto do comando de sair.





Considerações finais

Notem o texto dos comandos de "Carregar imagem..." (menu Arquivo) e "Preferências..." (menu Editar). Eles terminam em reticências.

Terminar um texto de exibição (tanto de comandos de menu como também de botões) com reticências informa imediatamente ao usuário que uma nova janela se abrirá, onde ele terá que executar mais ações.


Outra observação importante é onde posicionar o código para criação dos menus. No início, eu colocava tudo no construtor. As coisas foram aumentando de tamanho, e resolvi apenas criar a barra no construtor e chamar uma função, populateMenu(), para fazer essa tarefa.

Agora, as coisas no meu projeto estão ficando grandes demais, e estou pensando seriamente em criar uma nova classe só para isso. Acontece que, na pressa de obter resultados, a gente acaba tocando tudo no mesmo lugar. Nessa brincadeira até o padrão MVC vai para o espaço. Por favor, não façam isso.


Bom, esse é o que tem de mais básico em termos de menus. Ainda vou discutir sobre submenus e outros elementos que podem aparecer em um FXMenuPanel. Um abraço e até lá.