The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use strict;
use warnings;

use Test::More;
use Prima::Test;
use Prima qw(Application);

plan tests => 1835;

my ($src, $mask, $dst);
my $can_argb = $::application->get_system_value(sv::LayeredWidgets);

my $win32 = $^O =~ /win32/i;

sub test_src
{
	my $descr = shift;
	$src->pixel(0,0,cl::Black);
	$src->pixel(1,0,cl::White);
	my $ok = $dst->put_image(0,0,$src);
	ok( $ok, "put $descr" );
	is( $dst->pixel(0,0), cl::Black, "$descr 0");
	is( $dst->pixel(1,0), cl::White, "$descr 1");
}

sub bitop
{
	my ( $pix, $descr, $s, $m, $d ) = @_;
	my $res = ( $d & $m ) ^ $s;
	my $clr = $res ? cl::White : cl::Black;
	is($pix, $clr, "$descr ($s & $m ^ $d == $res)");
}

sub fill_dst
{
	my $dst = shift;
	$dst->rop(rop::CopyPut);
	$dst->pixel(0,0,cl::Black);
	$dst->pixel(1,0,cl::Black);
	$dst->pixel(2,0,cl::Black);
	$dst->pixel(3,0,cl::Black);
	$dst->pixel(0,1,cl::White);
	$dst->pixel(1,1,cl::White);
	$dst->pixel(2,1,cl::White);
	$dst->pixel(3,1,cl::White);
}

sub test_mask
{
#  ....   .*.*   ..**   ..**
#  **** & .*.* ^ ..** = .**.
#
#  this doesn't work with RGBA blending because XOR can do inversions, while alpha channel cannot
#
	my $descr = shift;

	
	$mask->pixel(0,0,cl::Black);
	$mask->pixel(1,0,cl::White);
	$mask->pixel(2,0,cl::Black);
	$mask->pixel(3,0,cl::White);
	# convert AND-mask to alpha-channel (only to be converted back, but still..)
	$mask->put_image( 0, 0, $mask, rop::NotPut) if $mask->type == im::Byte;

	$src->pixel(0,0,cl::Black);
	$src->pixel(1,0,cl::Black);
	$src->pixel(2,0,cl::White);
	$src->pixel(3,0,cl::White);
	
	fill_dst($dst);

	my $icon = Prima::Icon->new;
	$icon->combine($src,$mask);

	my $ok = 1;
	$ok &= $dst->put_image(0,0,$icon);
	$ok &= $dst->put_image(0,1,$icon);
	ok( $ok, "put $descr" );

	bitop( $dst->pixel(0,0), $descr, 0,0,0);
	bitop( $dst->pixel(1,0), $descr, 0,1,0);
	bitop( $dst->pixel(2,0), $descr, 1,0,0);
	bitop( $dst->pixel(3,0), $descr, 1,1,0);

	bitop( $dst->pixel(0,1), $descr, 0,0,1);
	bitop( $dst->pixel(1,1), $descr, 0,1,1);
	bitop( $dst->pixel(2,1), $descr, 1,0,1);
	bitop( $dst->pixel(3,1), $descr, 1,1,1);
	
	# test 2: test a8 source in paint
}

sub test_dst
{
	my ($target, %opt) = @_;
	$src = Prima::DeviceBitmap->create( width => 2, height => 1, type => dbt::Bitmap);
	$dst->set(color => cl::Black, backColor => cl::White);
	test_src( "bitmap on $target");

	$dst->set(color => cl::White, backColor => cl::Black);
	$dst->clear;
	$src->pixel(0,0,cl::Black);
	$dst->put_image(0,0,$src);
	is( $dst->pixel(0,0), cl::White, "inverse bitmap on $target 0");
	$src->pixel(0,0,cl::White);
	$dst->put_image(0,0,$src);
	is( $dst->pixel(0,0), cl::Black, "inverse bitmap on $target 1");

	$dst->set(color => cl::Black, backColor => cl::Black);
	$src->pixel(0,0,cl::Black);
	$dst->put_image(0,0,$src);
	is( $dst->pixel(0,0), cl::Black, "clear bitmap on $target 0");
	$src->pixel(0,0,cl::White);
	$dst->put_image(0,0,$src);
	is( $dst->pixel(0,0), cl::Black, "clear bitmap on $target 1");

	$dst->set(color => cl::White, backColor => cl::White);
	$src->pixel(0,0,cl::Black);
	$dst->put_image(0,0,$src);
	is( $dst->pixel(0,0), cl::White, "set bitmap on $target 0");
	$src->pixel(0,0,cl::White);
	$dst->put_image(0,0,$src);
	is( $dst->pixel(0,0), cl::White, "set bitmap on $target 1");

	$src = Prima::DeviceBitmap->create( width => 2, height => 1, type => dbt::Pixmap);
	test_src( "pixmap on $target");

	$src = Prima::Image->create( width => 2, height => 1, type => im::BW);
	test_src( "im::BW on $target");
	is( unpack('C', $src->data), 0x40, "im::BW pixel(white) = 1");

	$src->begin_paint;
	test_src( "im::BW/paint on $target");

	$dst->set(color => cl::White, backColor => cl::Black);
	test_src( "inverse im::BW/paint on $target");
	$dst->set(color => cl::Black, backColor => cl::Black);
	test_src( "clear im::BW/paint on $target");
	$dst->set(color => cl::White, backColor => cl::White);
	test_src( "set im::BW/paint on $target");
	$src->end_paint;

	$src->type(im::bpp1);
	$src->colormap(cl::Black, cl::White);
	test_src( "im::bpp1/BW on $target");
	$src->colormap(cl::White, cl::Black);
	test_src( "im::bpp1/WB on $target");

	$src->colormap(cl::White, cl::Black);
	$src->begin_paint;
	test_src( "im::bpp1/paint on $target");
	$src->end_paint;

	$src->type(im::bpp4);
	test_src( "im::bpp4 on $target");

	$src->type(im::bpp4);
	$src->colormap(cl::White, cl::Black);
	$src->begin_paint;
	test_src( "im::bpp4/paint on $target");
	$src->end_paint;

	$src->type(im::bpp8);
	test_src( "im::bpp8 on $target");

	$src->type(im::bpp8);
	$src->colormap(cl::White, cl::Black);
	$src->begin_paint;
	test_src( "im::bpp8/paint on $target");
	$src->end_paint;

	$src->set( type => im::RGB);
	test_src( "im::RGB on $target");
	$src->begin_paint;
	test_src( "im::RGB/paint on $target");
	$src->end_paint;
	
	$mask = Prima::Image->create( width => 4, height => 1, type => im::BW);
	$src = Prima::Image->create( width => 4, height => 1, type => im::BW);
	test_mask( "1-bit grayscale xor mask / 1-bit and mask on $target");
	for my $bit ( 4, 8, 24) {
		$src = Prima::Image->create( width => 4, height => 1, type => $bit);
		test_mask( "$bit-bit xor mask / 1-bit and mask on $target");
	}

	$mask = Prima::Image->create( width => 4, height => 1, type => im::Byte);
	$src = Prima::Image->create( width => 4, height => 1, type => im::BW);
	test_blend( "1-bit grayscale image / 8-bit alpha on $target");
	$src = Prima::Image->create( width => 4, height => 1, type => im::bpp1);
	test_blend( "1-bit image / 8-bit alpha on $target");

	for my $bit ( 4, 8, 24) {
		$src = Prima::Image->create( width => 4, height => 1, type => $bit);
		test_blend( "$bit-bit image / 8-bit alpha on $target");
	}
}

sub blendop
{
	my ( $pix, $descr, $s, $m, $d ) = @_;
	if ( $win32 && $s == 1 && $m == 0 && $d == 0 ) {
		# this is win32 specific stuff; not that this behavior is
		# wrong for practical blending, but still a minor WTF
		ok( $pix == 0xffffff || $pix == 0, "$descr (($s + a$m) OVER $d ) == either 0 or 1 )");
	} else {
		my $res = $m ? $s : ( $s | $d );
		my $clr = $res ? cl::White : cl::Black;
		is($pix, $clr, "$descr (($s + a$m) OVER $d ) == $res)");
	}
}

sub test_blend_pixels
{
	my ($icon, $descr) = @_;

	my $ok = 1;
	$ok &= $dst->put_image(0,0,$icon);
	$ok &= $dst->put_image(0,1,$icon);

	ok( $ok, "put $descr" );

	blendop( $dst->pixel(0,0), $descr, 0,0,0);
	blendop( $dst->pixel(1,0), $descr, 0,1,0);
	blendop( $dst->pixel(2,0), $descr, 1,0,0);
	blendop( $dst->pixel(3,0), $descr, 1,1,0);

	blendop( $dst->pixel(0,1), $descr, 0,0,1);
	blendop( $dst->pixel(1,1), $descr, 0,1,1);
	blendop( $dst->pixel(2,1), $descr, 1,0,1);
	blendop( $dst->pixel(3,1), $descr, 1,1,1);
}

sub test_blend
{
SKIP: {
    	skip "no argb capability", 9 * 3 unless $can_argb;
#  0011 + ALPHA(1010) = 0.1*
# 
#  0000      0.1* 0011 ( . - fully transparent )
#  1111 OVER 0.1* 0111 ( * - transparent white )

	my $descr = shift;


	$mask->pixel(0,0,cl::Black);
	$mask->pixel(1,0,cl::White);
	$mask->pixel(2,0,cl::Black);
	$mask->pixel(3,0,cl::White);

	$src->pixel(0,0,cl::Black);
	$src->pixel(1,0,cl::Black);
	$src->pixel(2,0,cl::White);
	$src->pixel(3,0,cl::White);
	$src->type(im::RGB);

	my $icon = Prima::Icon->new( autoMasking => am::None );
	$icon->combine($src,$mask);

	fill_dst($dst);
	$dst->rop(rop::SrcOver);
	test_blend_pixels($icon, $descr);

	fill_dst($dst);
	$dst->rop(rop::SrcOver);
	$icon->begin_paint;
	test_blend_pixels($icon, "$descr (in paint)");
	$icon->end_paint;

	fill_dst($dst);
	$dst->rop(rop::SrcOver);
	test_blend_pixels($icon->bitmap, "$descr (layered)");
}}

sub test_blend_native
{
#  0011 + ALPHA(1010) = 0.1*
# 
#  0000      0.1* 0011 ( . - fully transparent )
#  1111 OVER 0.1* 0111 ( * - transparent white )

	my $descr = shift;


	$mask->pixel(0,0,cl::Black);
	$mask->pixel(1,0,cl::White);
	$mask->pixel(2,0,cl::Black);
	$mask->pixel(3,0,cl::White);

	$src->pixel(0,0,cl::Black);
	$src->pixel(1,0,cl::Black);
	$src->pixel(2,0,cl::White);
	$src->pixel(3,0,cl::White);

	my $icon = Prima::Icon->new( autoMasking => am::None );
	$icon->combine($src,$mask);

	fill_dst($dst);
	$dst->rop(rop::SrcOver);
	
	my $ok = 1;
	$ok &= $dst->put_image(0,0,$icon);
	$ok &= $dst->put_image(0,1,$icon);

	my $save = $dst;

	$dst = $dst->dup;
	$dst->type(im::RGB); # to convert 0xff into 0xffffff 

	ok( $ok, "put $descr" );

	blendop( $dst->pixel(0,0), $descr, 0,0,0);
	blendop( $dst->pixel(1,0), $descr, 0,1,0);
	blendop( $dst->pixel(2,0), $descr, 1,0,0);
	blendop( $dst->pixel(3,0), $descr, 1,1,0);

	blendop( $dst->pixel(0,1), $descr, 0,0,1);
	blendop( $dst->pixel(1,1), $descr, 0,1,1);
	blendop( $dst->pixel(2,1), $descr, 1,0,1);
	blendop( $dst->pixel(3,1), $descr, 1,1,1);

	$dst = $save;
}

$dst = Prima::Image->create( width => 4, height => 2, type => im::RGB);
$src  = Prima::Image->create( width => 4, height => 1, type => im::RGB);
$mask = Prima::Image->create( width => 4, height => 1, type => im::BW);
test_mask( "reference implementation / 1bit mask");

$mask = Prima::Image->create( width => 4, height => 1, type => im::Byte);
my $target = "reference implementation / 8bit mask";
$mask = Prima::Image->create( width => 4, height => 1, type => im::Byte);
$src = Prima::Image->create( width => 4, height => 1, type => im::Byte);
$dst = Prima::Image->create( width => 4, height => 2, type => im::Byte);
test_blend_native( "8-bit grayscale image / 8-bit alpha on $target");
$dst = Prima::Image->create( width => 4, height => 2, type => im::RGB);
$src = Prima::Image->create( width => 4, height => 1, type => im::RGB);
test_blend_native( "24-bit image / 8-bit alpha on $target");

$dst = Prima::DeviceBitmap->create( width => 4, height => 2, type => dbt::Bitmap);
test_dst("bitmap");

$dst = Prima::DeviceBitmap->create( width => 4, height => 2, type => dbt::Pixmap);
test_dst("pixmap");

$dst = Prima::Image->create( width => 4, height => 2, type => im::BW);
$dst->begin_paint;
test_dst("im::BW");
$dst->end_paint;

$dst = Prima::Image->create( width => 4, height => 2, type => im::bpp1);
$dst->begin_paint;
test_dst("im::bpp1");
$dst->end_paint;

$dst = Prima::Image->create( width => 4, height => 2, type => im::RGB);
$dst->begin_paint;
test_dst("im::RGB");
$dst->end_paint;

# Because get_pixel from non-buffered guarantees nothing. 
# .buffered is also not guaranteed, but for 8 pixel widget that shouldn't be a problem
#
# also, do test inside onPaint to make sure it's on the buffer, not on the screen
$dst = Prima::Widget->create( width => 4, height => 2, buffered => 1, onPaint => sub {
	return if get_flag;
	set_flag;
	test_dst("widget");
}); 
$dst->bring_to_front;
SKIP: {
	skip "cannot get widget to paint", 226 unless wait_flag;
}

SKIP: {
    skip "no argb capability", 226 unless $can_argb;
    reset_flag;
    $dst = Prima::Widget->create( width => 4, height => 2, buffered => 1, layered => 1, onPaint => sub {
	return if get_flag;
	set_flag;
        test_dst("argb widget");
    });

    $dst->bring_to_front;
    skip "cannot get widget to paint", 226 unless wait_flag;
}

SKIP: {
    skip "no argb capability", 226 unless $can_argb;
    $dst = Prima::DeviceBitmap->create( width => 4, height => 2, type => dbt::Layered);
    test_dst("layered");
}