{picLeft, picRight, picTop, picBottom} = If( 1,
picURL = "https://upload.wikimedia.org/wikipedia/commons/1/1e/Rooster_-_Public_Domain.jpg"; // Lisafern, CC0, via Wikimedia Commons -- https://commons.wikimedia.org/wiki/Special:ListFiles/lisafern
{600000, 6000000, 35, 5};//
, //
picURL = "https://upload.wikimedia.org/wikipedia/commons/3/3a/Foster_graph_hamiltonian.png"; // public domain: https://commons.wikimedia.org/wiki/File:Foster_graph_hamiltonian.png
{450000, 10000000, 32, 8};//
);
gTempFiles = Convert File Path( "$temp\pil_webp\", "windows" );
Delete Directory( gTempFiles ); // careful!
Create Directory( gTempFiles ); // the directory exists, and is empty
// load python and make sure PIL is available...
rc = Python Execute(
{},
{},
"\[from PIL import Image, ImageSequence
import numpy as np
import os
import time
def rmse(imageA, imageB):
if imageA.size != imageB.size:
raise ValueError("Images must have the same dimensions to calculate RMSE")
imgA_np = np.asarray(imageA).astype("float")
imgB_np = np.asarray(imageB).astype("float")
err = np.sum((imgA_np - imgB_np) ** 2)
err /= float(imageA.size[0] * imageA.size[1])
return np.sqrt(err)
]\",
echo( 0 )
);
If( rc != 0,
Throw( "python init error rc=" || Char( rc ) ),
"ok"
);
// add a JSL image to a list of files for a .webp and .jpg and .gif
addNewFrame = Function( {filename, img},
img << saveimage( gTempFiles || filename || ".png", "png" ); // png is lossless and the rmse uses this for comparison
File Size( gTempFiles || filename || ".png" );
);
// the saveFrameTo... and saveFramesTo... functions
// reflect the way the formats are typically used;
// you would not normally make a single frame GIF
// and you can't really make a multi-frame JPG.
saveFrameToWebP = Function( {outputfilename, qual},
{filelist, webpname, rc}, //
filelist = Transform Each( {f}, Files In Directory( gTempFiles ), //
Convert File Path( gTempFiles || f, "windows" );//
);
If( N Items( filelist ) != 1, //
Throw( "saveFrameToWebP nitems=" || Char( N Items( filelist ) ) );//
);
webpname = Convert File Path( outputfilename, "windows" );
rc = Python Execute(
{filelist, webpname, qual}, // inputs
{_rmse, _ossize, _timew, _timer}, // outputs
"\[# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#webp
imgs = []
for f in filelist:
imgs.append(Image.open(f).convert('RGB'))
img=imgs[0]
_timew = time.perf_counter()
img.save(webpname,save_all=True,format="WEBP",
minimize_size=True,
lossless=False,quality=int(qual))
_timew = time.perf_counter() - _timew
_timer = time.perf_counter()
ir = Image.open(webpname).convert('RGB')
_timer = time.perf_counter() - _timer
_rmse = rmse(img,ir)
_ossize = os.path.getsize(webpname)
imgs = None
]\",
echo( 0 )
);
If( rc != 0, //
Throw( "saveFrameToWebP error rc=" || Char( rc ) );//
, //
Eval List( {_rmse, _ossize, _timew, _timer} );// return value
);
);
saveFrameToPng = Function( {outputfilename, qual},
{filelist, pngname, rc}, //
filelist = Transform Each( {f}, Files In Directory( gTempFiles ), //
Convert File Path( gTempFiles || f, "windows" );//
);
If( N Items( filelist ) != 1, //
Throw( "saveFrameToPng nitems=" || Char( N Items( filelist ) ) );//
);
pngname = Convert File Path( outputfilename, "windows" );
rc = Python Execute(
{filelist, pngname, qual}, // inputs
{_rmse, _ossize, _timew, _timer}, // outputs
"\[#
imgs = []
for f in filelist:
imgs.append(Image.open(f).convert('RGB'))
img=imgs[0]
_timew = time.perf_counter()
img.save(pngname,save_all=True,format="PNG",
optimize=True,
quality=int(qual))
_timew = time.perf_counter() - _timew
_timer = time.perf_counter()
ir = Image.open(pngname).convert('RGB')
_timer = time.perf_counter() - _timer
_rmse = rmse(img,ir)
_ossize = os.path.getsize(pngname)
imgs = None
]\",
echo( 0 )
);
If( rc != 0, //
Throw( "saveFrameToWebP error rc=" || Char( rc ) );//
, //
Eval List( {_rmse, _ossize, _timew, _timer} );// return value
);
);
saveFrameToJPG = Function( {outputfilename, qual},
{filelist, jpgname, rc}, //
filelist = Transform Each( {f}, Files In Directory( gTempFiles ), //
Convert File Path( gTempFiles || f, "windows" );//
);
If( N Items( filelist ) != 1, //
Throw( "saveFrameToJPG nitems=" || Char( N Items( filelist ) ) );//
);
jpgname = Convert File Path( outputfilename, "windows" );
rc = Python Execute(
{filelist, jpgname, qual}, // inputs
{_rmse, _ossize, _timew, _timer}, // outputs
"\[# https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html#webp
imgs = []
for f in filelist:
imgs.append(Image.open(f).convert('RGB'))
img=imgs[0]
_timew = time.perf_counter()
img.save(jpgname,format="JPEG",
optimize=True,
quality=int(qual))
_timew = time.perf_counter() - _timew
_timer = time.perf_counter()
ir = Image.open(jpgname).convert('RGB')
_timer = time.perf_counter() - _timer
_rmse = rmse(img,ir)
_ossize = os.path.getsize(jpgname)
imgs = None
]\",
echo( 0 )
);
If( rc != 0, //
Throw( "saveFrameToJPG error rc=" || Char( rc ) );//
, //
Eval List( {_rmse, _ossize, _timew, _timer} );// return value
);
);
// Lisafern, CC0, via Wikimedia Commons -- https://commons.wikimedia.org/wiki/Special:ListFiles/lisafern
img = New Image( picURL );
start = HP Time();
pngSize1 = addNewFrame( "1jsl.png", img );
JSLpngTime = (HP Time() - start) / 1e6;
dt = New Table( //
New Column( "extension", "character" ),
New Column( "size" ),
New Column( "timewrite" ),
New Column( "timeread" ),
New Column( "rmse" )
);
dt << addrows( 1 );
extension = "JSLPng";
size = pngSize1;
rmse = 0;
timewrite = JSLpngTime;
dt << addrows( 1 );
extension = "PILPng";
{rmse, size, timewrite, timeread} = saveFrameToPng( "$temp/1.png", 95 );
For( i = 0, i <= 100, i += 1,
dt << addrows( 1 );
extension = "WebP";
{rmse, size, timewrite, timeread} = saveFrameToWebP( "$temp/1.webp", i );
Wait( .01 );
dt << addrows( 1 );
extension = "Jpg";
{rmse, size, timewrite, timeread} = saveFrameToJpg( "$temp/1.jpg", i );
Wait( .01 );
);
dt << Graph Builder(
Size( 996, 713 ),
Show Control Panel( 0 ),
Legend Position( "Inside Floating" ),
Variables( X( :size ), Y( :rmse ), Overlay( :extension ), Size( :timewrite ) ),
Elements( Points( X, Y, Legend( 47 ) ) ),
SendToReport(
Dispatch( {}, "size", ScaleBox,
{Scale( "Log" ), Format( "Engineering SI", 12 ), Min( 1e4 ), Max( 6.1e6 ), Minor Ticks( 1 ), Label Row(
{Show Major Grid( 1 ), Show Minor Grid( 1 ), Show Minor Labels( 1 )}
)}
),
Dispatch( {}, "rmse", ScaleBox, {Min( -0.5 ), Max( 32 ), Inc( 5 ), Minor Ticks( 0 )} ),
Dispatch( {}, "400", ScaleBox,
{Legend Model(
47,
Type Properties( 0, "Marker", {Marker( "FilledCircle" )} ),
Properties( 1, {Line Color( 3 ), Marker( "FilledCircle" )}, Item ID( "Jpg", 1 ) ),
Properties( 2, {Line Color( 5 ), Marker( "FilledCircle" )}, Item ID( "JSLPng", 1 ) ),
Properties( 3, {Line Color( 6 ), Marker( "FilledCircle" )}, Item ID( "PILPng", 1 ) ),
Properties( 4, {Line Color( 8 ), Marker( "FilledCircle" )}, Item ID( "WebP", 1 ) ),
Properties( -1, {Marker( "FilledSquare" )}, Item ID( "Png", 1 ) )
)}
),
Dispatch( {}, "graph title", TextEditBox, {Set Text( "WebP vs Jpg vs Png" )} ),
Dispatch( {}, "size", TextEditBox, {Set Text( "Size on disk, bytes" )} ),
Dispatch( {}, "rmse", TextEditBox, {Set Text( "RMSE out of 255" )} ),
Dispatch( {}, "Graph Builder", FrameBox,
{Marker Drawing Mode( "Normal" ), Reference Line Order( 6 ), Add Image(
2, // layer 2 above grid lines
image( New Image( picURL ) ),
Bounds( Left( picLeft ), Right( picRight ), Top( picTop ), Bottom( picBottom ) )
), Add Graphics Script( 7, Description( "" ), Empty() )}
),
Dispatch( {}, "400", LegendBox, {Legend Position( {47, [4, 0, 3, 2, 1]} )} )
)
);
dt << Graph Builder(
Size( 638, 370 ),
Show Control Panel( 0 ),
Variables( X( :size ), Y( :timeread ), Overlay( :extension ) ),
Elements( Points( X, Y, Legend( 9 ) ) ),
SendToReport(
Dispatch( {}, "400", ScaleBox,
{Legend Model(
9,
Properties( 0, {Line Color( 3 )}, Item ID( "Jpg", 1 ) ),
Properties( 1, {Line Color( 5 )}, Item ID( "JSLPng", 1 ) ),
Properties( 2, {Line Color( 6 )}, Item ID( "PILPng", 1 ) ),
Properties( 3, {Line Color( 8 )}, Item ID( "WebP", 1 ) )
)}
),
Dispatch( {}, "400", LegendBox, {Legend Position( {9, [0, 3, 2, 1]} )} )
)
);