Skip to content

Commit

Permalink
Add CI scanning to reject alpha PNG images (#69)
Browse files Browse the repository at this point in the history
Add a Python script to the repo which recursively scans for `*.png`
files and checks each one for the presence of an alpha channel. If
any are discovered, they'll be reported and the program exit status
incremented (reporting an error condition to the calling shell).

The script uses Python Pillow to quickly examine each PNG file
encountered during its scan.

A GitHub Actions workflow sets up Python 3.13 and Pillow on a ubuntu
runner, then runs the scanning script. The script will output GitHub
Actions annotated strings, which the workflow will turn into file
annotations visible on the workflow summary page. (The annotations
are not as useful on image files as they would be on text content,
but they'll serve.)

Scanning of this sort in general is more likely to be useful if
wrong-format images are submitted via PR, where they can be blocked
from ever reaching the main branch until they've been converted to
the correct format.

If the images are directly committed to the repo by maintainers,
scanning them and flagging issues after-the-fact will be far less
effective.
  • Loading branch information
ferdnyc authored Jan 18, 2025
1 parent e838f5e commit 6a21712
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 0 deletions.
19 changes: 19 additions & 0 deletions .github/workflows/format-check.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: image-scan

on:
push:
pull_request:
workflow_dispatch:

jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: "3.13"
- name: Install Pillow
run: pip install pillow
- name: Scan images
run: python3 check_png_format.py .
72 changes: 72 additions & 0 deletions check_png_format.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env python3

import sys
import logging
import typing as T
from pathlib import Path
from PIL import Image


logging.basicConfig()
log = logging.getLogger('check_png_format')


class AlphaChannelError(Exception):
"""Alert when one of the scanned images contains an alpha channel."""
def __init__(self, msg: str = "", **kwargs) -> None:
self.image_format: str = kwargs.get('format', "unknown alpha-channel")
self.image_path: Path | None = kwargs.get('path')
if not msg:
msg = " ".join([
f"Found {self.image_format} format image",
f"at {self.image_path}",
])
super().__init__(msg)

def github_actions_error(self):
file = self.image_path.resolve()
title= "Invalid PNG format detected!"
message= (
f"`{self.image_path}` is in {self.image_format} format, "
+ "which contains an alpha channel. Convert it to a "
"non-transparent format."
)
return f"::error file={file},title={title}::{message}"


def check_image_format(path: Path) -> bool:
"""Determine whether an image file has an alpha channel."""
log.debug("Checking file: %s", path)
image = Image.open(path.resolve())
image_format = image.mode
bands = image.getbands()
log.debug("Found %s format image with bands: %s", image_format, bands)
if bands[-1] in ('a', 'A'):
raise AlphaChannelError(path=path, format=image_format)
return True


def scan_directory(img_dir: Path) -> int:
"""Call check_image_format on all PNG files in a directory tree."""
log.debug("Recursively scanning directory: %s", img_dir)
error_count = 0
for png in img_dir.rglob('*.png', case_sensitive=False):
try:
check_image_format(png)
except AlphaChannelError as ex:
print(ex.github_actions_error())
error_count += 1
return error_count


if __name__ == "__main__":
args = set(sys.argv[1:])
if '--debug' in args:
log.setLevel(logging.DEBUG)
args = args - set(['--debug'])
target_dirs = list(args) or ['.']
log.debug("Beginning scan of %d paths", len(target_dirs))
errors = sum(
scan_directory(Path(target_dir))
for target_dir in target_dirs)
sys.exit(errors)

0 comments on commit 6a21712

Please sign in to comment.