WordPressで画像を自動最適化する自作プラグイン

~コピペで導入できる完成形~

できること

  • アップロード時に JPEG/PNG → WebP/AVIF 変換
  • 品質調整(JPEG/WebP/AVIF個別に設定可)
  • 最大サイズ以上は縮小(例:2560px以内)
  • EXIFメタ情報削除(Imagick有効時のみ)
  • 管理画面(設定 → NekoPixel)からGUIで数値変更

導入手順

  1. wp-content/plugins/neko-pixel-optimizer フォルダを作成
  2. 中に neko-pixel-optimizer.php を置く
  3. 下のコードをコピペして保存
  4. 管理画面 → プラグイン一覧 → 「NekoPixel Optimizer」を有効化
  5. 設定 → NekoPixel から数値を調整

完成コード(コピペOK)

<?php
/**
 * Plugin Name: NekoPixel Optimizer
 * Description: アップロード画像を自動最適化(WebP/AVIF変換・品質調整・縮小・EXIF削除)。GUI設定付き完成版。
 * Version: 1.0.0
 * Author: Kagioneko + Chappie
 */

if (!defined('ABSPATH')) exit;

class NekoPixel_Optimizer {
    const OPT_KEY = 'nekopixel_options';

    public function __construct() {
        add_action('admin_init', [$this, 'register_settings']);
        add_action('admin_menu', [$this, 'add_settings_page']);
        add_filter('image_editor_output_format', [$this, 'map_output_format']);
        add_filter('wp_editor_set_quality', [$this, 'set_quality'], 10, 2);
        add_filter('wp_handle_upload', [$this, 'maybe_downscale_original']);
        add_filter('image_make_intermediate_size', [$this, 'strip_exif_after_resize']);
    }

    /* ===== デフォルト値 ===== */
    public static function defaults() {
        return [
            'jpeg_quality' => 82,
            'webp_quality' => 75,
            'avif_quality' => 60,
            'max_width'    => 2560,
            'max_height'   => 2560,
            'strip_exif'   => 1,
            'prefer_avif'  => 1,
        ];
    }
    private function opts() {
        return wp_parse_args(get_option(self::OPT_KEY, []), self::defaults());
    }

    /* ===== 設定ページ ===== */
    public function register_settings() {
        register_setting('nekopixel', self::OPT_KEY, [
            'type' => 'array',
            'sanitize_callback' => [$this, 'sanitize'],
            'default' => self::defaults(),
        ]);
        add_settings_section('nekopixel_main', 'NekoPixel Optimizer 設定', '__return_false', 'nekopixel');

        $fields = [
            ['jpeg_quality','JPEG 品質(1-100)','number'],
            ['webp_quality','WebP 品質(1-100)','number'],
            ['avif_quality','AVIF 品質(1-100)','number'],
            ['max_width','オリジナル最大幅(px, 0で無効)','number'],
            ['max_height','オリジナル最大高さ(px, 0で無効)','number'],
            ['strip_exif','EXIF削除(Imagick使用時のみ)','checkbox'],
            ['prefer_avif','AVIFを優先(対応環境のみ)','checkbox'],
        ];
        foreach ($fields as [$key,$label,$type]) {
            add_settings_field($key,$label,[$this,'render_field'],'nekopixel','nekopixel_main',['key'=>$key,'type'=>$type]);
        }
    }
    public function sanitize($in) {
        $d=self::defaults(); $out=[];
        $out['jpeg_quality']=max(1,min(100,intval($in['jpeg_quality']??$d['jpeg_quality'])));
        $out['webp_quality']=max(1,min(100,intval($in['webp_quality']??$d['webp_quality'])));
        $out['avif_quality']=max(1,min(100,intval($in['avif_quality']??$d['avif_quality'])));
        $out['max_width']=max(0,intval($in['max_width']??$d['max_width']));
        $out['max_height']=max(0,intval($in['max_height']??$d['max_height']));
        $out['strip_exif']=empty($in['strip_exif'])?0:1;
        $out['prefer_avif']=empty($in['prefer_avif'])?0:1;
        return $out;
    }
    public function render_field($args) {
        $o=$this->opts(); $key=$args['key']; $type=$args['type']; $val=$o[$key];
        if ($type==='checkbox') {
            printf("<input type='checkbox' name='%s[%s]' value='1' %s />",
                esc_attr(self::OPT_KEY),esc_attr($key),checked($val,1,false));
        } else {
            printf("<input type='%s' name='%s[%s]' value='%s' style='width:120px' />",
                esc_attr($type),esc_attr(self::OPT_KEY),esc_attr($key),esc_attr($val));
        }
    }
    public function add_settings_page() {
        add_options_page('NekoPixel Optimizer','NekoPixel','manage_options','nekopixel',[$this,'render_settings_page']);
    }
    public function render_settings_page() {
        echo "<div class='wrap'><h1>NekoPixel Optimizer</h1><form method='post' action='options.php'>";
        settings_fields('nekopixel'); do_settings_sections('nekopixel'); submit_button();
        echo "</form></div>";
    }

    /* ===== 実処理 ===== */
    public function map_output_format($formats) {
        $o=$this->opts(); $target=null;
        if ($o['prefer_avif'] && function_exists('imageavif')) $target='image/avif';
        elseif (function_exists('imagewebp')) $target='image/webp';
        elseif (class_exists('Imagick')) {
            $f=@Imagick::queryFormats();
            if (is_array($f)&&in_array('AVIF',$f,true)) $target='image/avif';
            elseif (is_array($f)&&in_array('WEBP',$f,true)) $target='image/webp';
        }
        if ($target) {
            $formats['image/jpeg']=$target;
            $formats['image/png']=$target;
        }
        return $formats;
    }
    public function set_quality($q,$mime) {
        $o=$this->opts();
        if ($mime==='image/avif') return $o['avif_quality'];
        if ($mime==='image/webp') return $o['webp_quality'];
        if ($mime==='image/jpeg') return $o['jpeg_quality'];
        return $q;
    }
    public function maybe_downscale_original($upload) {
        $o=$this->opts();
        if (empty($o['max_width'])&&empty($o['max_height'])) return $upload;
        if (empty($upload['file'])||!file_exists($upload['file'])) return $upload;

        $type=wp_check_filetype($upload['file']);
        if (!in_array($type['type'],['image/jpeg','image/png','image/webp','image/avif'],true)) return $upload;

        $editor=wp_get_image_editor($upload['file']);
        if (is_wp_error($editor)) return $upload;

        $size=$editor->get_size();
        $w=intval($size['width']??0); $h=intval($size['height']??0);
        $mw=intval($o['max_width']); $mh=intval($o['max_height']);
        $need=($mw && $w>$mw)||($mh && $h>$mh);
        if ($need) {
            $editor->resize($mw?:null,$mh?:null,false);
            $editor->set_quality($this->pick_quality($type['type'],$o));
            $editor->save($upload['file']);
        }
        return $upload;
    }
    private function pick_quality($mime,$o) {
        if ($mime==='image/jpeg') return $o['jpeg_quality'];
        if ($mime==='image/webp') return $o['webp_quality'];
        if ($mime==='image/avif') return $o['avif_quality'];
        return 82;
    }
    public function strip_exif_after_resize($resized_path) {
        $o=$this->opts();
        if (!$resized_path||!file_exists($resized_path)) return $resized_path;
        if (empty($o['strip_exif'])||!class_exists('Imagick')) return $resized_path;
        try {
            $img=new Imagick($resized_path);
            $img->stripImage(); $img->writeImage($resized_path);
            $img->clear(); $img->destroy();
        } catch(\Throwable $e){ error_log('[NekoPixel] strip failed: '.$e->getMessage()); }
        return $resized_path;
    }
}

new NekoPixel_Optimizer();

動作確認

  1. 設定 → NekoPixel で品質や最大サイズを調整
  2. 3000×3000px以上の画像をアップ → 2560px以内に縮小される
  3. サムネイルが WebP/AVIF で生成される
  4. EXIF情報が削除される(Imagick有効な場合)

まとめ

  • 新規アップロードの画像を自動で最適化
  • ShortPixel風の動作を、完全自作&無料で実現
  • 設定GUIで簡単にカスタマイズ可能

/

コメント

タイトルとURLをコピーしました