Início > Android, Tecnologia > Virtual Nails – Realidade aumentada no android

Virtual Nails – Realidade aumentada no android

Mês passado fiz um curso de programação para Android com o Fernando Anselmo. Excelente curso, mas o mais legal foi no fim, fazer uma espécie de projeto final, sem a burocracia de uma monografia. Essencialmente “mão-na-massa”.

Meu projeto é (ainda está em desenvolvimento) um aplicativo que utiliza realidade aumentada para que as mulheres possam ver como fica a cor de um esmalte em sua mão antes de aplicá-lo.

O escopo desse artigo é tão somente a implementação desse projeto na plataforma android. Os conceitos e áreas de aplicação da realidade aumentada estão muito bem explicados aqui e aqui.

A proposta do aplicativo é você poder, através da câmera do seu celular, ver em tempo real usando o preview da câmera ou mesmo tirar uma foto da sua mão (sugestão dada na apresentação do projeto no curso), posicionar melhor a imagem de cada unha em cima de cada dedo e escolher a cor do esmalte.

O principal problema foi conseguir a rotação correta da câmera no meu celular (um motorola Atrix). A imagem da câmera aparecia girada em 90 graus (o telefone estava apontado para cima e a imagem da mão aparecia deitada), o que é, aparentemente, um problema que acontece em vários modelos de celular, como podemos ver aqui.
Eu percebi que o aplicativo da máquina fotográfica que vem no celular não apresentava esse problema então baixei o código fonte dele para ver como ele trabalha a rotação da imagem. Todo o código fonte, não só do android, mas também dos pacotes de aplicativos de cada uma das versões pode ser baixado de um repositório GIT aqui. No windows eu uso o mysysgit para fazer o clone do repositório.
Descobri então, que na versão 2.2 ou API level 8 foi introduzido o método setDisplayOrientation() na classe Camera que recebe como parâmetro o número de graus a rodar a imagem. Esse método simplificou o processo, mas ainda estou trabalhando na correção da rotação da imagem para as versões anteriores do android.

 

Vamos ao código.
Não vou colocar o código completo aqui. Apenas algumas partes que merecem algum tipo de observação.
Criei um repositório no GitHub para esse projeto: acesse aqui para baixar o projeto completo.

O projeto é composto pelo arquivo XML de layout, o arquivo XML de strings, algumas imagens e mais três classes, sendo elas MainActivity.java que controla a aplicação, CameraOverlay.java, que cria a surface view e controla a câmera e TouchScreenView.java que cria as imagens e permite arrastar e posicionar cada uma delas.

O arquivo de layout main.xml:

<FrameLayout android:id="@+id/cameraLayout"
	android:layout_weight="1"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">
</FrameLayout>

No arquivo de layout, a única coisa diferente é o uso do FrameLayout para “empilhar” os objetos na tela. A forma como o FrameLayout gera a tela permite que as imagens das unhas fiquem por cima do surfaceView da Câmera.

A classe MainActivity.java:

private void montaLayout() {
// TODO Auto-generated method stub
mCameraOverlay = new CameraOverlay(this);
fl = (FrameLayout) findViewById(R.id.cameraLayout);
fl.addView(mCameraOverlay);
Button btnFoto = (Button) findViewById(R.id.btnFoto);
btnFoto.setOnClickListener(new OnClickListener() {
	public void onClick(View v) {
		mCameraOverlay.mCamera.takePicture(shutterCallback, rawCallback, null);
	}
});

Button btnPreview = (Button) findViewById(R.id.btnPreview);
btnPreview.setOnClickListener(new OnClickListener() {
	public void onClick(View v) {
		mCameraOverlay.restartPreview();
	}
});

mTs = new TouchScreenView(this);
fl.addView(mTs);
}

ShutterCallback shutterCallback = new ShutterCallback() {
	public void onShutter() {
		Log.d(TAG, "onShutter callback");
	}
};

/** Callback chamado quando tira a foto */
PictureCallback rawCallback = new PictureCallback() {
	public void onPictureTaken(byte[] data, Camera camera) {
		Log.d(TAG, "onPictureTaken - raw");
	}
};

A activity simplesmente faz o layout do main.xml, e chama o método montaLayout(), que inicialmente instancia a classe CameraOverlay (que estende a SurfaceView) e adiciona seu conteúdo ao FrameLayout. Em seguida define os listeners dos botões de foto (que chama o método takePicture da classe Camera passando os callbacks como parâmetro) e limpar a tela para então instanciar a TouchScreenView (que deriva de View) e adicionar ao FrameLayout, por cima da SurfaceView da CameraOverlay. Por último, cria  os callbacks para os botões de tirar foto (onde poderíamos salvar a foto, se fosse o caso) e limpar tela.

A classe CameraOverlay:

class CameraOverlay extends SurfaceView implements SurfaceHolder.Callback {
	private static final String TAG = "VirtualNails";

	SurfaceHolder mHolder;
	public Camera mCamera;
	private Parameters mParameters;
CameraOverlay(Context context) {
	super(context);
	// Cria um Surfaceholder.Callback para
	// notificacao da criação, modificação e destruição da surface
	mHolder = getHolder();
	mHolder.addCallback(this);
	mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}

public void surfaceCreated(SurfaceHolder holder) {
	// Surface criada. pega a camera e chama o preview
	mCamera = Camera.open();
	setPreviewDisplay(holder);
}
public void surfaceDestroyed(SurfaceHolder holder) {
	// Surface vai ser encerrada, entao paramos o preview da camera
	// TODO: CORRIGIR: o surface destroyed eh chamado antes do surfaceChanged quando muda a rotação da tela,
      // causando um blink na tela
	Log.v(TAG, "surfaceDestroyed");
	stopPreview();
	closeCamera();
	holder = null;
}

public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
	// usa o setDisplayOrientation para resolver o bug dos telefones motorola
	// que a imagem aparece virada em 90 graus -- so disponivel a partir da versao 2.2

	//TODO: Resolver esse problema para versões anteriores do android

	Log.v(TAG, "surfaceChanged");
	mParameters = mCamera.getParameters();

	Configuration config = getResources().getConfiguration();
    if (config.orientation == Configuration.ORIENTATION_PORTRAIT)
    {
    	  mCamera.setDisplayOrientation(90); //disponivel apenas na versao 2.2 ou >
    	  mParameters.setPreviewSize(h, w); //TODO: testar em um telefone que não seja motorola para ver se distorce
    }
    else
    	mParameters.setPreviewSize(w, h);

	mCamera.setParameters(mParameters);

	try {
		startPreview();
	}
	catch (Exception e) {
		// TODO: handle exception
	}
}

A classe CameraOverlay extemde a SurfaceView e implementa a SurfaceHolder.Callback.
A SurfaceView fica por trás da janela onde é instalada, e “abre um buraco” na view, permitindo mostrar a superfície. A interface SurfaceHolder permite o acesso à superfície debaixo dela, e precisa necessariamente ser um callback, para garantir que a tela está pronta antes de, por exemplo, inicializarmos a câmera.
No surfaceCreated() é onde criamos o objeto Camera, à partir do método open() da classe Camera e no surfaceChanged() fazemos as configurações dos parâmetros iniciais da câmera e adicionamos os parâmetros que iremos utilizar. Eu utilizei o setDisplayOrientation() para corrigir um problema do meu telefone como expliquei acima (ainda preciso testar em outros telefones). Por último e iniciamos o preview da imagem.
É importante liberar a câmera ao encerrar o aplicativo pois não é um recurso compartilhado.

Por último temos a classe TouchScreenView.java:

@Override
public boolean onTouchEvent(MotionEvent event) {
	// TODO Auto-generated method stub

	float x = event.getX();
	float y = event.getY();

	Log.i(TAG, "onTouchEvent X/Y: " + x + "/" + y);

	switch (event.getAction()){
		case MotionEvent.ACTION_DOWN:
			selecionadoPolegar = imgPolegar.copyBounds().contains((int) x, (int) y);
			selecionadoIndicador = imgIndicador.copyBounds().contains((int) x, (int) y);
			selecionadoMedio = imgMedio.copyBounds().contains((int) x, (int) y);
			selecionadoAnular = imgAnular.copyBounds().contains((int) x, (int) y);
			selecionadoMindinho = imgMindinho.copyBounds().contains((int) x, (int) y);
			break;
		case MotionEvent.ACTION_MOVE:
			if (selecionadoPolegar){
				this.xPolegar = (int)x - (larguraImgPolegar/2);
				this.yPolegar = (int)y - (alturaImgPolegar/2);
			}
			else if (selecionadoIndicador){
				this.xIndicador = (int)x - (larguraImgIndicador/2);
				this.yIndicador = (int)y - (alturaImgIndicador/2);
			}
			else if (selecionadoMedio){
				this.xMedio = (int)x - (larguraImgMedio/2);
			this.yMedio = (int)y - (alturaImgMedio/2);
			}
			else if (selecionadoAnular){
				this.xAnular = (int)x - (larguraImgAnular/2);
				this.yAnular = (int)y - (alturaImgAnular/2);
			}
			else if (selecionadoMindinho){
				this.xMindinho = (int)x - (larguraImgMindinho/2);
				this.yMindinho = (int)y - (alturaImgMindinho/2);
			}
			break;
		case MotionEvent.ACTION_UP:
			selecionadoPolegar =
			selecionadoIndicador =
			selecionadoMedio =
			selecionadoAnular =
			selecionadoMindinho = false;
			break;
	}
	invalidate();

	return true;

}

Nela carrego as imagens fixas. Ainda estou trabalhando nessa mudança, para poder criar o efeito do brilho do esmalte desenhando diretamente no canvas, e passar somente o valor hexadecimal da cor quando esta for modificada.

A classe faz override no método onDraw(), o que já permite desenhar cada imagem no canvas com seu respectivo tamanho e no onTouchEvent identificamos quem foi selecionado à partir do copyBounds().contains() passando a área do retângulo onde o evento está ocorrendo e em seguida setamos as coordenadas novas da imagem e chamamos o método invalidate() que faz a tela ser redesenhada, com as coordenadas novas.

Advertisements
Categorias:Android, Tecnologia
  1. Diego
    21 de Janeiro de 2013 às 10:58

    Excelente adorei o post.
    Gostaria de sua ajuda já que você é o autor do projeto.
    Estou fazendo uma camera comum simplemente para tirar foto.
    Porem nao estou conseguindo implementar o metodo para salvar a imagem com seu projeto.
    Como eu poderia fazer isso?

  1. No trackbacks yet.

Deixe uma Resposta

Preencha os seus detalhes abaixo ou clique num ícone para iniciar sessão:

Logótipo da WordPress.com

Está a comentar usando a sua conta WordPress.com Terminar Sessão / Alterar )

Imagem do Twitter

Está a comentar usando a sua conta Twitter Terminar Sessão / Alterar )

Facebook photo

Está a comentar usando a sua conta Facebook Terminar Sessão / Alterar )

Google+ photo

Está a comentar usando a sua conta Google+ Terminar Sessão / Alterar )

Connecting to %s

%d bloggers like this: