'lossless', 'content' => 'auto', 'effort' => 'balanced', 'formats' => 'auto', 'metadata' => 'preserve', ); } /** * A recipe kept to its allowed choices. * * @param array $input The recipe asked for. * @return array The recipe. * @throws \RuntimeException With invalid_compression_policy when a choice is not allowed, lossless_avif_not_supported * when a lossless recipe asks for AVIF. */ public static function clean( array $input ): array { $out = self::defaults(); $choices = array( 'mode' => array( 'lossless', 'high', 'maximum' ), 'content' => array( 'auto', 'photo', 'graphics' ), 'effort' => array( 'fast', 'balanced' ), 'formats' => array( 'auto', 'native', 'webp', 'avif' ), 'metadata' => array( 'preserve', 'remove_gps', 'remove_all' ), ); foreach ( $choices as $k => $list ) { if ( isset( $input[ $k ] ) ) { if ( ! is_string( $input[ $k ] ) || ! in_array( $input[ $k ], $list, true ) ) { throw new \RuntimeException( 'invalid_compression_policy' ); } $out[ $k ] = $input[ $k ]; } } if ( 'lossless' === $out['mode'] && 'avif' === $out['formats'] ) { throw new \RuntimeException( 'lossless_avif_not_supported' ); } return $out; } /** * The recipe of a picture (its own saved one, or the settings' recipe). * * @param array|null $row The source row, or null for the settings' recipe. * @return array The recipe. */ public static function current( ?array $row = null ): array { return self::clean( (array) ( $row['details']['compression'] ?? Settings::get()['compression'] ?? array() ) ); } /** * A key for a recipe, so copies made with another recipe are told apart. * * @param array $recipe The recipe. * @return string SHA-256 of the policy version and the cleaned recipe. */ public static function key( array $recipe ): string { return hash( 'sha256', self::VERSION . '|' . wp_json_encode( self::clean( $recipe ) ) ); } /** * The name of a recipe mode. * * @param string $mode lossless, high or maximum. * @return string The translated name (Lossless for an unknown mode). */ public static function label( string $mode ): string { return array( 'lossless' => __( 'Lossless', 'affstackdev-image-manager' ), 'high' => __( 'High Quality', 'affstackdev-image-manager' ), 'maximum' => __( 'Maximum Savings', 'affstackdev-image-manager' ), )[ $mode ] ?? __( 'Lossless', 'affstackdev-image-manager' ); } /** * Conservative content hints, not a claim to understand the subject of a photograph. * * @param array $info The picture's input record (ImageInput). * @param array $recipe The recipe. * @param string $source The source's address or name. * @return string photo or graphics. */ public static function content( array $info, array $recipe, string $source ): string { if ( 'auto' !== $recipe['content'] ) { return $recipe['content']; } if ( ! empty( $info['alpha'] ) || in_array( $info['mime'], array( 'image/png', 'image/gif' ), true ) || preg_match( '/(?:logo|screen|artwork|diagram|chart|icon|banner|catalog|interface)/i', basename( $source ) ) ) { return 'graphics'; } return 'photo'; } /** * The fidelity a copy gets: lossless for the Lossless mode, graphics and transparent pictures, otherwise the recipe's * mode. * * @param array $recipe The recipe. * @param string $content photo or graphics. * @param bool $alpha Whether the picture is transparent. * @return string lossless, high or maximum. */ public static function fidelity( array $recipe, string $content, bool $alpha ): string { return 'lossless' === $recipe['mode'] || 'graphics' === $content || $alpha ? 'lossless' : $recipe['mode']; } /** * Format-specific encoder settings; these numbers are not equivalent quality scales. * * @param string $format jpeg, webp or avif. * @param string $mode high or maximum. * @return int The encoder's quality setting. */ public static function quality( string $format, string $mode ): int { return ( 'maximum' === $mode ? array( 'jpeg' => 82, 'webp' => 78, 'avif' => 58, ) : array( 'jpeg' => 92, 'webp' => 90, 'avif' => 75, ) )[ $format ] ?? 100; } /** * Whether a manifest copy was made under a policy this release still serves. * * @param array $v The copy. * @return bool True when it was. */ public static function valid_variant( array $v ): bool { if ( Codec::POLICY === ( $v['policy'] ?? '' ) ) { return 'image/webp' === ( $v['output_mime'] ?? 'image/webp' ); } return self::VERSION === ( $v['policy'] ?? '' ) && isset( $v['recipe_key'], $v['verification'], $v['output_mime'] ) && in_array( $v['fidelity'] ?? '', array( 'lossless', 'high', 'maximum' ), true ); } }