From 63d8c60cd23f5779e0b04ed9ec79d2df386c55ba Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 13 Aug 2025 14:51:43 +0000 Subject: [PATCH 1/3] Add advanced AVIF conversion options: chroma subsampling and bit depth Co-authored-by: dingoes-51.slots --- avif-local-support.php | 4 ++ includes/class-avif-suite.php | 71 +++++++++++++++++++++++++++++++++++ includes/class-converter.php | 10 +++++ 3 files changed, 85 insertions(+) diff --git a/avif-local-support.php b/avif-local-support.php index d8887fe..e172b29 100644 --- a/avif-local-support.php +++ b/avif-local-support.php @@ -61,6 +61,8 @@ function avif_local_support_activate(): void add_option('avif_local_support_preserve_color_profile', true); add_option('avif_local_support_wordpress_logic', true); add_option('avif_local_support_cache_duration', 3600); + add_option('avif_local_support_chroma_subsampling', '4:2:0'); + add_option('avif_local_support_bit_depth', 8); } function avif_local_support_deactivate(): void @@ -99,6 +101,8 @@ function avif_local_support_uninstall(): void 'avif_local_support_preserve_color_profile', 'avif_local_support_wordpress_logic', 'avif_local_support_cache_duration', + 'avif_local_support_chroma_subsampling', + 'avif_local_support_bit_depth', ]; foreach ($options as $option) { diff --git a/includes/class-avif-suite.php b/includes/class-avif-suite.php index 877fa8e..e67baf8 100644 --- a/includes/class-avif-suite.php +++ b/includes/class-avif-suite.php @@ -270,6 +270,63 @@ function (): void { 'avif_local_support_conversion', [ 'label_for' => 'avif_local_support_wordpress_logic' ] ); + + // Advanced options section + register_setting('avif_local_support_settings', 'avif_local_support_chroma_subsampling', [ + 'type' => 'string', + 'default' => '4:2:0', + 'sanitize_callback' => [$this, 'sanitize_chroma_subsampling'], + 'show_in_rest' => true, + ]); + register_setting('avif_local_support_settings', 'avif_local_support_bit_depth', [ + 'type' => 'integer', + 'default' => 8, + 'sanitize_callback' => [$this, 'sanitize_bit_depth'], + 'show_in_rest' => true, + ]); + + add_settings_section( + 'avif_local_support_advanced', + '', + function (): void {}, + 'avif-local-support' + ); + + add_settings_field( + 'avif_local_support_chroma_subsampling', + __('Chroma subsampling', 'avif-local-support'), + function (): void { + $value = (string) get_option('avif_local_support_chroma_subsampling', '4:2:0'); + $options = ['4:2:0', '4:2:2', '4:4:4']; + echo ''; + echo '

' . esc_html__('Lower subsampling reduces file size; 4:4:4 preserves the most color detail (best for graphics/text).', 'avif-local-support') . '

'; + }, + 'avif-local-support', + 'avif_local_support_advanced', + [ 'label_for' => 'avif_local_support_chroma_subsampling' ] + ); + + add_settings_field( + 'avif_local_support_bit_depth', + __('Bit depth', 'avif-local-support'), + function (): void { + $value = (int) get_option('avif_local_support_bit_depth', 8); + $options = [8, 10, 12]; + echo ''; + echo '

' . esc_html__('Higher bit depths can reduce banding and improve gradients (requires Imagick with AVIF support).', 'avif-local-support') . '

'; + }, + 'avif-local-support', + 'avif_local_support_advanced', + [ 'label_for' => 'avif_local_support_bit_depth' ] + ); } public function sanitize_schedule_time(string $value): string @@ -285,6 +342,20 @@ public function sanitize_speed($value): int return $n; } + public function sanitize_chroma_subsampling($value): string + { + $val = is_string($value) ? $value : ''; + $allowed = ['4:2:0', '4:2:2', '4:4:4']; + return in_array($val, $allowed, true) ? $val : '4:2:0'; + } + + public function sanitize_bit_depth($value): int + { + $n = (int) $value; + $allowed = [8, 10, 12]; + return in_array($n, $allowed, true) ? $n : 8; + } + public function render_admin_page(): void { if (!current_user_can('manage_options')) { diff --git a/includes/class-converter.php b/includes/class-converter.php index a641ef1..4e9a53b 100644 --- a/includes/class-converter.php +++ b/includes/class-converter.php @@ -136,6 +136,10 @@ private function convertToAvif(string $sourcePath, string $avifPath, ?array $tar $speedSetting = max(0, min(10, (int) get_option('avif_local_support_speed', 1))); $preserveMeta = (bool) get_option('avif_local_support_preserve_metadata', true); $preserveICC = (bool) get_option('avif_local_support_preserve_color_profile', true); + $chromaSubsampling = (string) get_option('avif_local_support_chroma_subsampling', '4:2:0'); + if (!in_array($chromaSubsampling, ['4:2:0','4:2:2','4:4:4'], true)) { $chromaSubsampling = '4:2:0'; } + $bitDepthSetting = (int) get_option('avif_local_support_bit_depth', 8); + if (!in_array($bitDepthSetting, [8,10,12], true)) { $bitDepthSetting = 8; } // Ensure directory exists $dir = \dirname($avifPath); @@ -181,6 +185,12 @@ private function convertToAvif(string $sourcePath, string $avifPath, ?array $tar $im->setImageFormat('AVIF'); $im->setImageCompressionQuality($quality); $im->setOption('avif:speed', (string) min(8, $speedSetting)); + // Advanced: chroma subsampling and bit depth + $im->setOption('avif:chroma-subsampling', $chromaSubsampling); + $im->setOption('heic:chroma-subsampling', $chromaSubsampling); + $im->setOption('avif:depth', (string) $bitDepthSetting); + $im->setOption('heic:depth', (string) $bitDepthSetting); + try { $im->setImageDepth($bitDepthSetting); } catch (\Throwable $e) {} if ($preserveMeta || $preserveICC) { try { From c94a6ca2646599c037eb47cb8c4a100a429717a6 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 13 Aug 2025 14:55:27 +0000 Subject: [PATCH 2/3] Remove redundant HEIC-specific image conversion options Co-authored-by: dingoes-51.slots --- includes/class-converter.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/includes/class-converter.php b/includes/class-converter.php index 4e9a53b..265d299 100644 --- a/includes/class-converter.php +++ b/includes/class-converter.php @@ -187,9 +187,7 @@ private function convertToAvif(string $sourcePath, string $avifPath, ?array $tar $im->setOption('avif:speed', (string) min(8, $speedSetting)); // Advanced: chroma subsampling and bit depth $im->setOption('avif:chroma-subsampling', $chromaSubsampling); - $im->setOption('heic:chroma-subsampling', $chromaSubsampling); $im->setOption('avif:depth', (string) $bitDepthSetting); - $im->setOption('heic:depth', (string) $bitDepthSetting); try { $im->setImageDepth($bitDepthSetting); } catch (\Throwable $e) {} if ($preserveMeta || $preserveICC) { From 28ba96c361aaf30e830cd1384a9124145349ed45 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 13 Aug 2025 16:19:15 +0000 Subject: [PATCH 3/3] Improve image conversion with ICC profile handling and resize techniques Co-authored-by: dingoes-51.slots --- includes/class-converter.php | 52 +++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/includes/class-converter.php b/includes/class-converter.php index 265d299..3f20920 100644 --- a/includes/class-converter.php +++ b/includes/class-converter.php @@ -160,6 +160,10 @@ private function convertToAvif(string $sourcePath, string $avifPath, ?array $tar } } + // Detect whether source has an embedded ICC profile + $hasIcc = false; + try { $maybeIcc = $im->getImageProfile('icc'); $hasIcc = !empty($maybeIcc); } catch (\Throwable $e) {} + if ($targetDimensions && isset($targetDimensions['width'], $targetDimensions['height'])) { $srcW = $im->getImageWidth(); $srcH = $im->getImageHeight(); @@ -179,7 +183,20 @@ private function convertToAvif(string $sourcePath, string $avifPath, ?array $tar $cropY = (int) (($srcH - $cropH) / 2); } $im->cropImage($cropW, $cropH, $cropX, $cropY); + + // Tweak Lanczos filter and use linear-light resize for untagged images + try { $im->setOption('filter:blur', '0.985'); } catch (\Throwable $e) {} + try { $im->setOption('filter:window', 'Jinc'); } catch (\Throwable $e) {} + try { $im->setOption('filter:lobes', '3'); } catch (\Throwable $e) {} + if (!$hasIcc) { + try { $im->transformImageColorspace(\Imagick::COLORSPACE_RGB); } catch (\Throwable $e) {} + } $im->resizeImage($tW, $tH, \Imagick::FILTER_LANCZOS, 1.0); + if (!$hasIcc) { + try { $im->transformImageColorspace(\Imagick::COLORSPACE_SRGB); } catch (\Throwable $e) {} + } + // Mild post-resize sharpening to restore micro-contrast + try { $im->unsharpMaskImage(0.0, 0.8, 0.9, 0.02); } catch (\Throwable $e) {} } $im->setImageFormat('AVIF'); @@ -195,7 +212,18 @@ private function convertToAvif(string $sourcePath, string $avifPath, ?array $tar $src = new \Imagick($sourcePath); if ($preserveICC) { $icc = $src->getImageProfile('icc'); - if (!empty($icc)) { $im->setImageProfile('icc', $icc); } + if (!empty($icc)) { + $im->setImageProfile('icc', $icc); + } else { + // Assign sRGB only (do not transform) when no ICC exists + $srgbPath = $this->findSrgbProfile(); + if ($srgbPath && is_readable($srgbPath)) { + $srgbBytes = @file_get_contents($srgbPath); + if ($srgbBytes !== false && $srgbBytes !== '') { + $im->setImageProfile('icc', $srgbBytes); + } + } + } } if ($preserveMeta) { foreach (['exif', 'xmp', 'iptc'] as $profile) { @@ -490,4 +518,26 @@ public function convertAttachmentNow(int $attachmentId): array return $results; } + + /** + * Try to locate a system sRGB ICC profile to tag unprofiled images without converting pixels. + */ + private function findSrgbProfile(): ?string + { + $candidates = [ + '/usr/share/color/icc/sRGB.icc', + '/usr/share/color/icc/colord/sRGB.icc', + '/usr/share/color/icc/ghostscript/sRGB.icc', + '/usr/local/share/color/icc/sRGB.icc', + '/usr/share/icc/sRGB.icc', + 'C:\\Windows\\System32\\spool\\drivers\\color\\sRGB Color Space Profile.icm', + '/System/Library/ColorSync/Profiles/sRGB Profile.icc', + ]; + foreach ($candidates as $path) { + if (@is_file($path) && @is_readable($path)) { + return $path; + } + } + return null; + } }