#!/usr/bin/perl -w
use strict;
use OpenGL(':all');

# Images used for testing
my $src_image = 'test.png';
my $dst_image = 'test.jpg';
my $tga_image = 'test.tga';
my $width = 128;
my $height = 128;
my $deviation = 0.15;


# Init tests
my $t = new MyTests(26,'Testing OpenGL::Image');


# Get OpenGL version
my $pogl_ver = $OpenGL::VERSION;
my $has_pogl5503 = $pogl_ver ge '0.5503';
$t->status("Using OpenGL v$pogl_ver");
$t->status("Recommend OpenGL 0.55_03 or newer to use") if (!$has_pogl5503);


#1 Get module version
my $ogi_ver;
my $exec = qq
{
  use OpenGL\::Image;
  \$ogi_ver = \$OpenGL::Image::VERSION;
};
eval($exec);
$t->bail("OpenGL::Image failed to load: $@") if ($@ || !$ogi_ver);
$t->ok("OpenGL::Image module loaded: v$ogi_ver");


#2 Get ImageMagick version
my $im_ver = 0;
$exec = qq
{
  use Image\::Magick;
  \$im_ver = \$Image::Magick::VERSION;
};
eval($exec);
if ($@ || !$im_ver)
{
  $t->skip("Image::Magick module not installed: $@") 
}
elsif ($im_ver lt '6.3.5' )
{
  $t->skip("Image::Magick module installed: v$im_ver - recommend 6.3.5 or newer");
}
else
{
  $t->ok("Image::Magick module installed: v$im_ver");
}


#3 Enumerate installed engines
$t->status("Testing OpenGL::Image::GetEngines():");
my $engines = OpenGL::Image::GetEngines();
my @engines = keys(%$engines);
$t->bail("No imaging engines installed!") if (!@engines);
my $has_TGA = 0;
my $has_IM = 0;
my $has_IM635 = 0;
foreach my $engine (sort @engines)
{
  $t->status("  $engine: ".$engines->{$engine}->{version});
  if ($engine eq 'Targa')
  {
    $has_TGA = 1;
  }
  elsif ($engine eq 'Magick')
  {
    $has_IM = 1;
    $has_IM635 = $engines->{'Magick'}->{version} ge '6.3.5';
  }
}
$t->status('Targa is ' . ($has_TGA ? '' : 'NOT ') . "installed");
$t->status('Magick is ' . ($has_IM ? '' : 'NOT ') . "installed");
$t->ok("At least one imaging engine is installed");


#4 Test HasEngine()
my $engine_ver = OpenGL::Image::HasEngine($engines[0])->{version};
$t->bail("HasEngine('$engines[0]') failed to return a version") if (!$engine_ver);
$t->ok("HasEngine('$engines[0]') returned '$engine_ver'");


#5 Test OpenGL::Array
my $oga = OpenGL::Array->new_list(OpenGL::GL_UNSIGNED_BYTE,1,2,3,4);
$t->bail("Unable to instantiate OpenGL::Array") if (!$oga);
$t->bail("OpenGL::Array returned invalid element count") if (4 != $oga->elements());
$t->ok("Instantiated OpenGL::Array");


#6 Test image object instantiation
my $tga = new OpenGL::Image(width=>$width,height=>$height);
$t->bail("Unable to instantiate OpenGL::Image") if (!$tga);
$t->ok("Instantiated OpenGL::Image(width\=>$width,height\=>$height)");


#7 Test Get/Set Pixel
$tga->SetPixel(0,0, 0.1, 0.2, 0.3, 0.4);
my($v0,$v1,$v2,$v3) = $tga->GetPixel(0,0);

# Normalized values introduce rounding errors
my $dev = (abs($v0 - 0.1) + abs($v1 - 0.2) + abs($v2 - 0.3) + abs($v3 - 0.4)) / 4;
#$t->status("Get/SetPixel deviation: $dev");
if ($dev > $deviation)
{
  $t->bail("GetPixel failed to return values used with SetPixel");
}
$t->ok("GetPixel returns valid values used with SetPixel");


# set up test pixels
my @pixels = ();
my $x0 = 1.0 / $width;
my $y0 = 1.0 / $height;
my $r = 1.0;
my $g = 0.0;
for (my $y=0; $y<$height; $y++)
{
  $b = 1.0;
  $a = 0.0;
  for (my $x=0; $x<$width; $x++)
  {
    push(@pixels,[$x,$y, $r,$g,$b,$a]);
    $b -= $x0;
    $a += $x0;
  }
  $r -= $y0;
  $g += $y0;
}

foreach my $pixel (@pixels)
{
  $tga->SetPixel(@$pixel);
}


#8 Test image saving
$tga->Save($tga_image);
$t->bail("Save('$tga_image') failed to create $tga_image") if (!-e $tga_image);
$t->ok("Save('$tga_image') created image");


#9 Test image loading
my $sav = new OpenGL::Image(source=>$tga_image);
$t->bail("Unable to instantiate OpenGL::Image") if (!$sav);
$t->ok("Instantiated OpenGL::Image(source=>'$tga_image')");
unlink($tga_image);


#10 Test image parameters
my $params = $sav->Get();
$t->fail("Get() failed to return a parameter hashref") if (!$params);
my @params = keys(%$params);
$t->fail("Get() failed to return parameters") if (!scalar(@params));

$t->status("Testing object parameters:");
foreach my $key (sort @params)
{
  $t->status("  $key: ".$params->{$key});
}
$t->ok("Get() returned parameters");


#11 Test image size
my($w,$h,$p,$c,$s) = $sav->Get('width','height','pixels','components','size');
if ($w != $width || $h != $height)
{
  $t->fail("Get('width','height') returned invalid dimensions: $w x $h");
}
elsif($p != $w * $h)
{
  $t->fail("Get('pixels') failed to return $w x $h: $p");
}
else
{
  $t->ok("Get('width','height','pixels') returned: $w x $h = $p");
}


#12 Test pixel deviation
my $d = 0;
my $i = 0;
for (my $y=0; $y<$height; $y++)
{
  for (my $x=0; $x<$width; $x++)
  {
     my($r,$g,$b,$a) = $sav->GetPixel($x,$y);
     my $pixel = $pixels[$i++];
     $d += abs($r - (@$pixel)[2]);
     $d += abs($g - (@$pixel)[3]);
     $d += abs($b - (@$pixel)[4]);
     $d += abs($a - (@$pixel)[5]);
  }
}

$d /= ($i * 4);
if ($d > $deviation)
{
  $t->fail("Set/Get Pixels deviation out of range: $d")
}
elsif ($d)
{
  $t->ok("Set/Get Pixels within acceptable deviation: $d");
}
else
{
  $t->ok("Set/Get Pixels resulted in no deviation");
}


#13 Test IsPowerOf2()
if (!$sav->IsPowerOf2(256))
{
  $t->fail("IsPowerOf2(256) returned false");
}
elsif ($sav->IsPowerOf2(13))
{
  $t->fail("IsPowerOf2(13) returned true");
}
elsif (!$sav->IsPowerOf2())
{
  $t->fail("IsPowerOf2() returned false");
}
else
{
  $t->ok("IsPowerOf2() returned true");
}


#14 Test GetArray()
$oga = $sav->GetArray();
$t->bail("GetArray() failed to return an OpenGL::Array object") if (!$oga);
my $elements = $oga->elements();
if ($elements != $p * $c)
{
  $t->bail("GetArray() contains invalid number of elements: $elements");
}
$t->ok("GetArray() contains $elements elements");


#15 Test Ptr()
if ($oga->ptr() && $oga->ptr() != $sav->Ptr())
{
  $t->bail("Ptr() returned invalid pointer: ".$oga->ptr().', '.$sav->Ptr()."\n");
}
$t->ok("Ptr() returned a valid pointer");


#16 Test GetBlob()
my $blob = $sav->GetBlob();
$t->bail("GetBlob() failed to return blob\n") if (!$blob);
my $blob_len = length($blob);

if ('Targa' eq $sav->Get('engine'))
{
  if ($blob_len != $p * $c * $s)
  {
    $t->bail("GetBlob() returned invalid blob length: $blob_len\n");
  }
}
$t->ok("GetBlob() returned a blob of length: $blob_len");


# Skip the rest if no Magick engine or test image
my $has_image = -e $src_image;
if (!$has_IM || !$has_image)
{
  my $msg = $has_IM ? "Test image '$src_image' not found" : 'No ImageMagick';
  $t->done($msg);
  exit 0;
}


#17 Test Loading source image
my $src = new OpenGL::Image(engine=>'Magick',source=>$src_image);
$t->bail("Unable to instantiate OpenGL::Image(engine=>'Magick',source=>'$src_image')") if (!$src);
$t->ok("Instantiated OpenGL::Image(engine=>'Magick',source=>'$src_image')");


#18 Test source image size
my($ws,$hs,$ps,$cs,$ss) = $src->Get('width','height','pixels','components','size');
if ($ws != $width || $hs != $height)
{
  $t->fail("Get('width','height') returned invalid dimensions: $ws x $hs");
}
elsif($ps != $ws * $hs)
{
  $t->fail("Get('pixels') failed to return $ws x $hs: $ps");
}
else
{
  $t->ok("Get('width','height','pixels') returned: $ws x $hs = $ps");
}


#19 Test Save()
$src->Save($dst_image);
$t->bail("Save('$dst_image') failed to create file") if (!-e $dst_image);
$t->ok("Save('$dst_image') created image");


#20 Test Loading destination image
my $dst = new OpenGL::Image(engine=>'Magick',source=>$dst_image);
$t->bail("Unable to instantiate OpenGL::Image(engine=>'Magick',source=>'$dst_image')") if (!$dst);
$t->ok("Instantiated OpenGL::Image(engine=>'Magick',source=>'$dst_image')");
unlink($dst_image);


#21 Test destination image size
my($wd,$hd,$pd,$cd,$sd) = $dst->Get('width','height','pixels','components','size');
if ($wd != $ws || $hd != $hs)
{
  $t->fail("Get('width','height') returned invalid dimensions: $wd x $hd");
}
elsif($pd != $wd * $hd)
{
  $t->fail("Get('pixels') failed to return $wd x $hd: $pd");
}
else
{
  $t->ok("Get('width','height','pixels') returned: $wd x $hd = $pd");
}


#22 Test RGB deviation
$d = 0;
for (my $y=0; $y<$height; $y++)
{
  for (my $x=0; $x<$width; $x++)
  {
     my($rs,$gs,$bs,$as) = $src->GetPixel($x,$y);
     my($rd,$gd,$bd,$ad) = $dst->GetPixel($x,$y);
     $d += abs($rs-$rd) + abs($gs-$gd) + abs($bs-$bd);
  }
}

$d /= ($ps * 3);
if ($d > $deviation)
{
  $t->fail("Set/Get Pixels deviation out of range: $d")
}
elsif ($d)
{
  $t->ok("Set/Get Pixels within acceptable deviation: $d");
}
else
{
  $t->ok("Set/Get Pixels resulted in no deviation");
}


#23 Test Native()
$t->bail("Native() returned invalid PerlMagick object") if (!$src->Native());
my($x,$y) = $src->Native->Get('width','height');
if ($x != $w || $y != $h)
{
  $t->bail("Native->Get('width','height') returned invalid dimensions");
}
$t->ok("Native->Get('width','height') returned: $x x $y");


#24 Test GetBlob()
$blob = $src->GetBlob(magick=>'jpg');
$t->bail("GetBlob(type=>'jpg') failed to return a blob") if (!$blob);

my $im = Image::Magick->new(magick=>'jpg');
$im->BlobToImage($blob);
my($w0,$h0) = $im->Get('width','height');
if (!$w0 || !$h0)
{
  $t->bail("GetBlob(type=>'jpg') failed");
}
elsif ($w != $w0 || $h != $h0)
{
  $t->bail("GetBlob(type=>'jpg') returns invalid dimensions: $w0 x $h0");
}
$t->ok("GetBlob(type=>'jpg') returned a blob of length: ".length($blob));


#25 Test GetArray()
$oga = $src->GetArray();
$t->bail("GetArray() failed to return an OpenGL::Array object") if (!$oga);
$elements = $oga->elements();
if ($elements != $p * $c)
{
  $t->bail("GetArray() contains invalid number of elements: $elements");
}
$t->ok("GetArray() contains $elements elements");


#26 Test Ptr()
if ($oga->ptr() && $oga->ptr() != $src->Ptr())
{
  $t->bail("Ptr() returned invalid pointer: ".$oga->ptr().', '.$src->Ptr()."\n");
}
$t->ok("Ptr() returned a valid pointer");


$t->done();
exit 0;






package MyTests;
sub new
{
  my $this = shift;
  my $class = ref($this) || $this;
  my $self = {count=>0};
  bless($self,$class);

  my($tests,$title) = @_;
  $self->{tests} = $tests;
  print "1..$tests\n";
  $self->status("\n________________________________________");
  $self->status($title);
  $self->status("----------------------------------------");

  return $self;
}
sub status
{
  my($self,$msg) = @_;
  print STDERR "$msg\n";
}
sub ok
{
  my($self,$msg) = @_;
  $self->status("* ok: $msg");
  print 'ok '.++$self->{count}."\n";
}
sub skip
{
  my($self,$msg) = @_;
  $self->status("* skip: $msg");
  print 'ok '.++$self->{count}." \# skip $msg\n";
}
sub fail
{
  my($self,$msg) = @_;
  $self->status("* fail: $msg");
  print 'not ok '.++$self->{count}."\n";
}
sub bail
{
  my($self,$msg) = @_;
  $self->status("* bail: $msg\n");
  print "Bail out!\n";
  exit 0;
}
sub done
{
  my($self,$msg) = @_;

  for (my $c=$self->{count}; $self->{count} < $self->{tests}; $c++)
  {
    $self->skip('#'.($c+1)." - $msg");
  }

  $self->status("________________________________________");
}

__END__