diff --git a/assets/images/aeg_ahmedabad.jpg b/assets/images/aeg_ahmedabad.jpg index ed26488..4f7bf48 100644 Binary files a/assets/images/aeg_ahmedabad.jpg and b/assets/images/aeg_ahmedabad.jpg differ diff --git a/assets/images/book_image_introduction_to_the_commandline.jpg b/assets/images/book_image_introduction_to_the_commandline.jpg index 162b891..15873e4 100644 Binary files a/assets/images/book_image_introduction_to_the_commandline.jpg and b/assets/images/book_image_introduction_to_the_commandline.jpg differ diff --git a/assets/images/book_review_docker_up_and_running/main.jpg b/assets/images/book_review_docker_up_and_running/main.jpg index 3307856..d0bf6f3 100644 Binary files a/assets/images/book_review_docker_up_and_running/main.jpg and b/assets/images/book_review_docker_up_and_running/main.jpg differ diff --git a/assets/images/cpython_devsprint.jpg b/assets/images/cpython_devsprint.jpg index 6f98b15..7fae70b 100644 Binary files a/assets/images/cpython_devsprint.jpg and b/assets/images/cpython_devsprint.jpg differ diff --git a/assets/images/django_girls_blr_2018/group_photo.jpg b/assets/images/django_girls_blr_2018/group_photo.jpg index 5f0fcdc..514b58e 100644 Binary files a/assets/images/django_girls_blr_2018/group_photo.jpg and b/assets/images/django_girls_blr_2018/group_photo.jpg differ diff --git a/assets/images/gnu_mailman_team.jpg b/assets/images/gnu_mailman_team.jpg index 3b80db1..2555cad 100644 Binary files a/assets/images/gnu_mailman_team.jpg and b/assets/images/gnu_mailman_team.jpg differ diff --git a/assets/images/ilugc.jpg b/assets/images/ilugc.jpg index d2d1321..b199e22 100644 Binary files a/assets/images/ilugc.jpg and b/assets/images/ilugc.jpg differ diff --git a/assets/images/pycon_india.jpg b/assets/images/pycon_india.jpg index 4270fc5..40795db 100644 Binary files a/assets/images/pycon_india.jpg and b/assets/images/pycon_india.jpg differ diff --git a/assets/images/pycon_pune_2017_keynote_speakers.jpg b/assets/images/pycon_pune_2017_keynote_speakers.jpg index 894379c..309d53a 100644 Binary files a/assets/images/pycon_pune_2017_keynote_speakers.jpg and b/assets/images/pycon_pune_2017_keynote_speakers.jpg differ diff --git a/assets/images/pydelhi_conf_2017/desk.jpg b/assets/images/pydelhi_conf_2017/desk.jpg index b0e6370..2e0af6d 100644 Binary files a/assets/images/pydelhi_conf_2017/desk.jpg and b/assets/images/pydelhi_conf_2017/desk.jpg differ diff --git a/assets/images/pydelhi_conf_2017/devsprint.jpg b/assets/images/pydelhi_conf_2017/devsprint.jpg index 28f49b0..0f92964 100644 Binary files a/assets/images/pydelhi_conf_2017/devsprint.jpg and b/assets/images/pydelhi_conf_2017/devsprint.jpg differ diff --git a/assets/images/pydelhi_conf_2017/group.jpg b/assets/images/pydelhi_conf_2017/group.jpg index 910771a..bd0c0a8 100644 Binary files a/assets/images/pydelhi_conf_2017/group.jpg and b/assets/images/pydelhi_conf_2017/group.jpg differ diff --git a/assets/images/pydelhi_conf_2017/group_photo.jpg b/assets/images/pydelhi_conf_2017/group_photo.jpg index 910771a..bd0c0a8 100644 Binary files a/assets/images/pydelhi_conf_2017/group_photo.jpg and b/assets/images/pydelhi_conf_2017/group_photo.jpg differ diff --git a/assets/images/pydelhi_conf_2017/lunch.jpg b/assets/images/pydelhi_conf_2017/lunch.jpg index c595efb..8668da0 100644 Binary files a/assets/images/pydelhi_conf_2017/lunch.jpg and b/assets/images/pydelhi_conf_2017/lunch.jpg differ diff --git a/assets/images/pydelhi_conf_2017/pannel_disussion.jpg b/assets/images/pydelhi_conf_2017/pannel_disussion.jpg index b53fdea..9bdec20 100644 Binary files a/assets/images/pydelhi_conf_2017/pannel_disussion.jpg and b/assets/images/pydelhi_conf_2017/pannel_disussion.jpg differ diff --git a/assets/images/pydelhi_conf_2017/pydelhi_community.jpg b/assets/images/pydelhi_conf_2017/pydelhi_community.jpg index 38388c5..dc0576f 100644 Binary files a/assets/images/pydelhi_conf_2017/pydelhi_community.jpg and b/assets/images/pydelhi_conf_2017/pydelhi_community.jpg differ diff --git a/assets/images/pydelhi_conf_2017/ricardo.jpg b/assets/images/pydelhi_conf_2017/ricardo.jpg index 50daf50..4afa64b 100644 Binary files a/assets/images/pydelhi_conf_2017/ricardo.jpg and b/assets/images/pydelhi_conf_2017/ricardo.jpg differ diff --git a/assets/images/pydelhi_conf_2017/t_shirt.jpg b/assets/images/pydelhi_conf_2017/t_shirt.jpg index 5784372..5c8a54c 100644 Binary files a/assets/images/pydelhi_conf_2017/t_shirt.jpg and b/assets/images/pydelhi_conf_2017/t_shirt.jpg differ diff --git a/assets/images/python_slice_function/title_image.jpg b/assets/images/python_slice_function/title_image.jpg index e7eb9be..12e0ba8 100644 Binary files a/assets/images/python_slice_function/title_image.jpg and b/assets/images/python_slice_function/title_image.jpg differ diff --git a/assets/images/reserved-bit.jpg b/assets/images/reserved-bit.jpg index 1ae6a8c..e7ae9ba 100644 Binary files a/assets/images/reserved-bit.jpg and b/assets/images/reserved-bit.jpg differ diff --git a/assets/images/reserved_bit_boards_box_1.jpg b/assets/images/reserved_bit_boards_box_1.jpg index 1458d7b..258b004 100644 Binary files a/assets/images/reserved_bit_boards_box_1.jpg and b/assets/images/reserved_bit_boards_box_1.jpg differ diff --git a/assets/images/reserved_bit_boards_box_2.jpg b/assets/images/reserved_bit_boards_box_2.jpg index dfae3c0..2f4fbf4 100644 Binary files a/assets/images/reserved_bit_boards_box_2.jpg and b/assets/images/reserved_bit_boards_box_2.jpg differ diff --git a/assets/images/reserved_bit_dragonboard.jpg b/assets/images/reserved_bit_dragonboard.jpg index 5975f90..75825c3 100644 Binary files a/assets/images/reserved_bit_dragonboard.jpg and b/assets/images/reserved_bit_dragonboard.jpg differ diff --git a/assets/images/stephen_turnbull.jpg b/assets/images/stephen_turnbull.jpg index f3c95d4..a128087 100644 Binary files a/assets/images/stephen_turnbull.jpg and b/assets/images/stephen_turnbull.jpg differ diff --git a/assets/images/walkthrough_python_3_7/breakpoint_environment_variable_example.gif b/assets/images/walkthrough_python_3_7/breakpoint_environment_variable_example.gif index 6461d65..f44c8ea 100644 Binary files a/assets/images/walkthrough_python_3_7/breakpoint_environment_variable_example.gif and b/assets/images/walkthrough_python_3_7/breakpoint_environment_variable_example.gif differ diff --git a/assets/images/walkthrough_python_3_7/breakpoint_example.gif b/assets/images/walkthrough_python_3_7/breakpoint_example.gif index 1aa9047..b108c63 100644 Binary files a/assets/images/walkthrough_python_3_7/breakpoint_example.gif and b/assets/images/walkthrough_python_3_7/breakpoint_example.gif differ diff --git a/assets/images/walkthrough_python_3_7/dataclasses_dataclass.gif b/assets/images/walkthrough_python_3_7/dataclasses_dataclass.gif index a08a32d..3a27695 100644 Binary files a/assets/images/walkthrough_python_3_7/dataclasses_dataclass.gif and b/assets/images/walkthrough_python_3_7/dataclasses_dataclass.gif differ diff --git a/assets/images/walkthrough_python_3_7/subprocess_run_capture_output.gif b/assets/images/walkthrough_python_3_7/subprocess_run_capture_output.gif index 6acbcdb..74cdcf9 100644 Binary files a/assets/images/walkthrough_python_3_7/subprocess_run_capture_output.gif and b/assets/images/walkthrough_python_3_7/subprocess_run_capture_output.gif differ diff --git a/scripts/optimize-images b/scripts/optimize-images new file mode 100755 index 0000000..76b557f --- /dev/null +++ b/scripts/optimize-images @@ -0,0 +1,139 @@ +#!/usr/bin/env bash +# +# Keep assets/images small enough to serve. +# +# scripts/optimize-images rewrite oversized images in place +# scripts/optimize-images --check report oversized images, change nothing +# +# The --check mode is what CI runs (via scripts/test). It only looks at file +# sizes, so it works anywhere. Rewriting needs image tools that are assumed to +# be on the author's macOS machine, which is where new images enter the repo. +# +set -euo pipefail + +cd "$(dirname "$0")/.." + +# Budgets, in bytes. A photo is displayed at roughly 800px, so 1600px covers +# high-density screens with room to spare; 300 KB is comfortably above the +# largest photo once it has been through the pipeline below. +readonly MAX_PHOTO_BYTES=307200 # 300 KB — jpg, png +readonly MAX_PHOTO_EDGE=1600 + +# Animated screencasts cost far more because they carry hundreds of frames. +# Resizing is the only lever that helps them: palette reduction and lossy LZW +# together save under 10%, while halving the width saves 60%. +readonly MAX_GIF_BYTES=1258291 # 1.2 MB +readonly MAX_GIF_WIDTH=1200 + +readonly IMAGE_DIR="assets/images" + +# stat(1) is not portable between macOS and the Linux runners. +file_size() { + stat -f %z "$1" 2>/dev/null || stat -c %s "$1" +} + +human() { + awk -v b="$1" 'BEGIN { printf "%.0f KB", b / 1024 }' +} + +budget_for() { + case "${1##*.}" in + gif|GIF) echo "$MAX_GIF_BYTES" ;; + *) echo "$MAX_PHOTO_BYTES" ;; + esac +} + +check() { + local over=0 file size budget + + while IFS= read -r file; do + size=$(file_size "$file") + budget=$(budget_for "$file") + if [ "$size" -gt "$budget" ]; then + printf ' %-58s %8s (budget %s)\n' \ + "$file" "$(human "$size")" "$(human "$budget")" >&2 + over=$((over + 1)) + fi + done < <(find "$IMAGE_DIR" -type f \ + \( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.gif' \) \ + | sort) + + if [ "$over" -gt 0 ]; then + cat >&2 </dev/null 2>&1 || { + echo "error: $1 is required to optimize images. $2" >&2 + exit 1 + } +} + +optimize() { + require sips "It ships with macOS." + require cjpeg "Install with: brew install jpeg-turbo" + require gifsicle "Install with: brew install gifsicle" + + local file size budget tmp before after + before=0 + after=0 + + while IFS= read -r file; do + size=$(file_size "$file") + budget=$(budget_for "$file") + before=$((before + size)) + + # Already small enough. Skipping keeps this script idempotent: re-encoding + # a JPEG that is already fine would only lose quality for no gain. + if [ "$size" -le "$budget" ]; then + after=$((after + size)) + continue + fi + + case "${file##*.}" in + gif|GIF) + tmp=$(mktemp -t optimize-images) + gifsicle -O3 --resize-fit-width "$MAX_GIF_WIDTH" --colors 128 \ + "$file" > "$tmp" + mv "$tmp" "$file" + ;; + png|PNG) + sips -Z "$MAX_PHOTO_EDGE" "$file" >/dev/null + ;; + *) + # sips resizes well but writes bulky JPEGs — on a 2048px photo its + # encoder produced 452 KB where cjpeg produces 262 KB from the same + # pixels. So sips only resizes, and cjpeg does the encoding. + tmp=$(mktemp -t optimize-images) + sips -Z "$MAX_PHOTO_EDGE" -s format png "$file" --out "$tmp.png" >/dev/null + cjpeg -quality 80 -optimize -progressive "$tmp.png" > "$tmp" + mv "$tmp" "$file" + rm -f "$tmp.png" + ;; + esac + + size=$(file_size "$file") + after=$((after + size)) + printf ' %-58s -> %8s\n' "$file" "$(human "$size")" + done < <(find "$IMAGE_DIR" -type f \ + \( -iname '*.jpg' -o -iname '*.jpeg' -o -iname '*.png' -o -iname '*.gif' \) \ + | sort) + + printf '\n%s -> %s\n' "$(human "$before")" "$(human "$after")" +} + +case "${1:-}" in + --check) check ;; + "") optimize ;; + *) echo "usage: $0 [--check]" >&2; exit 2 ;; +esac diff --git a/scripts/test b/scripts/test index fb2ad6c..66b4b7a 100755 --- a/scripts/test +++ b/scripts/test @@ -3,6 +3,10 @@ set -euo pipefail export LANG=en_US.UTF-8 +# Fail before building if an oversized image has been committed. Cheap, and it +# catches the problem at review time rather than after it ships. +./scripts/optimize-images --check + ./scripts/build # External URLs htmlproofer should not fail on. Two reasons appear here: