Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Chriss Miller on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Perl Tk Transparent Icons 2

Status
Not open for further replies.

Kirsle

Programmer
Jan 21, 2006
1,179
US
This is a follow-up to thread219-1225348. I did some more digging and have finally gotten somewhere.

The task was in trying to figure out how to make the window icon of a Tk app have transparency in it. Using a GIF, PNG, or XPM file with transparent pixels doesn't work--the transparent parts just get filled in with black (inconsistently; oftentimes there will be small speckles of transparent pixels among the black ones).

On Win32, Tk windows have a default icon of the letters "Tk" with red text, and the rest of the icon is transparent. On Linux, the default icon is that of a generic application icon (i.e. an icon of a white box with a title bar of sorts).

I used to think that maybe XPM files were the key to having transparent window icons, but XPM files with explicit transparency pixels set gave the same effect that GIF and PNG did (black pixels). At any rate, on Linux I copied the Tk.xpm file from Tk's folder (the one I thought Tk was using as its default icon on Windows) and had my test script explicitly use that icon.

Code:
use Tk;

my $mw = MainWindow->new (
	-title => 'Test',
);

my $icon = $mw->Photo (
	-file => 'Tk.xpm',
	-format => 'xpm',
);

$mw->Icon (-image => $icon);

MainLoop;

tk-icon-1.png


The XPM file itself has a teal background and red text, and the same was the result of my window's icon. So, the possibility of teal being a special reserved color was out of the question.

I did some more Googling and discovered that through the use of bitmaps you can specify an "icon mask". I tested it with a built-in bitmap: questhead.

Code:
use Tk;

my $mw = MainWindow->new (
	-title => 'Test',
);

$mw->iconbitmap ('questhead');

MainLoop;

tk-icon-2.png


This would set the icon to be the built-in bitmap questhead. The bitmap itself was white but the background remained black. However, add the "iconmask" method, and:

Code:
use Tk;

my $mw = MainWindow->new (
	-title => 'Test',
);

$mw->iconbitmap ('questhead');
$mw->iconmask ('questhead');

MainLoop;

tk-icon-3.png


Voila: a really transparent icon! Here's it again using the Clearlooks theme to see the transparency better:

tk-icon-4.png


I then kinda crossed the two, and combined the XPM of the Tk icon with the built-in bitmap called 'Tk':

Code:
use Tk;

my $mw = MainWindow->new (
	-title => 'Test',
);

my $icon = $mw->Photo (
	-file => 'Tk.xpm',
	-format => 'xpm',
);

$mw->Icon (-image => $icon);
$mw->iconmask ('Tk');

MainLoop;

The result: on Linux, I got the window icon to look just like the default icon on Windows: red Tk letters on a transparent background.

tk-icon-5.png


So, I've finally unlocked the secret to having transparent window icons for Tk scripts. The task now is to try to figure out how to create my own custom bitmap images. I looked into that once before (well, specifically I was looking for custom cursors, but the idea is the same) and iirc it wasn't very easy to do. Specifically, while Tk has a handful of built-in bitmaps, the files containing these bitmaps is quite elusive to find. However, the existence of Tk::Bitmap leaves me hopeful.

The idea of combining a GIF, PNG, or XPM image with a bitmap to define its transparency mask is pretty fun to think about.

-------------
Cuvou.com | My personal homepage
Project Fearless | My web blog
 
Update:

I was digging through the Tk demo folders (on my system, /usr/lib/perl5/site_perl/5.8.8/i386-linux-thread-multi/Tk/demos/images) used by the widget program, and found a few interesting files:

cursor.xbm (apparently a cursor image that looks like a mouse)
cursor.mask (apparently the mask file for the cursor)
dir.icon (an icon of a folder)
dir.mask (the mask for the folder icon)

While the cursor files were interesting and I'll be sure to play with them later, I was more interested in the dir.icon, which appeared to be an X11 bitmap image.

Real quick, the source codes to those files:

dir.icon
Code:
#define nfm_dir.icon_width 32
#define nfm_dir.icon_height 32
#define nfm_dir.icon_x_hot 16
#define nfm_dir.icon_y_hot 16
static char nfm_dir.icon_bits[] = {
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0xe0, 0x0f, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x08, 0x20,
   0xfe, 0xff, 0x07, 0x40, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0,
   0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0,
   0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0,
   0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0,
   0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0,
   0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0,
   0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0, 0x01, 0x00, 0x00, 0xc0,
   0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff};

dir.mask
Code:
#define nfm_dir.mask_width 32
#define nfm_dir.mask_height 32
#define nfm_dir.mask_x_hot 16
#define nfm_dir.mask_y_hot 16
static char nfm_dir.mask_bits[] = {
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0xe0, 0x0f, 0x00, 0x00, 0xf0, 0x1f, 0x00, 0x00, 0xf8, 0x3f,
   0xfe, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
   0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
   0xff, 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff};

Anyway, it took me a while to figure out how to use these bitmaps (documentation for Tk bitmaps is very poor, I've discovered), but I finally got something thrown together.

Here's my test code:

Code:
use Tk;
use Tk::PNG;

my $mw = MainWindow->new (
	-title => 'Test',
);
$mw->geometry ('200x30');

my $bmp = $mw->Photo (
	-file => 'test.png',
	-format => 'png',
);

$mw->iconimage ($bmp);
$mw->iconmask ('@dir.mask');

$mw->Label (
	-image => $bmp,
)->pack;

MainLoop;

I created a PNG image in $bmp. The PNG is a simple 32x32 block of wood (literally--see screenshot). I copied dir.mask to the directory of my script and so iconmask points to it via the @ sign (telling it not to use a built-in bitmap).

The result:

tk-icon-6.png


Pretty neat. :) Hope this will be helpful to others, cuz I know that this has personally been something that's been bugging me for the longest time.

-------------
Cuvou.com | My personal homepage
Project Fearless | My web blog
 
maybe make a FAQ, this thread will disappear quickly.

------------------------------------------
- Kevin, perl coder unexceptional! [wiggle]
 
I agree, this is definitely faq worthy. Fortunately, good ole google knows what's happen'n.

- Miller
 
I put up a FAQ a day or two ago. Also, I created a web-based XBM mask file generator:


It uses GD to read the image data and Image::Xbm to write the XBM file. Here's the relevant bits of code if you're interested in how it does it:

Code:
if ($action eq 'upload') {
	my $filename = $cgi->param ('icon');
	my @parts = split(/(\/|\\)/, $filename);
	my $name = pop(@parts);

	# Determine the file's type.
	my ($type) = $name =~ /\.(.+?)$/;
	$type = lc($type);
	if ($type !~ /^(gif|png)$/) {
		&printError("The file type has to be either a GIF or PNG image.");
	}

	# Download the file.
	my $handle = $cgi->upload ('icon');
	my $bin = '';
	while (<$handle>) {
		$bin .= $_;
	}

	# Save it to a temporary file.
	mkdir ("./xbmask-temp") unless (-d "./xbmask-temp");
	my $tmp = md5_hex($ENV{REMOTE_ADDR} . $ENV{HTTP_USER_AGENT} . time());
	while (-d "./xbmask-temp/$tmp") {
		$tmp = md5_hex (int(rand(99999)));
	}
	mkdir ("./xbmask-temp/$tmp");
	open (WRITE, ">./xbmask-temp/$tmp/$name");
	binmode WRITE;
	print WRITE $bin;
	close (WRITE);

	# Load GD and read our image file.
	my $gd = undef;
	if ($type eq 'png') {
		$gd = GD::Image->newFromPng ("./xbmask-temp/$tmp/$name");
	}
	else {
		$gd = GD::Image->newFromGif ("./xbmask-temp/$tmp/$name");
	}

	# Verify that this is an indexed-color image.
	if ($gd->isTrueColor) {
		&printError("Truecolor images are not supported by this service!",$tmp);
	}

	# Get the image's dimensions and the index of our transparent pixel.
	my $width = $gd->width;
	my $height = $gd->height;
	my $alpha  = $gd->transparent;

	# Too large?
	if ($width > 256 || $height > 256) {
		&printError("Your image is larger than the 256x256 maximum allowed size!",$tmp);
	}

	# No transparency?
	if ($alpha < 0) {
		&printError("The image you uploaded has no transparent pixels.",$tmp);
	}

	# Create a new XBM image.
	my $xbm = new Image::Xbm (
		-width  => $width,
		-height => $height,
	);

	# Transfer the pixels over.
	my ($x,$y) = (0,0);
	my $transferring = 1;
	while ($transferring) {
		my $pixel = $gd->getPixel ($x,$y);

		# See if this was a transparent pixel or not.
		if ($pixel == $alpha) {
			# Transfer a 'white' pixel to the XBM.
			$xbm->xy ($x,$y,'white');
		}
		else {
			# Transfer a 'black' pixel to the XBM.
			$xbm->xy ($x,$y,'black');
		}

		# Increment the fields.
		$x++;
		if ($x == $width) {
			$x = 0;
			$y++;
		}
		if ($y == $height) {
			$transferring = 0;
			last;
		}
	}

	# Save the XBM image.
	my $xbname = $name;
	$xbname =~ s/\.(gif|png)$//i;
	$xbm->save ("./xbmask-temp/$tmp/$xbname\.xbm");

	&printSuccess($tmp,$name,$xbname);
}

-------------
Cuvou.com | My personal homepage
Project Fearless | My web blog
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top