feat: initial v0.1.0 MVP
Replaces Smush Pro's optimisation pipeline without the grey-wash bug. CLI commands working: wp h4b-img status wp h4b-img optimise --id=<n> wp h4b-img bulk wp h4b-img rescue Verified on dev.rds.ink: - ICC profile preservation works (the Smush-bug fix) - Bulk: 20 attachments → 487 KB saved (10.4%), 0 errors - Rescue: end-to-end mechanism verified on WorkingAsOne_horse fixture - WebP synchronous, AVIF queued via WP-Cron - Originals backed up to wp-content/h4b-img-originals/ See CHANGELOG.md for details + ../DESIGN-h4b-image-optim.md for architecture. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
67
includes/class-icc-profile.php
Normal file
67
includes/class-icc-profile.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/**
|
||||
* ICC profile handling — the critical fix for the Smush grey-wash bug.
|
||||
*
|
||||
* If an image has an ICC profile, KEEP IT. Stripping ICC + applying YCbCr
|
||||
* subsampling is what produced the grey-wash on rds.ink art.
|
||||
*
|
||||
* If an image has NO profile, the safest default is to embed a small sRGB v4
|
||||
* profile so browsers don't fall back to vendor-specific assumptions.
|
||||
*
|
||||
* @package H4B\ImageOptim
|
||||
*/
|
||||
|
||||
namespace H4B\ImageOptim;
|
||||
|
||||
use Imagick;
|
||||
use ImagickException;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
final class ICC_Profile {
|
||||
|
||||
/**
|
||||
* Path to the bundled sRGB v4 ICC profile.
|
||||
* If missing, we silently skip injection (the file's pixels will just
|
||||
* be interpreted as implicit sRGB by the browser).
|
||||
*/
|
||||
public static function srgb_profile_path(): string {
|
||||
return H4B_IMG_OPTIM_DIR . 'assets/sRGB_v4_ICC_preference.icc';
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspect an Imagick image and ensure an ICC profile is present.
|
||||
* Does NOT modify the colour space — only attaches a profile.
|
||||
*
|
||||
* @return array{result:bool, source:string} result is true if a profile
|
||||
* is present/added. source is one of: 'original','injected','none'.
|
||||
*/
|
||||
public static function preserve_or_inject( Imagick $img ): array {
|
||||
try {
|
||||
$profiles = $img->getImageProfiles( 'icc', true );
|
||||
} catch ( ImagickException $e ) {
|
||||
$profiles = [];
|
||||
}
|
||||
if ( ! empty( $profiles ) ) {
|
||||
// Source has an ICC profile — leave it alone. This is the
|
||||
// common case for camera JPEGs and properly-saved Photoshop exports.
|
||||
return [ 'result' => true, 'source' => 'original' ];
|
||||
}
|
||||
|
||||
// No profile. Try to inject sRGB v4 fallback.
|
||||
$profile_path = self::srgb_profile_path();
|
||||
if ( ! is_readable( $profile_path ) ) {
|
||||
return [ 'result' => false, 'source' => 'none' ];
|
||||
}
|
||||
|
||||
try {
|
||||
$img->setImageColorspace( Imagick::COLORSPACE_SRGB );
|
||||
$img->profileImage( 'icc', (string) file_get_contents( $profile_path ) );
|
||||
return [ 'result' => true, 'source' => 'injected' ];
|
||||
} catch ( ImagickException $e ) {
|
||||
return [ 'result' => false, 'source' => 'none' ];
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user