Copied WebP Convertor files from Zed Suite repo to make it free to all users
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
/**
|
||||
* Plugin Name: ZedSuite WebP Converter
|
||||
* Description: Automatically converts uploaded JPEG and PNG images to WebP with 75% compression, adds them to the media library, and deletes the original file.
|
||||
* Version: 0.1.1
|
||||
* Author: Ze'ev Schurmann
|
||||
* License: GPL3 or later
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
add_filter('wp_handle_upload', 'zs_convert_to_webp', 10, 2);
|
||||
|
||||
function zs_convert_to_webp($upload, $context) {
|
||||
$file_path = $upload['file'];
|
||||
$file_type = $upload['type'];
|
||||
|
||||
if (!in_array($file_type, ['image/jpeg', 'image/png'])) {
|
||||
return $upload; // Skip non-JPEG/PNG files
|
||||
}
|
||||
|
||||
$image = ($file_type === 'image/jpeg') ? imagecreatefromjpeg($file_path) : imagecreatefrompng($file_path);
|
||||
if (!$image) {
|
||||
return $upload; // If image creation fails, return original upload
|
||||
}
|
||||
|
||||
$webp_path = preg_replace('/\.(jpe?g|png)$/i', '.webp', $file_path);
|
||||
|
||||
if (imagewebp($image, $webp_path, 75)) {
|
||||
imagedestroy($image);
|
||||
|
||||
// Validate WebP file
|
||||
if (file_exists($webp_path) && getimagesize($webp_path)['mime'] === 'image/webp') {
|
||||
unlink($file_path); // Delete the original file
|
||||
|
||||
// Update media library to point to the WebP file
|
||||
$upload['file'] = $webp_path;
|
||||
$upload['type'] = 'image/webp';
|
||||
$upload['url'] = preg_replace('/\.(jpe?g|png)$/i', '.webp', $upload['url']);
|
||||
} else {
|
||||
unlink($webp_path); // Remove invalid WebP file
|
||||
add_action('admin_notices', function () {
|
||||
echo '<div class="notice notice-error"><p><strong>Error:</strong> Image conversion to WebP failed. The original file has been kept.</p></div>';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return $upload;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
.img-comp-container {
|
||||
position: relative;
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
}
|
||||
.img-comp-img {
|
||||
width: auto;
|
||||
height: 100%;
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
.img-comp-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
width: 50%; /* Default */
|
||||
}
|
||||
/*.img-comp-overlay img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
position: relative;
|
||||
}*/
|
||||
.slider-handle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 24px;
|
||||
background: rgba(255,255,255,0.5);
|
||||
border: 1px solid #999;
|
||||
cursor: ew-resize;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
}
|
||||
.img-comp-labels {
|
||||
display: flex;
|
||||
width: 100%; /* or any fixed width */
|
||||
height: auto; /* example height */
|
||||
}
|
||||
.img-comp-labels-child {
|
||||
flex: 1; /* both boxes take equal space */
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.img-comp-labels-left {
|
||||
text-align: left;
|
||||
}
|
||||
.img-comp-labels-right {
|
||||
text-align: right;
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
jQuery(document).ready(function($){
|
||||
const sliderContainer = $('#zs_webp_test_slider_container');
|
||||
const beforeImg = $('#before_img');
|
||||
const afterImg = $('#after_img');
|
||||
const sliderHandle = $('.slider-handle');
|
||||
const overlay = $('.img-comp-overlay');
|
||||
const sourceInfo = $('#zs_webp_source_info');
|
||||
const labelWebP = $('#webp-label');
|
||||
const labelPNG = $('#png-label');
|
||||
let dragging = false;
|
||||
|
||||
function waitForImagesToLoad(callback) {
|
||||
let loadedCount = 0;
|
||||
|
||||
function checkLoaded() {
|
||||
loadedCount++;
|
||||
if (loadedCount === 2) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
// If already loaded (from cache)
|
||||
if (beforeImg[0].complete) checkLoaded();
|
||||
else beforeImg.on('load', checkLoaded).on('error', function () {
|
||||
console.error("Before image failed to load.");
|
||||
});
|
||||
|
||||
if (afterImg[0].complete) checkLoaded();
|
||||
else afterImg.on('load', checkLoaded).on('error', function () {
|
||||
console.error("After image failed to load.");
|
||||
});
|
||||
}
|
||||
|
||||
//function setOverlayWidth(percent) {
|
||||
// overlay.width(percent + '%');
|
||||
// sliderHandle.css('left', percent + '%');
|
||||
//}
|
||||
function setOverlayWidth(percent) {
|
||||
$('.img-comp-overlay').css('width', percent + '%');
|
||||
$('.slider-handle').css('left', percent + '%');
|
||||
}
|
||||
|
||||
// Then call this function
|
||||
waitForImagesToLoad(function () {
|
||||
$('.img-comp-container').fadeIn();
|
||||
//initializeSlider(); // Your custom slider logic
|
||||
|
||||
// Initialize slider position to 50%
|
||||
setOverlayWidth(50);
|
||||
|
||||
// Draggable handle
|
||||
sliderHandle.on('mousedown touchstart', function(e) {
|
||||
e.preventDefault();
|
||||
dragging = true;
|
||||
});
|
||||
$(document).on('mouseup touchend', function() {
|
||||
dragging = false;
|
||||
});
|
||||
$(document).on('mousemove touchmove', function(e) {
|
||||
if (!dragging) return;
|
||||
let containerOffset = sliderContainer.offset().left;
|
||||
let pageX = e.pageX || e.originalEvent.touches[0].pageX;
|
||||
let pos = pageX - containerOffset;
|
||||
let width = sliderContainer.width();
|
||||
let percent = Math.min(Math.max(pos / width * 100, 0), 100);
|
||||
setOverlayWidth(percent);
|
||||
});
|
||||
|
||||
// Click on any webp sample to load before/after
|
||||
$('#zs_webp_test_images img').click(function(){
|
||||
let basename = $(this).data('basename');
|
||||
$.post(zsWebpTest.ajax_url, {
|
||||
action: 'zs_convert_png_to_webp',
|
||||
nonce: zsWebpTest.nonce,
|
||||
basename: basename,
|
||||
quality: $('#zs_webp_quality_slider').val()
|
||||
}, function(response){
|
||||
if (response.success) {
|
||||
let originalPNG = zsWebpTest.plugin_url + 'samples/' + basename + '.png';
|
||||
let convertedWebP = response.data.webp_url;
|
||||
beforeImg.attr('src', originalPNG);
|
||||
afterImg.attr('src', convertedWebP);
|
||||
labelWebP.html('WebP [' + response.data.webp_size + ' KBytes]');
|
||||
labelPNG.html('PNG [' + response.data.png_size + ' KBytes]');
|
||||
sourceInfo.html('Source: <a href="' + response.data.source_info + '" target="_blank">' + response.data.source_info + '</a>');
|
||||
sliderContainer.show();
|
||||
setOverlayWidth(50);
|
||||
} else {
|
||||
alert(response.data || 'Error loading images');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
https://pixabay.com/photos/business-idea-planning-board-3683781/
|
After Width: | Height: | Size: 700 KiB |
|
@ -0,0 +1 @@
|
|||
https://pixabay.com/photos/mexico-cdmx-reform-chapultepec-7596566/
|
After Width: | Height: | Size: 645 KiB |
|
@ -0,0 +1 @@
|
|||
https://pixabay.com/photos/boat-lake-mountains-snow-italy-6686952/
|
After Width: | Height: | Size: 597 KiB |
|
@ -0,0 +1 @@
|
|||
https://pixabay.com/photos/coffee-phone-paper-business-792113/
|
After Width: | Height: | Size: 394 KiB |
|
@ -0,0 +1 @@
|
|||
https://pixabay.com/photos/chick-ducklings-nature-cute-animal-8160008/
|
After Width: | Height: | Size: 662 KiB |
|
@ -0,0 +1 @@
|
|||
https://pixabay.com/photos/waiting-unhappy-woman-rain-china-9588284/
|
After Width: | Height: | Size: 556 KiB |
|
@ -0,0 +1 @@
|
|||
https://pixabay.com/photos/meeting-brainstorming-business-594091/
|
After Width: | Height: | Size: 434 KiB |
|
@ -0,0 +1 @@
|
|||
https://pixabay.com/photos/coffee-phone-paper-business-792113/
|
After Width: | Height: | Size: 736 KiB |
|
@ -0,0 +1 @@
|
|||
https://pixabay.com/photos/aircraft-pilatus-pc-24-corporate-jet-5611528/
|
After Width: | Height: | Size: 452 KiB |
|
@ -0,0 +1 @@
|
|||
https://pixabay.com/photos/street-japan-city-urban-road-5130030/
|
After Width: | Height: | Size: 713 KiB |
|
@ -0,0 +1,298 @@
|
|||
<?php
|
||||
/**
|
||||
* Plugin Name: ZedSuite WebP Converter
|
||||
* Description: Automatically converts uploaded JPEG and PNG images to WebP with 75% compression (or you can set your own compression), adds them to the media library, and deletes the original file.
|
||||
* Version: 0.2.1
|
||||
* Author: Ze'ev Schurmann
|
||||
* License: GPL3 or later
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
if (is_admin()) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
|
||||
add_filter('wp_handle_upload', 'zs_convert_to_webp', 10, 2);
|
||||
|
||||
function zs_convert_to_webp($upload, $context) {
|
||||
$file_path = $upload['file'];
|
||||
$file_type = $upload['type'];
|
||||
|
||||
if (!in_array($file_type, ['image/jpeg', 'image/png'])) {
|
||||
return $upload; // Skip non-JPEG/PNG files
|
||||
}
|
||||
|
||||
if ($file_type === 'image/jpeg') {
|
||||
$image = imagecreatefromjpeg($file_path);
|
||||
} else {
|
||||
$image = imagecreatefrompng($file_path);
|
||||
|
||||
// Force palette-based PNGs to truecolor (indexed → RGB)
|
||||
if ($image && imageistruecolor($image) === false) {
|
||||
$truecolor = imagecreatetruecolor(imagesx($image), imagesy($image));
|
||||
imagecopy($truecolor, $image, 0, 0, 0, 0, imagesx($image), imagesy($image));
|
||||
imagedestroy($image);
|
||||
$image = $truecolor;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$image) {
|
||||
return $upload; // If image creation fails, return original upload
|
||||
}
|
||||
|
||||
$webp_path = preg_replace('/\.(jpe?g|png)$/i', '.webp', $file_path);
|
||||
|
||||
if ($file_type === 'image/png') {
|
||||
imagealphablending($image, false);
|
||||
imagesavealpha($image, true);
|
||||
}
|
||||
|
||||
// Get quality setting from wp_options
|
||||
$quality = get_option('zedsuite_webpconv_quality', 75);
|
||||
|
||||
// If quality is 101 then save with lossless compression
|
||||
$webp_saved = ($quality == 101)
|
||||
? imagewebp($image, $webp_path, true)
|
||||
: imagewebp($image, $webp_path, $quality);
|
||||
|
||||
if ($webp_saved) {
|
||||
imagedestroy($image);
|
||||
|
||||
// Validate WebP file
|
||||
if (file_exists($webp_path) && getimagesize($webp_path)['mime'] === 'image/webp') {
|
||||
unlink($file_path); // Delete the original file
|
||||
|
||||
// Update media library to point to the WebP file
|
||||
$upload['file'] = $webp_path;
|
||||
$upload['type'] = 'image/webp';
|
||||
$upload['url'] = preg_replace('/\.(jpe?g|png)$/i', '.webp', $upload['url']);
|
||||
} else {
|
||||
unlink($webp_path); // Remove invalid WebP file
|
||||
add_action('admin_notices', function () {
|
||||
echo '<div class="notice notice-error"><p><strong>Error:</strong> Image conversion to WebP failed. The original file has been kept.</p></div>';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return $upload;
|
||||
}
|
||||
|
||||
add_action('admin_menu', 'zs_webp_settings_menu');
|
||||
|
||||
function zs_webp_settings_menu() {
|
||||
add_submenu_page(
|
||||
'upload.php', // Parent slug (Media menu)
|
||||
'ZS WebP Settings', // Page title
|
||||
'ZS WebP Settings', // Menu title
|
||||
'manage_options', // Capability (admin only)
|
||||
'zs-webp-settings', // Menu slug
|
||||
'zs_webp_settings_page' // Callback to render page
|
||||
);
|
||||
}
|
||||
|
||||
function zs_webp_settings_page() {
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_die('Unauthorized user');
|
||||
}
|
||||
|
||||
$option_key = 'zedsuite_webpconv_quality';
|
||||
$default_quality = 75;
|
||||
|
||||
// Folder paths
|
||||
$plugin_dir = plugin_dir_path(__FILE__);
|
||||
$webp_dir = $plugin_dir . 'samples/'; // Your 10 images here
|
||||
|
||||
// Scan webp files
|
||||
$webp_files = glob($webp_dir . '*.webp');
|
||||
|
||||
// Create option on first load
|
||||
if (get_option($option_key) === false) {
|
||||
add_option($option_key, $default_quality);
|
||||
}
|
||||
|
||||
// Handle form actions
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (isset($_POST['zs_webp_reset'])) {
|
||||
update_option($option_key, $default_quality);
|
||||
} elseif (isset($_POST['zs_webp_save']) && isset($_POST['webp_quality'])) {
|
||||
$q = intval($_POST['webp_quality']);
|
||||
if ($q >= 0 && $q <= 101) {
|
||||
update_option($option_key, $q);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$quality = intval(get_option($option_key));
|
||||
$plugin_data = get_plugin_data(__FILE__);
|
||||
$version = esc_html($plugin_data['Version']);
|
||||
?>
|
||||
|
||||
<div class="wrap">
|
||||
<h1>ZS WebP Converter Settings</h1>
|
||||
<p><strong>Version:</strong> <?php echo $version; ?></p>
|
||||
|
||||
<p>This plugin automatically converts JPEG and PNG uploads to WebP format. You can adjust the quality of the output image below. Use a lower value for smaller files, or 101 for lossless compression.</p>
|
||||
|
||||
<form method="post">
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="webp_quality">WebP Quality</label>
|
||||
</th>
|
||||
<td>
|
||||
<input style="width: 300px;" type="range" min="0" max="101" id="webp_quality" name="webp_quality" value="<?php echo $quality; ?>" oninput="this.nextElementSibling.value = this.value">
|
||||
<output><?php echo $quality; ?></output>
|
||||
<p class="description">0 = lowest quality, 101 = lossless</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div style="display: flex; justify-content: flex-start; gap: 10px;">
|
||||
<button type="submit" name="zs_webp_reset" class="button button-secondary">Reset</button>
|
||||
<button type="submit" name="zs_webp_save" class="button button-primary">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<h2>Test</h2>
|
||||
<div id="zs_webp_test_images" style="display:flex; flex-wrap: wrap; gap: 10px;">
|
||||
<?php foreach ($webp_files as $webp_file):
|
||||
$basename = basename($webp_file, '.webp');
|
||||
$url = plugin_dir_url(__FILE__) . "samples/$basename.webp";
|
||||
?>
|
||||
<img src="<?php echo esc_url($url); ?>" data-basename="<?php echo esc_attr($basename); ?>" style="cursor:pointer; width: 100px; height: auto; border: 1px solid #ccc;" />
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
if (file_exists($png_path)) {
|
||||
$sizeBytes = filesize($png_path); // Size in bytes
|
||||
$PNGsizeKB = round($sizeBytes / 1024, 2); // Convert to KB, 2 decimal places
|
||||
}
|
||||
|
||||
if (file_exists($webp_tmp_path)) {
|
||||
$sizeBytes = filesize($webp_tmp_path); // Size in bytes
|
||||
$WebPsizeKB = round($sizeBytes / 1024, 2); // Convert to KB, 2 decimal places
|
||||
}
|
||||
?>
|
||||
|
||||
<div id="zs_webp_test_slider_container" style="margin-top: 20px; display:none;">
|
||||
<h3>Comparison</h3>
|
||||
<div class="img-comp-labels" style="max-width: 600px; margin: auto;">
|
||||
<div id="webp-label" class="img-comp-labels-child img-comp-labels-left">WebP [<?php echo esc_attr($WebPsizeKB); ?> KBytes]</div>
|
||||
<div id="png-label" class="img-comp-labels-child img-comp-labels-right">PNG [<?php echo esc_attr($PNGsizeKB); ?> KBytes]</div>
|
||||
</div>
|
||||
<div id="zs_webp_slider" class="img-comp-container" style="max-width: 600px; margin: auto;">
|
||||
<img id="before_img" src="" alt="Before Image" class="img-comp-img" />
|
||||
<div class="img-comp-overlay">
|
||||
<img id="after_img" src="" alt="After Image" class="img-comp-img" />
|
||||
</div>
|
||||
<div class="slider-handle">⇄</div>
|
||||
</div>
|
||||
<p id="zs_webp_source_info"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#wpfooter {
|
||||
position: relative !important;
|
||||
margin-top: 800px !important; /* Adjust height as needed */
|
||||
}
|
||||
</style>
|
||||
|
||||
<?php
|
||||
}
|
||||
|
||||
add_action('admin_enqueue_scripts', 'zs_enqueue_webp_test_scripts');
|
||||
|
||||
function zs_enqueue_webp_test_scripts($hook) {
|
||||
if ($hook !== 'media_page_zs-webp-settings') return;
|
||||
|
||||
wp_enqueue_style('zs-webp-test-style', plugin_dir_url(__FILE__) . 'css/webp-test.css', [], '1.0');
|
||||
wp_enqueue_script('zs-webp-test-script', plugin_dir_url(__FILE__) . 'js/webp-test.js', ['jquery'], '1.0', true);
|
||||
|
||||
// Pass ajax URL and nonce for PNG conversion
|
||||
wp_localize_script('zs-webp-test-script', 'zsWebpTest', [
|
||||
'ajax_url' => admin_url('admin-ajax.php'),
|
||||
'nonce' => wp_create_nonce('zs_webp_test_nonce'),
|
||||
'quality' => intval(get_option('zedsuite_webpconv_quality', 75)),
|
||||
'plugin_url' => plugin_dir_url(__FILE__),
|
||||
]);
|
||||
}
|
||||
|
||||
add_action('wp_ajax_zs_convert_png_to_webp', 'zs_ajax_convert_png_to_webp');
|
||||
|
||||
function zs_ajax_convert_png_to_webp() {
|
||||
check_ajax_referer('zs_webp_test_nonce', 'nonce');
|
||||
|
||||
$basename = sanitize_file_name($_POST['basename'] ?? '');
|
||||
#$quality = intval($_POST['quality'] ?? 75);
|
||||
$quality = get_option('zedsuite_webpconv_quality', 75);
|
||||
|
||||
if ($basename === '') {
|
||||
wp_send_json_error('Invalid basename');
|
||||
}
|
||||
|
||||
$plugin_dir = plugin_dir_path(__FILE__);
|
||||
$samples_dir = $plugin_dir . 'samples/';
|
||||
$temp_dir = $plugin_dir . 'temp/';
|
||||
|
||||
$png_path = $samples_dir . $basename . '.png';
|
||||
$webp_tmp_path = $temp_dir . $basename . '_temp.webp';
|
||||
|
||||
if (!file_exists($png_path)) {
|
||||
wp_send_json_error('PNG file missing');
|
||||
}
|
||||
|
||||
$image = imagecreatefrompng($png_path);
|
||||
if (!$image) {
|
||||
wp_send_json_error('Could not load PNG');
|
||||
}
|
||||
|
||||
if ($quality === 101) {
|
||||
$result = imagewebp($image, $webp_tmp_path, 100);
|
||||
// Set lossless flag
|
||||
// PHP GD does not natively support lossless, but imagick can.
|
||||
// For simplicity, treat 101 as 100 quality here.
|
||||
} else {
|
||||
$result = imagewebp($image, $webp_tmp_path, $quality);
|
||||
}
|
||||
imagedestroy($image);
|
||||
|
||||
if (!$result) {
|
||||
wp_send_json_error('Conversion failed');
|
||||
}
|
||||
|
||||
$webp_url = plugin_dir_url(__FILE__) . "temp/{$basename}_temp.webp";
|
||||
|
||||
$png_url = plugin_dir_url(__FILE__) . "samples/{$basename}.png";
|
||||
|
||||
// Read NFO file content
|
||||
$nfo_path = $samples_dir . $basename . '.nfo';
|
||||
$source_info = '';
|
||||
if (file_exists($nfo_path)) {
|
||||
$source_info = trim(file_get_contents($nfo_path));
|
||||
}
|
||||
|
||||
if (file_exists($png_path)) {
|
||||
$sizeBytes = filesize($png_path); // Size in bytes
|
||||
$PNGsizeKB = round($sizeBytes / 1024, 2); // Convert to KB, 2 decimal places
|
||||
}
|
||||
|
||||
if (file_exists($webp_tmp_path)) {
|
||||
$sizeBytes = filesize($webp_tmp_path); // Size in bytes
|
||||
$WebPsizeKB = round($sizeBytes / 1024, 2); // Convert to KB, 2 decimal places
|
||||
}
|
||||
|
||||
wp_send_json_success([
|
||||
'webp_url' => $webp_url,
|
||||
'webp_size' => $WebPsizeKB,
|
||||
'png_url' => $png_url,
|
||||
'png_size' => $PNGsizeKB,
|
||||
'source_info' => $source_info,
|
||||
]);
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
.img-comp-container {
|
||||
position: relative;
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
}
|
||||
.img-comp-img {
|
||||
width: auto;
|
||||
height: 100%;
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
.img-comp-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
width: 50%; /* Default */
|
||||
}
|
||||
/*.img-comp-overlay img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
position: relative;
|
||||
}*/
|
||||
.slider-handle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 24px;
|
||||
background: rgba(255,255,255,0.5);
|
||||
border: 1px solid #999;
|
||||
cursor: ew-resize;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
}
|
||||
.img-comp-labels {
|
||||
display: flex;
|
||||
width: 100%; /* or any fixed width */
|
||||
height: auto; /* example height */
|
||||
}
|
||||
.img-comp-labels-child {
|
||||
flex: 1; /* both boxes take equal space */
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.img-comp-labels-left {
|
||||
text-align: left;
|
||||
}
|
||||
.img-comp-labels-right {
|
||||
text-align: right;
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
jQuery(document).ready(function($){
|
||||
const sliderContainer = $('#zs_webp_test_slider_container');
|
||||
const beforeImg = $('#before_img');
|
||||
const afterImg = $('#after_img');
|
||||
const sliderHandle = $('.slider-handle');
|
||||
const overlay = $('.img-comp-overlay');
|
||||
const sourceInfo = $('#zs_webp_source_info');
|
||||
const labelWebP = $('#webp-label');
|
||||
const labelPNG = $('#png-label');
|
||||
let dragging = false;
|
||||
|
||||
function waitForImagesToLoad(callback) {
|
||||
let loadedCount = 0;
|
||||
|
||||
function checkLoaded() {
|
||||
loadedCount++;
|
||||
if (loadedCount === 2) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
// If already loaded (from cache)
|
||||
if (beforeImg[0].complete) checkLoaded();
|
||||
else beforeImg.on('load', checkLoaded).on('error', function () {
|
||||
console.error("Before image failed to load.");
|
||||
});
|
||||
|
||||
if (afterImg[0].complete) checkLoaded();
|
||||
else afterImg.on('load', checkLoaded).on('error', function () {
|
||||
console.error("After image failed to load.");
|
||||
});
|
||||
}
|
||||
|
||||
//function setOverlayWidth(percent) {
|
||||
// overlay.width(percent + '%');
|
||||
// sliderHandle.css('left', percent + '%');
|
||||
//}
|
||||
function setOverlayWidth(percent) {
|
||||
$('.img-comp-overlay').css('width', percent + '%');
|
||||
$('.slider-handle').css('left', percent + '%');
|
||||
}
|
||||
|
||||
// Then call this function
|
||||
waitForImagesToLoad(function () {
|
||||
$('.img-comp-container').fadeIn();
|
||||
//initializeSlider(); // Your custom slider logic
|
||||
|
||||
// Initialize slider position to 50%
|
||||
setOverlayWidth(50);
|
||||
|
||||
// Draggable handle
|
||||
sliderHandle.on('mousedown touchstart', function(e) {
|
||||
e.preventDefault();
|
||||
dragging = true;
|
||||
});
|
||||
$(document).on('mouseup touchend', function() {
|
||||
dragging = false;
|
||||
});
|
||||
$(document).on('mousemove touchmove', function(e) {
|
||||
if (!dragging) return;
|
||||
let containerOffset = sliderContainer.offset().left;
|
||||
let pageX = e.pageX || e.originalEvent.touches[0].pageX;
|
||||
let pos = pageX - containerOffset;
|
||||
let width = sliderContainer.width();
|
||||
let percent = Math.min(Math.max(pos / width * 100, 0), 100);
|
||||
setOverlayWidth(percent);
|
||||
});
|
||||
|
||||
// Click on any webp sample to load before/after
|
||||
$('#zs_webp_test_images img').click(function(){
|
||||
let basename = $(this).data('basename');
|
||||
$.post(zsWebpTest.ajax_url, {
|
||||
action: 'zs_convert_png_to_webp',
|
||||
nonce: zsWebpTest.nonce,
|
||||
basename: basename,
|
||||
quality: $('#zs_webp_quality_slider').val()
|
||||
}, function(response){
|
||||
if (response.success) {
|
||||
let originalPNG = zsWebpTest.plugin_url + 'samples/' + basename + '.png';
|
||||
let convertedWebP = response.data.webp_url;
|
||||
beforeImg.attr('src', originalPNG);
|
||||
afterImg.attr('src', convertedWebP);
|
||||
labelWebP.html('WebP [' + response.data.webp_size + ' KBytes]');
|
||||
labelPNG.html('PNG [' + response.data.png_size + ' KBytes]');
|
||||
sourceInfo.html('Source: <a href="' + response.data.source_info + '" target="_blank">' + response.data.source_info + '</a>');
|
||||
sliderContainer.show();
|
||||
setOverlayWidth(50);
|
||||
} else {
|
||||
alert(response.data || 'Error loading images');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
https://pixabay.com/photos/business-idea-planning-board-3683781/
|
After Width: | Height: | Size: 700 KiB |
|
@ -0,0 +1 @@
|
|||
https://pixabay.com/photos/mexico-cdmx-reform-chapultepec-7596566/
|
After Width: | Height: | Size: 645 KiB |
|
@ -0,0 +1 @@
|
|||
https://pixabay.com/photos/boat-lake-mountains-snow-italy-6686952/
|
After Width: | Height: | Size: 597 KiB |
|
@ -0,0 +1 @@
|
|||
https://pixabay.com/photos/coffee-phone-paper-business-792113/
|
After Width: | Height: | Size: 394 KiB |
|
@ -0,0 +1 @@
|
|||
https://pixabay.com/photos/chick-ducklings-nature-cute-animal-8160008/
|
After Width: | Height: | Size: 662 KiB |
|
@ -0,0 +1 @@
|
|||
https://pixabay.com/photos/waiting-unhappy-woman-rain-china-9588284/
|
After Width: | Height: | Size: 556 KiB |
|
@ -0,0 +1 @@
|
|||
https://pixabay.com/photos/meeting-brainstorming-business-594091/
|
After Width: | Height: | Size: 434 KiB |
|
@ -0,0 +1 @@
|
|||
https://pixabay.com/photos/coffee-phone-paper-business-792113/
|
After Width: | Height: | Size: 736 KiB |
|
@ -0,0 +1 @@
|
|||
https://pixabay.com/photos/aircraft-pilatus-pc-24-corporate-jet-5611528/
|
After Width: | Height: | Size: 452 KiB |
|
@ -0,0 +1 @@
|
|||
https://pixabay.com/photos/street-japan-city-urban-road-5130030/
|
After Width: | Height: | Size: 713 KiB |
|
@ -0,0 +1,298 @@
|
|||
<?php
|
||||
/**
|
||||
* Plugin Name: ZedSuite WebP Converter
|
||||
* Description: Automatically converts uploaded JPEG and PNG images to WebP with 75% compression (or you can set your own compression), adds them to the media library, and deletes the original file.
|
||||
* Version: 0.2
|
||||
* Author: Ze'ev Schurmann
|
||||
* License: GPL3 or later
|
||||
*/
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
if (is_admin()) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
|
||||
add_filter('wp_handle_upload', 'zs_convert_to_webp', 10, 2);
|
||||
|
||||
function zs_convert_to_webp($upload, $context) {
|
||||
$file_path = $upload['file'];
|
||||
$file_type = $upload['type'];
|
||||
|
||||
if (!in_array($file_type, ['image/jpeg', 'image/png'])) {
|
||||
return $upload; // Skip non-JPEG/PNG files
|
||||
}
|
||||
|
||||
if ($file_type === 'image/jpeg') {
|
||||
$image = imagecreatefromjpeg($file_path);
|
||||
} else {
|
||||
$image = imagecreatefrompng($file_path);
|
||||
|
||||
// Force palette-based PNGs to truecolor (indexed → RGB)
|
||||
if ($image && imageistruecolor($image) === false) {
|
||||
$truecolor = imagecreatetruecolor(imagesx($image), imagesy($image));
|
||||
imagecopy($truecolor, $image, 0, 0, 0, 0, imagesx($image), imagesy($image));
|
||||
imagedestroy($image);
|
||||
$image = $truecolor;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$image) {
|
||||
return $upload; // If image creation fails, return original upload
|
||||
}
|
||||
|
||||
$webp_path = preg_replace('/\.(jpe?g|png)$/i', '.webp', $file_path);
|
||||
|
||||
if ($file_type === 'image/png') {
|
||||
imagealphablending($image, false);
|
||||
imagesavealpha($image, true);
|
||||
}
|
||||
|
||||
// Get quality setting from wp_options
|
||||
$quality = get_option('zedsuite_webpconv_quality', 75);
|
||||
|
||||
// If quality is 101 then save with lossless compression
|
||||
$webp_saved = ($quality == 101)
|
||||
? imagewebp($image, $webp_path, true)
|
||||
: imagewebp($image, $webp_path, $quality);
|
||||
|
||||
if ($webp_saved) {
|
||||
imagedestroy($image);
|
||||
|
||||
// Validate WebP file
|
||||
if (file_exists($webp_path) && getimagesize($webp_path)['mime'] === 'image/webp') {
|
||||
unlink($file_path); // Delete the original file
|
||||
|
||||
// Update media library to point to the WebP file
|
||||
$upload['file'] = $webp_path;
|
||||
$upload['type'] = 'image/webp';
|
||||
$upload['url'] = preg_replace('/\.(jpe?g|png)$/i', '.webp', $upload['url']);
|
||||
} else {
|
||||
unlink($webp_path); // Remove invalid WebP file
|
||||
add_action('admin_notices', function () {
|
||||
echo '<div class="notice notice-error"><p><strong>Error:</strong> Image conversion to WebP failed. The original file has been kept.</p></div>';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return $upload;
|
||||
}
|
||||
|
||||
add_action('admin_menu', 'zs_webp_settings_menu');
|
||||
|
||||
function zs_webp_settings_menu() {
|
||||
add_submenu_page(
|
||||
'upload.php', // Parent slug (Media menu)
|
||||
'ZS WebP Settings', // Page title
|
||||
'ZS WebP Settings', // Menu title
|
||||
'manage_options', // Capability (admin only)
|
||||
'zs-webp-settings', // Menu slug
|
||||
'zs_webp_settings_page' // Callback to render page
|
||||
);
|
||||
}
|
||||
|
||||
function zs_webp_settings_page() {
|
||||
if (!current_user_can('manage_options')) {
|
||||
wp_die('Unauthorized user');
|
||||
}
|
||||
|
||||
$option_key = 'zedsuite_webpconv_quality';
|
||||
$default_quality = 75;
|
||||
|
||||
// Folder paths
|
||||
$plugin_dir = plugin_dir_path(__FILE__);
|
||||
$webp_dir = $plugin_dir . 'samples/'; // Your 10 images here
|
||||
|
||||
// Scan webp files
|
||||
$webp_files = glob($webp_dir . '*.webp');
|
||||
|
||||
// Create option on first load
|
||||
if (get_option($option_key) === false) {
|
||||
add_option($option_key, $default_quality);
|
||||
}
|
||||
|
||||
// Handle form actions
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (isset($_POST['zs_webp_reset'])) {
|
||||
update_option($option_key, $default_quality);
|
||||
} elseif (isset($_POST['zs_webp_save']) && isset($_POST['webp_quality'])) {
|
||||
$q = intval($_POST['webp_quality']);
|
||||
if ($q >= 0 && $q <= 101) {
|
||||
update_option($option_key, $q);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$quality = intval(get_option($option_key));
|
||||
$plugin_data = get_plugin_data(__FILE__);
|
||||
$version = esc_html($plugin_data['Version']);
|
||||
?>
|
||||
|
||||
<div class="wrap">
|
||||
<h1>ZS WebP Converter Settings</h1>
|
||||
<p><strong>Version:</strong> <?php echo $version; ?></p>
|
||||
|
||||
<p>This plugin automatically converts JPEG and PNG uploads to WebP format. You can adjust the quality of the output image below. Use a lower value for smaller files, or 101 for lossless compression.</p>
|
||||
|
||||
<form method="post">
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="webp_quality">WebP Quality</label>
|
||||
</th>
|
||||
<td>
|
||||
<input style="width: 300px;" type="range" min="0" max="101" id="webp_quality" name="webp_quality" value="<?php echo $quality; ?>" oninput="this.nextElementSibling.value = this.value">
|
||||
<output><?php echo $quality; ?></output>
|
||||
<p class="description">0 = lowest quality, 101 = lossless</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div style="display: flex; justify-content: flex-start; gap: 10px;">
|
||||
<button type="submit" name="zs_webp_reset" class="button button-secondary">Reset</button>
|
||||
<button type="submit" name="zs_webp_save" class="button button-primary">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<h2>Test</h2>
|
||||
<div id="zs_webp_test_images" style="display:flex; flex-wrap: wrap; gap: 10px;">
|
||||
<?php foreach ($webp_files as $webp_file):
|
||||
$basename = basename($webp_file, '.webp');
|
||||
$url = plugin_dir_url(__FILE__) . "samples/$basename.webp";
|
||||
?>
|
||||
<img src="<?php echo esc_url($url); ?>" data-basename="<?php echo esc_attr($basename); ?>" style="cursor:pointer; width: 100px; height: auto; border: 1px solid #ccc;" />
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
if (file_exists($png_path)) {
|
||||
$sizeBytes = filesize($png_path); // Size in bytes
|
||||
$PNGsizeKB = round($sizeBytes / 1024, 2); // Convert to KB, 2 decimal places
|
||||
}
|
||||
|
||||
if (file_exists($webp_tmp_path)) {
|
||||
$sizeBytes = filesize($webp_tmp_path); // Size in bytes
|
||||
$WebPsizeKB = round($sizeBytes / 1024, 2); // Convert to KB, 2 decimal places
|
||||
}
|
||||
?>
|
||||
|
||||
<div id="zs_webp_test_slider_container" style="margin-top: 20px; display:none;">
|
||||
<h3>Comparison</h3>
|
||||
<div class="img-comp-labels" style="max-width: 600px; margin: auto;">
|
||||
<div id="webp-label" class="img-comp-labels-child img-comp-labels-left">WebP [<?php echo esc_attr($WebPsizeKB); ?> KBytes]</div>
|
||||
<div id="png-label" class="img-comp-labels-child img-comp-labels-right">PNG [<?php echo esc_attr($PNGsizeKB); ?> KBytes]</div>
|
||||
</div>
|
||||
<div id="zs_webp_slider" class="img-comp-container" style="max-width: 600px; margin: auto;">
|
||||
<img id="before_img" src="" alt="Before Image" class="img-comp-img" />
|
||||
<div class="img-comp-overlay">
|
||||
<img id="after_img" src="" alt="After Image" class="img-comp-img" />
|
||||
</div>
|
||||
<div class="slider-handle">⇄</div>
|
||||
</div>
|
||||
<p id="zs_webp_source_info"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#wpfooter {
|
||||
position: relative !important;
|
||||
margin-top: 800px !important; /* Adjust height as needed */
|
||||
}
|
||||
</style>
|
||||
|
||||
<?php
|
||||
}
|
||||
|
||||
add_action('admin_enqueue_scripts', 'zs_enqueue_webp_test_scripts');
|
||||
|
||||
function zs_enqueue_webp_test_scripts($hook) {
|
||||
if ($hook !== 'media_page_zs-webp-settings') return;
|
||||
|
||||
wp_enqueue_style('zs-webp-test-style', plugin_dir_url(__FILE__) . 'css/webp-test.css', [], '1.0');
|
||||
wp_enqueue_script('zs-webp-test-script', plugin_dir_url(__FILE__) . 'js/webp-test.js', ['jquery'], '1.0', true);
|
||||
|
||||
// Pass ajax URL and nonce for PNG conversion
|
||||
wp_localize_script('zs-webp-test-script', 'zsWebpTest', [
|
||||
'ajax_url' => admin_url('admin-ajax.php'),
|
||||
'nonce' => wp_create_nonce('zs_webp_test_nonce'),
|
||||
'quality' => intval(get_option('zedsuite_webpconv_quality', 75)),
|
||||
'plugin_url' => plugin_dir_url(__FILE__),
|
||||
]);
|
||||
}
|
||||
|
||||
add_action('wp_ajax_zs_convert_png_to_webp', 'zs_ajax_convert_png_to_webp');
|
||||
|
||||
function zs_ajax_convert_png_to_webp() {
|
||||
check_ajax_referer('zs_webp_test_nonce', 'nonce');
|
||||
|
||||
$basename = sanitize_file_name($_POST['basename'] ?? '');
|
||||
#$quality = intval($_POST['quality'] ?? 75);
|
||||
$quality = get_option('zedsuite_webpconv_quality', 75);
|
||||
|
||||
if ($basename === '') {
|
||||
wp_send_json_error('Invalid basename');
|
||||
}
|
||||
|
||||
$plugin_dir = plugin_dir_path(__FILE__);
|
||||
$samples_dir = $plugin_dir . 'samples/';
|
||||
$temp_dir = $plugin_dir . 'temp/';
|
||||
|
||||
$png_path = $samples_dir . $basename . '.png';
|
||||
$webp_tmp_path = $temp_dir . $basename . '_temp.webp';
|
||||
|
||||
if (!file_exists($png_path)) {
|
||||
wp_send_json_error('PNG file missing');
|
||||
}
|
||||
|
||||
$image = imagecreatefrompng($png_path);
|
||||
if (!$image) {
|
||||
wp_send_json_error('Could not load PNG');
|
||||
}
|
||||
|
||||
if ($quality === 101) {
|
||||
$result = imagewebp($image, $webp_tmp_path, 100);
|
||||
// Set lossless flag
|
||||
// PHP GD does not natively support lossless, but imagick can.
|
||||
// For simplicity, treat 101 as 100 quality here.
|
||||
} else {
|
||||
$result = imagewebp($image, $webp_tmp_path, $quality);
|
||||
}
|
||||
imagedestroy($image);
|
||||
|
||||
if (!$result) {
|
||||
wp_send_json_error('Conversion failed');
|
||||
}
|
||||
|
||||
$webp_url = plugin_dir_url(__FILE__) . "temp/{$basename}_temp.webp";
|
||||
|
||||
$png_url = plugin_dir_url(__FILE__) . "samples/{$basename}.png";
|
||||
|
||||
// Read NFO file content
|
||||
$nfo_path = $samples_dir . $basename . '.nfo';
|
||||
$source_info = '';
|
||||
if (file_exists($nfo_path)) {
|
||||
$source_info = trim(file_get_contents($nfo_path));
|
||||
}
|
||||
|
||||
if (file_exists($png_path)) {
|
||||
$sizeBytes = filesize($png_path); // Size in bytes
|
||||
$PNGsizeKB = round($sizeBytes / 1024, 2); // Convert to KB, 2 decimal places
|
||||
}
|
||||
|
||||
if (file_exists($webp_tmp_path)) {
|
||||
$sizeBytes = filesize($webp_tmp_path); // Size in bytes
|
||||
$WebPsizeKB = round($sizeBytes / 1024, 2); // Convert to KB, 2 decimal places
|
||||
}
|
||||
|
||||
wp_send_json_success([
|
||||
'webp_url' => $webp_url,
|
||||
'webp_size' => $WebPsizeKB,
|
||||
'png_url' => $png_url,
|
||||
'png_size' => $PNGsizeKB,
|
||||
'source_info' => $source_info,
|
||||
]);
|
||||
}
|
||||
|