Skip to content

Commit 436f5b6

Browse files
seanbonnerclaude
andcommitted
Scale punk images to 480x480 with nearest-neighbor
The 24x24 source images were being dithered when scaled by browsers. Now scales to 480x480 (20x) at fetch time using nearest-neighbor interpolation to preserve crisp pixel edges. Uses Imagick if available, falls back to GD pixel-by-pixel copy. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 69db070 commit 436f5b6

1 file changed

Lines changed: 79 additions & 4 deletions

File tree

wp-content/plugins/sb-punks-registry/sb-punks-registry.php

Lines changed: 79 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
/**
33
* Plugin Name: SB Punks Registry
44
* Description: BurnedPunks/MuseumPunks registry + front-page mosaic + numeric permalinks + single punk layout.
5-
* Version: 0.3.1
5+
* Version: 0.3.2
66
* Author: SB
77
*/
88

@@ -172,7 +172,7 @@ public static function filter_sb_punk_link($permalink, $post, $leavename = false
172172
}
173173

174174
public static function enqueue_assets() : void {
175-
$ver = '0.3.1';
175+
$ver = '0.3.2';
176176
wp_enqueue_style('sbpr', plugins_url('assets/sbpr.css', __FILE__), [], $ver);
177177
wp_enqueue_script('sbpr', plugins_url('assets/sbpr.js', __FILE__), [], $ver, true);
178178
}
@@ -669,9 +669,9 @@ public static function render_status_box($post) : void {
669669
const PUNK_IMAGE_BASE = 'https://www.larvalabs.com/public/images/cryptopunks/punk';
670670

671671
/**
672-
* Fetch punk PNG from Larva Labs
672+
* Fetch punk PNG from Larva Labs and scale up with nearest-neighbor
673673
*/
674-
private static function fetch_punk_image(int $punk_id) : string {
674+
private static function fetch_punk_image(int $punk_id, int $target_size = 480) : string {
675675
if ($punk_id < 0 || $punk_id > 9999) return '';
676676

677677
// Format: punk0001.png, punk0123.png, punk9999.png
@@ -699,9 +699,84 @@ private static function fetch_punk_image(int $punk_id) : string {
699699
return '';
700700
}
701701

702+
// Scale up the 24x24 image to target size using nearest-neighbor
703+
$scaled = self::scale_image_nearest_neighbor($body, $target_size);
704+
if (!empty($scaled)) {
705+
return $scaled;
706+
}
707+
708+
// Fallback to original if scaling fails
702709
return $body;
703710
}
704711

712+
/**
713+
* Scale PNG image data using nearest-neighbor interpolation
714+
*/
715+
private static function scale_image_nearest_neighbor(string $image_data, int $target_size) : string {
716+
// Try Imagick first (best quality control)
717+
if (class_exists('Imagick')) {
718+
try {
719+
$im = new Imagick();
720+
$im->readImageBlob($image_data);
721+
$im->setImageInterpolateMethod(Imagick::INTERPOLATE_NEAREST_NEIGHBOR);
722+
$im->resizeImage($target_size, $target_size, Imagick::FILTER_POINT, 1);
723+
$im->setImageFormat('png');
724+
$result = $im->getImageBlob();
725+
$im->destroy();
726+
return $result;
727+
} catch (Exception $e) {
728+
error_log('SBPR: Imagick scaling failed - ' . $e->getMessage());
729+
}
730+
}
731+
732+
// Fallback to GD
733+
if (function_exists('imagecreatefrompng')) {
734+
$src = @imagecreatefromstring($image_data);
735+
if ($src === false) {
736+
error_log('SBPR: GD could not read image');
737+
return '';
738+
}
739+
740+
$src_w = imagesx($src);
741+
$src_h = imagesy($src);
742+
743+
$dst = imagecreatetruecolor($target_size, $target_size);
744+
if ($dst === false) {
745+
imagedestroy($src);
746+
return '';
747+
}
748+
749+
// Preserve transparency
750+
imagealphablending($dst, false);
751+
imagesavealpha($dst, true);
752+
$transparent = imagecolorallocatealpha($dst, 0, 0, 0, 127);
753+
imagefill($dst, 0, 0, $transparent);
754+
755+
// Scale using nearest-neighbor (pixel-by-pixel copy)
756+
$scale = $target_size / $src_w;
757+
for ($y = 0; $y < $target_size; $y++) {
758+
for ($x = 0; $x < $target_size; $x++) {
759+
$src_x = (int)floor($x / $scale);
760+
$src_y = (int)floor($y / $scale);
761+
$color = imagecolorat($src, $src_x, $src_y);
762+
imagesetpixel($dst, $x, $y, $color);
763+
}
764+
}
765+
766+
ob_start();
767+
imagepng($dst);
768+
$result = ob_get_clean();
769+
770+
imagedestroy($src);
771+
imagedestroy($dst);
772+
773+
return $result;
774+
}
775+
776+
error_log('SBPR: No image library available for scaling');
777+
return '';
778+
}
779+
705780
/**
706781
* Generate punk image and set as featured image
707782
*/

0 commit comments

Comments
 (0)