segunda-feira, 31 de outubro de 2016

Porta-Retratos Digital Caseiro II (O Bug)

O porta-retratos digital funcionou bem por cerca de 8 horas e então parou com um erro de malloc(). Após algumas simulações e pesquisa dos bugs do Tk, descobri que o defeito está na classe Tk::Photo. Quando a imagem é carregada pela memória (com -data), os bits ficam guardados para sempre. Quando são lidos do disco, os dados não persistem além da próxima leitura.

Então, reescrevi alguns trechos e o código ficou um pouco menor. A função scale() não retorna mais os dados; agora ela escreve um arquivo.

sub scale {
  my ($width, $height, $image)=@_;
  my $scaled=$image->scale(xpixels=>$width,ypixels=>$height,type=>'min');
  $scaled->write(file=>'tmp.jpg', type=>'jpeg');  
}

Por sua vez, a função next_photo() lê este arquivo:

sub next_photo {
  my $index=int(rand($#file_list));
  my $filename=$file_list[$index];
  
  $image->open(file=>$filename);
  scale($photo_width, $photo_height, $image);
  $photo->configure(-file=>'tmp.jpg');
  
  $filename=$file_list[($index+1)%($#file_list+1)];
  
  $image->open(file=>$filename);
  scale($thumb_width, $thumb_height, $image);
  $thumb->configure(-file=>'tmp.jpg');
  
  $main->update();
}

Evidentemente, não quero escrever este arquivo no cartão de memória para não apressar o seu fim. Então, a solução é criar um disco em RAM e escrever lá. Ele não precisa ser muito grande, os JPEGs temporários não passam de 100KB (por isso, o script original demorou a ocupar toda a memória).

quinta-feira, 27 de outubro de 2016

Porta-Retratos Digital Caseiro

Os preços dos porta-retratos digitais estão começando a ficar interessantes, mas construir o próprio é mais divertido.

Usei um Raspberry Pi A+ (256MB de RAM e um ARM11 de 700MHz) com a última versão do Raspbian Jessie. Eu queria mesmo era usar um Raspberry Pi Zero (que custa apenas US$5), mas são muito procurados e ainda não apareceram na minha loja preferida.

Comecei muito otimista (como sempre) e tentei montar uma solução com HTML, Javascript, jQuery, transições, etc. Ela resultou ser muito pesada. Então, tentei em Tcl/Tk, mas manipular os tamanhos das imagens resultou ser muito trabalhoso (possível, mas trabalhoso).

Então, só me restou usar Perl. Foi preciso instalar dois pacotes: Tk e Imager.


sudo apt-get install perl-tk
sudo apt-get install libjpeg-devel
sudo cpanm Imager
sudo cpanm Imager::File::JPEG
sudo cpanm Tk::HideCursor 


A interface é muito simples. Há um quadro grande com uma foto qualquer e uma foto pequena com a foto seguinte. A foto grande é escolhida aleatoriamente, mas a pequena é sempre a que segue (para haver uma relação entre elas). Além disso, exibo as horas e a data.

O script recebe um único parâmetro que indica o diretório onde as fotos são armazenadas. Ctrl+C suavemente encerra o programa.

Futuramente, espero exibir algo mais interessante no quadro das horas (a previsão do tempo ou as notícias).

#!/usr/bin/perl
use Tk;
use Tk::JPEG;
use Tk::Photo;
use Tk::HideCursor;
use Imager;
use File::Find;
use MIME::Base64;
use POSIX 'strftime';
use strict;

my @file_list;

find(\&wanted, $ARGV[0]);

sub wanted {
    return unless -f;
    return unless /\.jpe?g$/i;
    push @file_list, $File::Find::name;
}

my $main = MainWindow->new (
  -title => 'PiFrame',
  -background => 'black'
);

my $w=$main->screenwidth();
my $h=$main->screenheight();
$main->overrideredirect(1);
$main->MoveToplevelWindow(0,0);
$main->geometry(join('x',$w,$h));
$main->hideCursor();

my $photo_width=int($w*3/4);
my $photo_height=$h;
my $thumb_width=$w-$photo_width;
my $thumb_height=int($h/3);

my $canvas=$main->Canvas(
  -width=>$photo_width,
  -height=>$h,
  -background=>'black',
  -highlightthickness => 0);
$canvas->pack(-side=>'left');

my $small_canvas=$main->Canvas(
  -width=>$thumb_width,
  -height=>$thumb_height,
  -background=>'black',
  -highlightthickness => 0);
$small_canvas->pack(-side=>'bottom');

my $time;

my $clock=$main->Label(
  -textvariable=>\$time,
  -width=>100,
  -height=>100,
  -background=>'black',
  -foreground=>'white',
  -highlightthickness => 0,
  -font=>'courier 40 bold');
$clock->pack(-side=>'top');

my $photo=$canvas->Photo();
$canvas->createImage(0,0,-image=>$photo,-anchor=>'nw');
my $thumb=$small_canvas->Photo();
$small_canvas->createImage(0,0,-image=>$thumb,-anchor=>'nw');

sub scale {
  my ($width, $height, $image)=@_;
  my $scaled=$image->scale(xpixels=>$width,ypixels=>$height,type=>'min');
  my $data;
  $scaled->write(data=>\$data, type=>'jpeg');
  return encode_base64($data)
}

my $image=Imager->new();

sub next_photo {
  my $index=int(rand($#file_list));
  my $filename=$file_list[$index];
  
  $image->open(file=>$filename);
  $photo->configure(-data=>scale($photo_width, $photo_height, $image));
  
  $filename=$file_list[($index+1)%($#file_list+1)];
  $image->open(file=>$filename);
  $thumb->configure(-data=>scale($thumb_width, $thumb_height, $image));
  
  $main->update();
}

$canvas->repeat(60000, sub {eval {next_photo()}});
$canvas->repeat(1000, sub {$time=strftime("%H:%M\n%A\n%d/%m", localtime)});

next_photo();

MainLoop;

O sistema operacional que usei para este projeto é o Raspbian com Pixel. As fotos estão armazenadas na pasta /data01/fotos. Então, adicionei a seguinte linha ao arquivo  /home/pi/.config/lxsession/LXDE-pi/autostart para que o sistema inicie automaticamente:

@perl /home/pi/piframe.pl /data01/fotos/

Para evitar que o gerenciamento de energia apague a tela por falta de movimentação, é preciso editar o arquivo /etc/lightdm/lightdm.conf. A linha original e a nova são, respectivamente:

#xserver-command=X
xserver-command=X -s 0 dpms


O gasto de memória oscila entre 25MB e 50MB, o que cabe confortavelmente no espaço que o A+ oferece. Um Pi 3 talvez possa executar isso ao mesmo tempo que ofereça o serviço do Amazon Echo.