Windows Image Acquisition

Windows Image Acquisition (WIA) é uma API da Microsoft para manipulação de dispositivos como câmeras e scanners.
Tive o interesse em pesquisar esta API para a criação de um windows service em .NET que fique tirando fotos de tempos em tempos de quem está usando o computador e mandando para um site ou e-mail. Isso é bem útil no caso de roubo de notebooks…, pelo menos se pode ter a foto do maldito ladrão!
O que vou colocar aqui é um exemplo de código que faz a captura de uma imagem. Uma coisa bem simples, mas que deu um pouco de trabalho até entender legal como é que funciona essa API.
Antes de começar, é necessário dizer que esta API não funciona com todos os dispositivos. Bom, basicamente e teoricamente irá funcionar com dispositivos que estejam listados em Painel de Controle –> Scanners e câmeras. Por causa disso, esse exemplo que vou passar não funcionou no meu desktop, que possui uma webcam da marca Bright. No entanto, funciona no meu netbook, que possui uma webcam embutida (Acer).
Bom, a primeira coisa a se fazer é baixar o SDK desta biblioteca do site da Microsoft. Note que isso só é necessário com o Windows XP, pois o Vista já vem com ela instalada. As instruções de instalação vêm junto do pacote (não se esqueça de fazer isso senão não irá funcionar!).
Vamos criar uma tela simples Windows Form que apresente uma foto recém tirada. Um detalhe que esta API é COM, não é nativa .NET. Neste caso, precisaremos trabalhar com interop. Nada complicado, bastando fazer uma referência ao Microsoft Windows Image Acquisition v2.0.
image
Todo o código abaixo foi colocado em um evento de clique de botão (é uma aplicação para testes apenas, então não vou me preocupar muito com boas práticas de programação). Outro detalhe é que importei os namespaces WIA, System.IO e System.Diagnostics neste exemplo.
A primeira coisa a ser feita é obter o ID do dispositivo que iremos utilizar. Como falei antes, está é uma API para uso de vários dispositivos, e precisamos escolher apenas um. Quando usamos a classe WIA.CommonDialog nos é exibida uma tela onde escolhemos qual dispositivo usar (essa tela é do Windows, não temos o controle dela). Um detalhe importante é que eu não consegui fazer a instância dessa classe através simplesmente de
CommonDialogClass class1 = new CommonDialogClass();


, pois quando fazia isso o Visual Studio me retornava o erro “Interop type 'WIA.CommonDialogClass' cannot be embedded. Use the applicable interface instead.”. Resolvi isso através de late binding.

O retorno desta seleção é um objeto do tipo Device, de onde podemos obter seu identificador único (ID).

// Escolhe o dispositivo que será usado para tirar a foto
string deviceId = string.Empty;
ICommonDialog class1 = (ICommonDialog)Activator.CreateInstance(Type.GetTypeFromProgID("WIA.CommonDialog"));
Device d = class1.ShowSelectDevice(WiaDeviceType.UnspecifiedDeviceType, true, false);
if (d != null)
deviceId = d.DeviceID.ToString();
MessageBox.Show(string.Format("O identificador do dispositivo é {0}", deviceId));


imageimage


Com o ID em mãos, podemos conectar no dispositivo que será usado. Um detalhe importante é que eu não precisaria fazer isso, pois já obtive uma referência a um Device quando selecionei através da tela anterior. Mas é bom fazer isso já que como meu código vai rodar num windows service, não é interessante que fique sendo exibido sempre qual dispositivo utilizar.


// Faz conexão com o dispositivo
IDeviceManager manager = (IDeviceManager)Activator.CreateInstance(Type.GetTypeFromProgID("WIA.DeviceManager"));
Device device = null;
foreach (DeviceInfo info in manager.DeviceInfos)
{
if (info.DeviceID == deviceId)
{
device = info.Connect();
break;
}
}


Agora vem a parte principal, que é tirar a foto. Isso é feito através do envio de uma execução de comando através da instância da classe Device. O guid {AF933CAC-ACAD-11D2-A093-00C04F72DC3C} é padrão para comando de “tirar foto”, e existe outros mais. Outra coisa que notei neste código é que ele demora bastante para rodar, uns 7 segundo até a foto ser tirada. Por isso coloque um cronômetro (StopWatch) no código para saber sempre quanto tempo a execução levou. Não sei como resolver isso, se é que dá para resolver, mas para o tipo de serviço que eu quero fazer, não vejo problemas por enquanto…


// Envia o comando para a câmera tirar uma foto
Stopwatch watch = new Stopwatch();
watch.Start();
Item item = device.ExecuteCommand("{AF933CAC-ACAD-11D2-A093-00C04F72DC3C}"); // CommandID.wiaCommandTakePicture
watch.Stop();
MessageBox.Show(string.Format("Foram necessários {0} milissegundos para tirar a foto.", watch.ElapsedMilliseconds.ToString()));


imageimage


Com o retorno da execução da foto, recebemos como retorno uma instância da classe Item, que é o item resultante do processo que foi feito. No nosso caso, uma foto. Com este objeto, podemos transferir seu conteúdo para uma instãncia da classe WIA.FileImage. E com este objeto, podemos chamar o método SaveFile para armazenarmos em disco. Isso eu faço para poder exibí-la em um controle do tipo PictureBox.


// Salva a foto temporáriamente para exibição
foreach (string format in item.Formats)
{
if (format.ToUpper() == "{B96B3CAE-0728-11D3-9D7B-0000F81EF32E}")   // ImageFormat.Jpeg.Guid
{
WIA.ImageFile imagefile = item.Transfer(format) as WIA.ImageFile;
string filename = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString() + ".jpg");
if (!string.IsNullOrEmpty(filename))
{
imagefile.SaveFile(filename);
try
{
this.pictureBox1.Load(filename);
}
finally
{
File.Delete(filename);
}
}
}
break;
}


Bom, com isso terminamos, certo? Não, ainda não…, a foto que tiramos ainda ficou armazenada na cãmera, precisamos removê-la para não ficar ocupando memória desnecessária. Para fazer isso, basta percorrer os itens do Device e remover o que tiver o mesmo ID da foto que foi tirada (Obs. Note que essa coleção de itens não começa com elemento na posição zero, mas sim na 1).


// Remove a foto que ficou armazenada
if (device.Items.Count > 0)
{
int indice = 1;
foreach (IItem i in device.Items)
{
if (i.ItemID == item.ItemID)
break;
indice++;
}
device.Items.Remove(indice);
}

Agora sim terminamos este exemplo. Agora vou começar a fazer o windows service que espero nunca precisar ter que realmente usar, rs.

Boas fotos!

[]’s

Comentários

Postagens mais visitadas deste blog

Trocando configurações padrão do Live TIM

Uma proposta de Clean Architecure com Modelo de Atores

Testes automatizados em sistemas autenticados com certificados digitais, usando Selenium e PhantomJS