Diagnosed visible faint grey rectangle around B&W ink art images on pages with pure-white backgrounds. Root cause was format encoder luminance shift in near-white pixels: Source JPG corner: (253,253,253) = #FDFDFD = 99.2% white AVIF q=65 (default): (250,250,250) = #FAFAFA = 98.0% (1.2% halo) WebP q=80 (default): (249,249,249) = #F9F9F9 = 97.6% (1.6% halo) Two changes: 1. avifenc now uses -y 444 (full chroma subsampling) instead of default 4:2:0. Brings AVIF corner to #FBFBFB = 98.4%, smaller file size as a bonus (~10% reduction on a typical art image). 2. WebP default quality raised 80 → 90. Reaches #FDFDFD = exact match with source JPG. File size increases ~30% but eliminates the halo entirely for WebP-capable browsers (vast majority). AVIF still has 0.4% residual halo (libavif 0.11.1 ceiling at this quality range — pushing higher yields no improvement, only file size). Acceptable tradeoff: WebP is the served-by-default fallback when AVIF isn't perfect. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
90 lines
2.9 KiB
PHP
90 lines
2.9 KiB
PHP
<?php
|
|
/**
|
|
* Settings — single option, schema in defaults().
|
|
*
|
|
* @package H4B\ImageOptim
|
|
*/
|
|
|
|
namespace H4B\ImageOptim;
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit;
|
|
}
|
|
|
|
final class Settings {
|
|
|
|
public const OPTION_KEY = 'h4b_image_optim_settings';
|
|
|
|
public static function register(): void {
|
|
// No admin UI in v0.1 — settings read-only via WP-CLI for now.
|
|
}
|
|
|
|
public static function defaults(): array {
|
|
return [
|
|
// Quality
|
|
'jpeg_quality' => 85,
|
|
'png_quality_min' => 70,
|
|
'png_quality_max' => 90,
|
|
'jpeg_chroma_subsampling' => '4:4:4', // preserve edges; the fix for the Smush bug
|
|
'preserve_icc_profile' => true, // CRITICAL — the bug fix
|
|
'strip_gps_exif' => true,
|
|
'strip_camera_exif' => false, // keep camera info for art provenance
|
|
|
|
// Format generation
|
|
'generate_webp' => true,
|
|
'generate_avif' => true, // design decision 1: on by default everywhere
|
|
'webp_quality' => 90, // matches source JPG luminance for B&W ink on white
|
|
'avif_quality' => 65, // ≈ JPEG q=85 visually
|
|
'avif_speed' => 6, // 0-10; 6 is balanced
|
|
'avif_async' => true, // background WP-Cron so upload UI is responsive
|
|
|
|
// Behaviour
|
|
'optimise_on_upload' => true,
|
|
'backup_originals' => true, // design decision 4: always
|
|
'backup_prune_days' => 90, // 0 = keep forever
|
|
'resize_max_width' => 2560,
|
|
'resize_max_height' => 2560,
|
|
'skip_already_processed' => true,
|
|
'min_optimise_bytes' => 20480, // skip files < 20KB (thumbnails)
|
|
|
|
// Serving
|
|
'rewrite_content_images' => true, // Picture tag rewriting
|
|
'use_htaccess_fallback' => true, // .htaccess content negotiation
|
|
|
|
// Bulk
|
|
'bulk_batch_size' => 10,
|
|
'bulk_pause_seconds' => 1,
|
|
|
|
// Exclusions
|
|
'exclude_mime_types' => [ 'image/svg+xml', 'image/x-icon', 'image/gif' ],
|
|
'exclude_paths_regex' => '',
|
|
];
|
|
}
|
|
|
|
public static function install_defaults(): void {
|
|
$existing = get_option( self::OPTION_KEY );
|
|
if ( false === $existing ) {
|
|
add_option( self::OPTION_KEY, self::defaults(), '', false );
|
|
return;
|
|
}
|
|
// Merge new keys without clobbering user changes
|
|
$merged = array_replace( self::defaults(), is_array( $existing ) ? $existing : [] );
|
|
update_option( self::OPTION_KEY, $merged, false );
|
|
}
|
|
|
|
public static function get( string $key, mixed $default = null ): mixed {
|
|
$settings = get_option( self::OPTION_KEY, self::defaults() );
|
|
return $settings[ $key ] ?? $default;
|
|
}
|
|
|
|
public static function set( string $key, mixed $value ): void {
|
|
$settings = get_option( self::OPTION_KEY, self::defaults() );
|
|
$settings[ $key ] = $value;
|
|
update_option( self::OPTION_KEY, $settings, false );
|
|
}
|
|
|
|
public static function all(): array {
|
|
return get_option( self::OPTION_KEY, self::defaults() );
|
|
}
|
|
}
|