11import datetime
22import io
3+ import itertools
34import logging
5+ from concurrent .futures import ThreadPoolExecutor
46from pathlib import Path
57
68import matplotlib
@@ -59,30 +61,30 @@ def utc_to_pt(dt: datetime.datetime) -> datetime.datetime:
5961 return dt .astimezone (tz = TZ_PACIFIC )
6062
6163
62- def savefig ( path : Path ) -> None :
64+ def quantize_png ( in_png : str | Path | io . BytesIO , out_path : Path , dither : bool ) -> None :
6365 """
64- Save matplotlib figure to PNG file.
65-
66- We perform a bit of optimization to make the output filesize smaller
67- without sacrificing quality.
66+ Quantize a PNG to save space and save to disk.
6867
6968 Args:
70- path: Path to output PNG file.
69+ in_png: Input PNG. Should be in "RGBA" format.
70+ out_path: Path to output PNG file.
71+ dither: Whether to use dithering.
7172 """
7273
73- bts = io . BytesIO ()
74- plt . savefig ( bts , format = "png" )
75-
76- with PIL . Image . open ( bts ) as img :
77- img2 = img . convert ( "RGB" ) .convert ("P" , palette = PIL .Image .Palette .WEB )
78- img2 .save (path , format = "png" )
74+ with PIL . Image . open ( in_png ) as img : # RGBA image
75+ if dither :
76+ # converting to RGB will enable dithering
77+ img = img . convert ( "RGB" )
78+ img = img .convert ("P" , palette = PIL .Image .Palette .WEB )
79+ img .save (out_path , format = "png" , optimize = True )
7980
8081
8182def main (
8283 grib_path : Path | None = None ,
8384 / ,
8485 out_dir : Path = Path ("_site" ),
8586 resolution : RESOLUTION = "h" ,
87+ dither : bool = True ,
8688) -> None :
8789 """
8890 Create plots for significant wave height.
@@ -94,6 +96,7 @@ def main(
9496 out_dir: Path to output directory.
9597 resolution: Resolution of the coastline map. Options are crude, low,
9698 intermediate, high, and full.
99+ dither: Dithering increases image quality, but also increases storage size.
97100 """
98101
99102 if resolution != "f" :
@@ -254,6 +257,9 @@ def main(
254257 LOG .info ("Creating colormap frames" )
255258 plot_dir = out_dir / "plots"
256259 plot_dir .mkdir (parents = True , exist_ok = True )
260+
261+ # render each frame
262+ frame_pngs_bytes : list [io .BytesIO ] = []
257263 for hour_i in tqdm (range (NUM_DATA_POINTS )):
258264 pacific_time = analysis_date_pacific + datetime .timedelta (hours = hour_i )
259265 pacific_time_str = pacific_time .strftime (DATETIME_FORMAT )
@@ -265,7 +271,15 @@ def main(
265271 ax_main .set_title (
266272 f"Significant wave height (ft) and peak wave direction\n Hour { hour_i :03} { pacific_time_str } "
267273 )
268- savefig (plot_dir / f"{ hour_i } .png" )
274+
275+ png_bytes = io .BytesIO ()
276+ plt .savefig (png_bytes , format = "png" )
277+ frame_pngs_bytes .append (png_bytes )
278+
279+ # quantize each PNG and save to disk
280+ with ThreadPoolExecutor () as exe :
281+ paths = [plot_dir / f"{ hour_i } .png" for hour_i in range (NUM_DATA_POINTS )]
282+ exe .map (quantize_png , frame_pngs_bytes , paths , itertools .repeat (dither ))
269283
270284 # Get current time and version
271285
0 commit comments