GIMP Plugin that allows exporting as JPEG using the Google Guetzli
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
from gimpfu import * | |
from os import remove, name | |
from os.path import basename | |
from tempfile import NamedTemporaryFile | |
from subprocess import check_output, CalledProcessError, STDOUT | |
# Export with Google Guetzli Perceptual JPEG encoder | |
# (copyLEFT) 2017 Fabricio Biazzotto. No rights reserved. | |
# based on Andrey "Efenstor" Pivovarov MozJPEG export plugin | |
# IMPORTANT!!! | |
# ------------ | |
# You must edit the "guetzli_path" variable below to point it to your guetzli | |
# location. This plugin REQUIRES the "Guetzli" utility from Google. | |
# You can download Gueztli from https://github.com/google/guetzli/releases | |
guetzli_path = 'guetzli' | |
# INSTALLATION | |
# ------------ | |
# Linux: | |
# 1. cp export_guetzli.py ~/.gimp-2.8/plug-ins | |
# 2. chmod +x export_guetzli.py | |
# Windows: | |
# 1. Remove Windows | |
# 2. Install Linux | |
# USAGE | |
# ----- | |
# Method 1 (preferred): | |
# 1. File > Export As... | |
# 2. Enter the output file name but specify .gtz as extension instead of .jpg | |
# 3. The resulting file will still be called <filename>.jpg | |
# NOTE: if the outfile file already exists it is overwritten without warning! | |
# Method 2: | |
# 1. File > Export As... | |
# 2. Open the "Select file type" list below | |
# 3. Select "JPEG image (Google Guetzli)" | |
# 4. On the "Extension mismatch" warning click "Save" | |
# Method 3: | |
# Rename/remove the original JPEG plugin (file-jpeg) | |
# Linux: | |
# 1. cd /usr/lib/gimp/2.0/plug-ins | |
# 2. sudo mv file-jpeg .file-jpeg | |
# Windows: | |
# 1. Remove Windows | |
# 2. Install Linux | |
# NOTES ON PRE-PROCESSING | |
# ----------------------- | |
# Selective smoothing: Reduces file size at the cost of some tolerable image | |
# quality degradation, sometimes also reducing perceptual blockiness. Values | |
# of 0-50 are usually enough for the most cases. | |
# Ringing reduction: Smoothens the image slightly, effectively suppressing | |
# ringing at the cost of some sharpness loss. The value of 1 is quite enough | |
# for the most cases. | |
# HISTORY | |
# ------- | |
# v0.1 - Initial release | |
# If guetzli_path is not set, it assumes it's somewhere on path | |
if name == 'nt': | |
guetzli_path = 'guetzli.exe' | |
else: | |
guetzli_path = 'guetzli' | |
def guetzli(img, drawable, filename, raw_filename, unknown, quality, memlimit, | |
nomemlimit, preproc1, preproc2): | |
# Initialization | |
gimp.progress_init('Saving ' + raw_filename) | |
# Prepare the temporary file | |
s_tmpfile = NamedTemporaryFile(suffix='.png', delete=False) | |
s_tmpfile_name = s_tmpfile.name | |
s_tmpfile.close() | |
# Pre-processing | |
proc_img = pdb.gimp_image_duplicate(img) | |
# proc_drawable = proc_img.active_layer | |
proc_drawable = proc_img.flatten() | |
if (preproc1 > 0): | |
radius = round((preproc1 / 100.0) * 10.0) | |
pdb.plug_in_sel_gauss(proc_img, proc_drawable, radius, preproc1) | |
if (preproc2 > 0): | |
pdb.plug_in_gauss(proc_img, proc_drawable, preproc2, preproc2, 0) | |
# Save | |
pdb.file_png_save(proc_img, proc_drawable, s_tmpfile_name, | |
basename(s_tmpfile_name), 0, 0, 0, 0, 0, 0, 0) | |
# Close the processed image | |
gimp.delete(proc_img) | |
# Change .gtz extension to .jpg (if needed) | |
last_dot = str.rfind(filename, '.') | |
ext = filename[last_dot:] | |
if (ext == '.gtz'): | |
filename = filename[:last_dot] + '.jpg' | |
# Convert | |
args = [] | |
args.append(guetzli_path) | |
if (int(quality) != 95): | |
args.append('--quality') | |
args.append(str(int(quality))) | |
if (nomemlimit == 1): | |
args.append('--nomemlimit') | |
elif (int(memlimit) != 6000): | |
args.append('--memlimit') | |
args.append(str(int(memlimit))) | |
args.append(s_tmpfile_name) | |
args.append(filename) | |
gimp.progress_init('Saving ' + basename(filename)) | |
try: | |
output = check_output(args, stderr=STDOUT) | |
except CalledProcessError as err: | |
gimp.progress_init('File' + basename(filename) + 'could not be saved.') | |
if err.output == 'Memory limit would be exceeded. Failing.\n': | |
gimp.message(err.output + 'Try to increase memory limit.') | |
else: | |
gimp.message(err.output) | |
# Delete the temporary file | |
remove(s_tmpfile_name) | |
# Exit | |
pdb.gimp_progress_end() | |
pdb.gimp_displays_flush() | |
def register_save(): | |
gimp.register_save_handler('file-guetzli-save', 'jpg,jpeg,jpe,gtz', '') | |
register( | |
proc_name='file-guetzli-save', | |
blurb='Save as JPEG using the Google Guetzli', | |
help='Save as JPEG using the Google Guetzli', | |
author='Fabricio Biazzotto', | |
copyright='(copyLEFT) 2017 Fabricio Biazzotto', | |
date='2017', | |
label='<Save>/JPEG image (Google Guetzli)', | |
imagetypes='RGB*, GRAY*', | |
params=[ | |
(PF_STRING, 'unknown', | |
'Undocumented (?) compulsory parameter, set to None', None), | |
# NOTE: Is this some kind of undocumented behaviour? It seems that the | |
# 1st parameter (actually it's the 5th) is COMPULSORY but ignored in | |
# the GUI and must be of the PF_STRING type (not sure about this). | |
(PF_SLIDER, 'quality', '_Quality', 95, (84, 100, 1)), | |
(PF_INT, 'memlimit', 'Memory _limit (MB)', 6000), | |
(PF_TOGGLE, 'nomemlimit', '_No memory limit', 0), | |
(PF_SLIDER, 'preproc1', 'Pre-processing: Selective s_moothing', 0, | |
(0, 100, 1)), | |
(PF_SLIDER, 'preproc2', 'Pre-processing: _Ringing reduction', 0, | |
(0, 2, 1)) | |
], | |
results=[], | |
function=guetzli, | |
on_query=register_save | |
) | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment