I've uploaded GD::Image::Scale2x, a Scale2x implementation for GD, to CPAN. The first version had a few bugs in it, so version 0.02 has hit CPAN.
The Scale2x web site has a downloadble distro which includes some sample 2x, 3x and 4x conversions. I've used those files to test my algorithm (see the t/ directory). I had to mark the 3x test as a TODO in version 0.01 because i just couldn't figure out why it wasn't matching up. By doing some comparison work in the gimp, i was able to see that block E3 was always off.
To back up a bit, the basic algorithm for 3x is this: Take a pixel and the 8 surrounding pixels and do some comparisons between them to compute a smooth interpolated 3x3 scaled version:
+---+---+---+ +----+----+----+ | A | B | C | | E0 | E1 | E2 | +---+---+---+ +----+----+----+ | D | E | F | => | E3 | E4 | E5 | +---+---+---+ +----+----+----+ | G | H | I | | E6 | E7 | E8 | +---+---+---+ +----+----+----+
Block E3, according to the website, is calculated like so:
E3 = (D == B && E != G) || (D == B && E != A) ? D : E;
Which is wrong. It should be:
E3 = (D == B && E != G) || (D == H && E != A) ? D : E;
...and presto, everything matched up :)