diff --git a/bin/check-content b/bin/check-content index d294481f..92e2f924 100755 --- a/bin/check-content +++ b/bin/check-content @@ -387,6 +387,74 @@ def check_social(people, sponsors): f"owner to fix.") +# The largest size the build asks of a source, by directory, and the weight +# that makes an oversized one worth rewriting. Both tables are the ones +# bin/fit-sources works from, and its docstring says which template sets each +# cap. Keep the two in step: this check is what points at that script. +SOURCE_CAPS = { + "sponsors": (1120, 300), + "photos": (1600, None), + "logo.png": (920, None), +} +SOURCE_MIN_KB = 150 +SOURCE_MAX_MPX = 4 + + +def source_cap(relative): + """Return the cap that covers this path under assets/images, or None.""" + for key, cap in SOURCE_CAPS.items(): + if relative.as_posix() == key or relative.parts[0] == key: + return cap + return None + + +def check_sources(): + """A source far larger than anything the site serves is weight and risk. + + Hugo makes every file a reader gets, so a source only has to be as large + as the biggest variant a template asks for. orpheus.png reached + 23944x5753, which is 138 megapixels and about 551 MB of RAM to decode, for + a logo the page shows at 560x135. Every build paid that, and so did every + tool that opened the repository. + + Being over the cap is not enough on its own: a 512x512 logo shown at + 300x300 costs almost nothing, and rewriting it would throw away the only + copy there is. The file has to be heavy as well, on disk or to decode. + """ + # The images directory above people/ and sponsors/, which is where the + # caps are keyed from: both lists name a directory inside it. + images = {d.parent for d in PHOTO_DIRS + LOGO_DIRS} + for base in sorted(images): + if not base.is_dir(): + continue + for path in sorted(base.rglob("*")): + if not path.is_file() or path.suffix.lower() not in SOURCE_SUFFIXES: + continue + relative = path.relative_to(base) + cap = source_cap(relative) + if cap is None: + continue + dimensions = image_size(path) + if not dimensions: + continue + width, height = dimensions + max_width, max_height = cap + over = width > max_width or (max_height and height > max_height) + if not over: + continue + size = path.stat().st_size + megapixels = width * height / 1e6 + if size / 1024 <= SOURCE_MIN_KB and megapixels <= SOURCE_MAX_MPX: + continue + fits = f"{max_width}x{max_height}" if max_height else f"{max_width} wide" + error(path, f"is {width}x{height} ({megapixels:.1f} Mpx, " + f"{size // 1024} KB), over the {fits} the build asks " + f"of it. Run bin/fit-sources.") + + +SOURCE_SUFFIXES = {".jpg", ".jpeg", ".png", ".webp"} + + def check_logos(sponsors): """A sponsor whose declared logo file is missing renders a broken image, and the Hugo build does not notice. A sponsor with no logo_image at all is only @@ -592,6 +660,7 @@ def main(): check_photos(people) check_logos(sponsors) check_social(people, sponsors) + check_sources() listed_people = check_listings(people, sponsors) check_agenda() check_orphans(people, listed_people) diff --git a/bin/fit-sources b/bin/fit-sources new file mode 100755 index 00000000..7b2d37a6 --- /dev/null +++ b/bin/fit-sources @@ -0,0 +1,248 @@ +#!/usr/bin/env python3 +"""Resize an image source down to the largest size the build asks of it. + +Hugo's image pipeline makes every file the site serves. A source only has to +be as large as the biggest variant the templates ask for; every pixel above +that is weight in the repository and work in the build, and none of it ever +reaches a reader. + +The sponsor logos show how far that drifted. orpheus.png is 23944x5753, which +is 138 million pixels and about 551 MB of RAM to decode, for a logo the page +shows at 560x135. Pillow refuses an image that size by default, as a +decompression bomb. + +The caps come from the templates, and the docstring of each entry in CAPS says +which one: + + sponsors Fit 1120x300 sponsor_summary.html, the 2x variant + photos 1600 wide image_hero.html, Fill 1600x450; the carousel + asks 1200 through image_resize.html + logo.png 920 wide nav.html, Resize 460x, the 2x of a 230px slot + +The person photos are not here. bin/square-photos owns them: they are cropped +to a square as well as resized, and the crop needs a face detector. + +Being over the cap is not enough on its own. A 512x512 logo shown at 300x300 +costs almost nothing and resizing it would throw away the only copy the +repository has, so a file is left alone unless it is also heavy to carry or +heavy to decode: over 150 KB on disk, or over 4 megapixels. Both thresholds +are arguments. That is what picks out the 23944px logo and leaves the 512px +one alone. + +Nothing is enlarged and nothing is cropped. A source inside its cap, or under +both thresholds, is left alone byte for byte. + +Usage: + bin/fit-sources --dry-run list what would change, change nothing + bin/fit-sources rewrite the sources in place + bin/fit-sources --min-kb 150 --max-mpx 4 move the thresholds + bin/fit-sources PATH... only these files + +A named path is how to rewrite a very large source without putting the +whole run at risk. Decoding orpheus.png takes about 550 MB, and a run that +holds that next to the rest was killed once on a machine with 8 GB. One +file to a process gives the memory back between them: + + bin/fit-sources --dry-run | cut -d: -f1 \ + | while read f; do bin/fit-sources "themes/pybcn_theme/assets/images/$f"; done + +Needs pillow, which is not needed to build the site. This is run by hand when +an image is added, not by the build and not by CI. bin/check-content fails on +a source above its cap, which is what stops the next 23944px upload. +""" + +import argparse +import pathlib +import sys + +try: + from PIL import Image, ImageOps +except ImportError: + sys.exit("needs pillow: pip install pillow") + +# Pillow refuses an image over 89 million pixels, because decoding one is a +# denial of service against whatever opens it. That is the case this script +# exists to end, so it has to be able to open the file once. +Image.MAX_IMAGE_PIXELS = None + +ROOT = pathlib.Path(__file__).resolve().parent.parent +IMAGES = ROOT / "themes" / "pybcn_theme" / "assets" / "images" +SUFFIXES = {".jpg", ".jpeg", ".png", ".webp"} + +# path relative to IMAGES -> (max width, max height). A height of None means +# the width is the only cap, which is what a hero and the logo need: they are +# cropped or scaled by width. +CAPS = { + "sponsors": (1120, 300), + "photos": (1600, None), + "logo.png": (920, None), +} + +# A file over the cap is only rewritten when it is also one of these. See the +# paragraph in the docstring. +DEFAULT_MIN_KB = 150 +DEFAULT_MAX_MPX = 4 + + +def cap_for(path): + """Return the cap that covers this file, or None when none does.""" + rel = path.relative_to(IMAGES) + for key, cap in CAPS.items(): + if rel.as_posix() == key or rel.parts[0] == key: + return cap + return None + + +def target_size(width, height, cap): + """Return the size this file should have, keeping its aspect ratio.""" + max_width, max_height = cap + scale = min(max_width / width, 1.0) + if max_height is not None: + scale = min(scale, max_height / height) + if scale >= 1.0: + return (width, height) + return (max(1, round(width * scale)), max(1, round(height * scale))) + + +# The JPEG qualities to try, in order, and the smallest one this script will +# settle for. See save(). +QUALITIES = (88, 84, 80, 76) + + +def save(image, path, budget): + """Write the image back in its own format, no larger than it was. + + Transparency is kept. A logo is a PNG with an alpha channel over whatever + the page puts behind it, and flattening it to RGB would put a white box + around every logo on a coloured band. + + A JPEG is re-encoded, and fewer pixels do not always mean fewer bytes: a + source saved at a low quality, resized and written back at a high one, + comes out larger. Eight of the hero photos did, one of them by half again. + The quality steps down until the file fits the budget, which is what the + file weighed before. At the last step it is written anyway: the pixels + are what the page needs, and a hero at 1600px wide is worth more than the + bytes of a 2500px one. + """ + suffix = path.suffix.lower() + if suffix in (".jpg", ".jpeg"): + flat = image.convert("RGB") + for quality in QUALITIES: + flat.save(path, "JPEG", quality=quality, optimize=True, + progressive=True) + if path.stat().st_size <= budget: + return quality + return QUALITIES[-1] + if suffix == ".png": + image.save(path, "PNG", optimize=True) + return None + image.save(path) + return None + + +# The palette size to quantise back to. 255 and not 256, so that an image +# with transparency keeps an index for it. +PALETTE_COLOURS = 255 + + +def flatten_palette(image): + """Return an image Pillow will resize with the filter it was given. + + Image.resize() ignores the resample argument for mode P and mode 1 and + uses NEAREST, because there is no sensible way to average two palette + indices. The filter this script asks for is silently dropped. + + RGBA when the palette carries transparency, RGB when it does not, so an + opaque image does not gain an alpha channel it has no use for. + """ + if image.mode not in ("P", "1"): + return image + transparent = "transparency" in image.info + return image.convert("RGBA" if transparent else "RGB") + + +def restore_palette(image, was_palette): + """Put a flattened image back on a palette, if that is where it started. + + A palette PNG packs a flat logo into a fraction of what full colour + needs: jobfluent.png is 10.8 KB on a palette and 38.3 KB in RGBA, for + the same picture. Resizing has to happen off the palette, but there is + no reason to leave it there. + + Measured against the ideal, which is the original resized straight to + the size the page serves: the quantised file is 2.07 of 255 away and the + RGBA one 1.71. A quarter of a point of mean difference, for a quarter of + the bytes. + + FASTOCTREE because it is the one of Pillow's methods that takes RGBA and + keeps the alpha. The count of fully transparent pixels is unchanged. + """ + if not was_palette: + return image + return image.quantize(colors=PALETTE_COLOURS, + method=Image.Quantize.FASTOCTREE) + + +def process(path, dry_run, min_kb, max_mpx): + cap = cap_for(path) + if cap is None: + return None + before = path.stat().st_size + with Image.open(path) as opened: + rotated = ImageOps.exif_transpose(opened) + was_palette = rotated.mode in ("P", "1") + image = flatten_palette(rotated) + width, height = image.size + target = target_size(width, height, cap) + if target == (width, height): + return None + heavy = before / 1024 > min_kb or width * height / 1e6 > max_mpx + if not heavy: + return None + note = f"{width}x{height} -> {target[0]}x{target[1]}" + if dry_run: + return f"{note} {before / 1024:.0f} KB {width * height / 1e6:.1f} Mpx" + resized = restore_palette(image.resize(target, Image.LANCZOS), + was_palette) + quality = save(resized, path, before) + after = path.stat().st_size + note = f"{note} {before / 1024:.0f} KB -> {after / 1024:.0f} KB" + if quality is not None: + note += f" q{quality}" + return note + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("paths", nargs="*", type=pathlib.Path) + parser.add_argument("--dry-run", action="store_true") + parser.add_argument("--min-kb", type=int, default=DEFAULT_MIN_KB) + parser.add_argument("--max-mpx", type=float, default=DEFAULT_MAX_MPX) + args = parser.parse_args() + + wanted = {p.resolve() for p in args.paths} + + changed = 0 + before = after = 0 + for path in sorted(IMAGES.rglob("*")): + if not path.is_file() or path.suffix.lower() not in SUFFIXES: + continue + if cap_for(path) is None: + continue + if wanted and path.resolve() not in wanted: + continue + before += path.stat().st_size + note = process(path, args.dry_run, args.min_kb, args.max_mpx) + after += path.stat().st_size + if note: + changed += 1 + print(f"{path.relative_to(IMAGES)}: {note}") + + verb = "would change" if args.dry_run else "changed" + print(f"\n{changed} sources {verb}.") + print(f"{before / 1048576:.1f} MB -> {after / 1048576:.1f} MB") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/content/_index.md b/content/_index.md index ea8632f4..eaa7d238 100644 --- a/content/_index.md +++ b/content/_index.md @@ -17,7 +17,7 @@ carousel_header: true carousel_indicators: true carousel_class: home_carousel carousel_photos: -- photo: /photos/canodrom_header.png +- photo: /photos/canodrom_header.jpg caption: PyDay 2025 link: events/pyday_bcn/pyday_bcn_2025/ - photo: /photos/meetups.jpg diff --git a/content/events/pydatabcn/pydatabcn_2023.md b/content/events/pydatabcn/pydatabcn_2023.md index 50b9e3d8..2182d3cb 100644 --- a/content/events/pydatabcn/pydatabcn_2023.md +++ b/content/events/pydatabcn/pydatabcn_2023.md @@ -8,7 +8,7 @@ menu: weight: 2 aliases: - /pydatabcn-2023 -heroBackground: /images/photos/canodrom.png +heroBackground: /images/photos/canodrom.jpg layout: event diff --git a/content/events/pyday_bcn/pyday_bcn_2020.md b/content/events/pyday_bcn/pyday_bcn_2020.md index 05046d82..97aee18e 100644 --- a/content/events/pyday_bcn/pyday_bcn_2020.md +++ b/content/events/pyday_bcn/pyday_bcn_2020.md @@ -8,7 +8,7 @@ menu: weight: 6 aliases: - /pyday-bcn-2020 -heroBackground: /images/photos/people-pyday-2019_1600_450.png +heroBackground: /images/photos/people-pyday-2019.jpg layout: event diff --git a/content/events/pyday_bcn/pyday_bcn_2022.md b/content/events/pyday_bcn/pyday_bcn_2022.md index 60b6b7a2..8d40841b 100644 --- a/content/events/pyday_bcn/pyday_bcn_2022.md +++ b/content/events/pyday_bcn/pyday_bcn_2022.md @@ -8,7 +8,7 @@ menu: weight: 4 aliases: - /pyday-bcn-2022 -heroBackground: /images/photos/canodrom.png +heroBackground: /images/photos/canodrom.jpg layout: event diff --git a/content/events/pyday_bcn/pyday_bcn_2024.md b/content/events/pyday_bcn/pyday_bcn_2024.md index f022c1db..5834ea97 100644 --- a/content/events/pyday_bcn/pyday_bcn_2024.md +++ b/content/events/pyday_bcn/pyday_bcn_2024.md @@ -8,7 +8,7 @@ menu: weight: 2 aliases: - /pyday-bcn-2024 -heroBackground: /images/photos/canodrom.png +heroBackground: /images/photos/canodrom.jpg layout: event diff --git a/content/events/pyday_bcn/pyday_bcn_2025.md b/content/events/pyday_bcn/pyday_bcn_2025.md index 98cf6034..e57b2466 100644 --- a/content/events/pyday_bcn/pyday_bcn_2025.md +++ b/content/events/pyday_bcn/pyday_bcn_2025.md @@ -8,7 +8,7 @@ menu: weight: 1 aliases: - /pyday-bcn-2025 -heroBackground: /images/photos/canodrom.png +heroBackground: /images/photos/canodrom.jpg layout: event diff --git a/themes/pybcn_theme/assets/images/logo.png b/themes/pybcn_theme/assets/images/logo.png index 7ede6ddc..79967ca9 100644 Binary files a/themes/pybcn_theme/assets/images/logo.png and b/themes/pybcn_theme/assets/images/logo.png differ diff --git a/themes/pybcn_theme/assets/images/photos/canodrom.jpg b/themes/pybcn_theme/assets/images/photos/canodrom.jpg new file mode 100644 index 00000000..7f2f0222 Binary files /dev/null and b/themes/pybcn_theme/assets/images/photos/canodrom.jpg differ diff --git a/themes/pybcn_theme/assets/images/photos/canodrom.png b/themes/pybcn_theme/assets/images/photos/canodrom.png deleted file mode 100644 index acd35169..00000000 Binary files a/themes/pybcn_theme/assets/images/photos/canodrom.png and /dev/null differ diff --git a/themes/pybcn_theme/assets/images/photos/canodrom_header.jpg b/themes/pybcn_theme/assets/images/photos/canodrom_header.jpg new file mode 100644 index 00000000..8c2d137a Binary files /dev/null and b/themes/pybcn_theme/assets/images/photos/canodrom_header.jpg differ diff --git a/themes/pybcn_theme/assets/images/photos/canodrom_header.png b/themes/pybcn_theme/assets/images/photos/canodrom_header.png deleted file mode 100644 index bb69a33f..00000000 Binary files a/themes/pybcn_theme/assets/images/photos/canodrom_header.png and /dev/null differ diff --git a/themes/pybcn_theme/assets/images/photos/django_pyladies_2022/carousel11.jpeg b/themes/pybcn_theme/assets/images/photos/django_pyladies_2022/carousel11.jpeg index 1e964cbc..f1b603af 100644 Binary files a/themes/pybcn_theme/assets/images/photos/django_pyladies_2022/carousel11.jpeg and b/themes/pybcn_theme/assets/images/photos/django_pyladies_2022/carousel11.jpeg differ diff --git a/themes/pybcn_theme/assets/images/photos/django_pyladies_2022/carousel13.jpeg b/themes/pybcn_theme/assets/images/photos/django_pyladies_2022/carousel13.jpeg index 9cc6f7e0..592362bb 100644 Binary files a/themes/pybcn_theme/assets/images/photos/django_pyladies_2022/carousel13.jpeg and b/themes/pybcn_theme/assets/images/photos/django_pyladies_2022/carousel13.jpeg differ diff --git a/themes/pybcn_theme/assets/images/photos/django_pyladies_2022/header.jpg b/themes/pybcn_theme/assets/images/photos/django_pyladies_2022/header.jpg index 0fa538f5..3424affe 100644 Binary files a/themes/pybcn_theme/assets/images/photos/django_pyladies_2022/header.jpg and b/themes/pybcn_theme/assets/images/photos/django_pyladies_2022/header.jpg differ diff --git a/themes/pybcn_theme/assets/images/photos/group-pydata-2017.jpg b/themes/pybcn_theme/assets/images/photos/group-pydata-2017.jpg index a6c1aedb..22f91577 100644 Binary files a/themes/pybcn_theme/assets/images/photos/group-pydata-2017.jpg and b/themes/pybcn_theme/assets/images/photos/group-pydata-2017.jpg differ diff --git a/themes/pybcn_theme/assets/images/photos/organize-with-us.jpg b/themes/pybcn_theme/assets/images/photos/organize-with-us.jpg index e000d3eb..cd3b9f96 100644 Binary files a/themes/pybcn_theme/assets/images/photos/organize-with-us.jpg and b/themes/pybcn_theme/assets/images/photos/organize-with-us.jpg differ diff --git a/themes/pybcn_theme/assets/images/photos/people-pydata-2017.jpg b/themes/pybcn_theme/assets/images/photos/people-pydata-2017.jpg index a9341576..6c3e62ae 100644 Binary files a/themes/pybcn_theme/assets/images/photos/people-pydata-2017.jpg and b/themes/pybcn_theme/assets/images/photos/people-pydata-2017.jpg differ diff --git a/themes/pybcn_theme/assets/images/photos/people-pyday-2016.jpg b/themes/pybcn_theme/assets/images/photos/people-pyday-2016.jpg index b638b8d8..8a781924 100644 Binary files a/themes/pybcn_theme/assets/images/photos/people-pyday-2016.jpg and b/themes/pybcn_theme/assets/images/photos/people-pyday-2016.jpg differ diff --git a/themes/pybcn_theme/assets/images/photos/people-pyday-2019.jpg b/themes/pybcn_theme/assets/images/photos/people-pyday-2019.jpg index e1a2cb5e..5c42f41f 100644 Binary files a/themes/pybcn_theme/assets/images/photos/people-pyday-2019.jpg and b/themes/pybcn_theme/assets/images/photos/people-pyday-2019.jpg differ diff --git a/themes/pybcn_theme/assets/images/photos/people-pyday-2019_1600_450.png b/themes/pybcn_theme/assets/images/photos/people-pyday-2019_1600_450.png deleted file mode 100644 index 92c3be3e..00000000 Binary files a/themes/pybcn_theme/assets/images/photos/people-pyday-2019_1600_450.png and /dev/null differ diff --git a/themes/pybcn_theme/assets/images/photos/propose-a-talk.jpg b/themes/pybcn_theme/assets/images/photos/propose-a-talk.jpg index 19e3e2cf..308bced9 100644 Binary files a/themes/pybcn_theme/assets/images/photos/propose-a-talk.jpg and b/themes/pybcn_theme/assets/images/photos/propose-a-talk.jpg differ diff --git a/themes/pybcn_theme/assets/images/photos/pyladies/header/header2.jpeg b/themes/pybcn_theme/assets/images/photos/pyladies/header/header2.jpeg index ab30f146..0282fe9a 100644 Binary files a/themes/pybcn_theme/assets/images/photos/pyladies/header/header2.jpeg and b/themes/pybcn_theme/assets/images/photos/pyladies/header/header2.jpeg differ diff --git a/themes/pybcn_theme/assets/images/photos/reg-desk-pyday-2019.jpg b/themes/pybcn_theme/assets/images/photos/reg-desk-pyday-2019.jpg index 7e89f5c3..54ec78bf 100644 Binary files a/themes/pybcn_theme/assets/images/photos/reg-desk-pyday-2019.jpg and b/themes/pybcn_theme/assets/images/photos/reg-desk-pyday-2019.jpg differ diff --git a/themes/pybcn_theme/assets/images/photos/sponsors-area-pyday-2019.jpg b/themes/pybcn_theme/assets/images/photos/sponsors-area-pyday-2019.jpg index 9de2b600..35f31b0c 100644 Binary files a/themes/pybcn_theme/assets/images/photos/sponsors-area-pyday-2019.jpg and b/themes/pybcn_theme/assets/images/photos/sponsors-area-pyday-2019.jpg differ diff --git a/themes/pybcn_theme/assets/images/sponsors/backmarket.png b/themes/pybcn_theme/assets/images/sponsors/backmarket.png index ac6f2dc2..e01c2dcd 100644 Binary files a/themes/pybcn_theme/assets/images/sponsors/backmarket.png and b/themes/pybcn_theme/assets/images/sponsors/backmarket.png differ diff --git a/themes/pybcn_theme/assets/images/sponsors/capitole.jpg b/themes/pybcn_theme/assets/images/sponsors/capitole.jpg index 1f08c844..48b7ace9 100644 Binary files a/themes/pybcn_theme/assets/images/sponsors/capitole.jpg and b/themes/pybcn_theme/assets/images/sponsors/capitole.jpg differ diff --git a/themes/pybcn_theme/assets/images/sponsors/cloudblue.jpg b/themes/pybcn_theme/assets/images/sponsors/cloudblue.jpg index 39715a30..04ca71c3 100644 Binary files a/themes/pybcn_theme/assets/images/sponsors/cloudblue.jpg and b/themes/pybcn_theme/assets/images/sponsors/cloudblue.jpg differ diff --git a/themes/pybcn_theme/assets/images/sponsors/hybridtheory.png b/themes/pybcn_theme/assets/images/sponsors/hybridtheory.png index 41751bc5..1afd5c06 100644 Binary files a/themes/pybcn_theme/assets/images/sponsors/hybridtheory.png and b/themes/pybcn_theme/assets/images/sponsors/hybridtheory.png differ diff --git a/themes/pybcn_theme/assets/images/sponsors/jobfluent.png b/themes/pybcn_theme/assets/images/sponsors/jobfluent.png index a591d534..6e3f6b2b 100644 Binary files a/themes/pybcn_theme/assets/images/sponsors/jobfluent.png and b/themes/pybcn_theme/assets/images/sponsors/jobfluent.png differ diff --git a/themes/pybcn_theme/assets/images/sponsors/orpheus.png b/themes/pybcn_theme/assets/images/sponsors/orpheus.png index a60331d7..bc32c50d 100644 Binary files a/themes/pybcn_theme/assets/images/sponsors/orpheus.png and b/themes/pybcn_theme/assets/images/sponsors/orpheus.png differ diff --git a/themes/pybcn_theme/assets/images/sponsors/pybcn.png b/themes/pybcn_theme/assets/images/sponsors/pybcn.png index 7ede6ddc..453e7ca6 100644 Binary files a/themes/pybcn_theme/assets/images/sponsors/pybcn.png and b/themes/pybcn_theme/assets/images/sponsors/pybcn.png differ diff --git a/themes/pybcn_theme/assets/images/sponsors/qilimanjaro.png b/themes/pybcn_theme/assets/images/sponsors/qilimanjaro.png index 29d2851c..4d8e817e 100644 Binary files a/themes/pybcn_theme/assets/images/sponsors/qilimanjaro.png and b/themes/pybcn_theme/assets/images/sponsors/qilimanjaro.png differ diff --git a/themes/pybcn_theme/assets/images/sponsors/satellogic.png b/themes/pybcn_theme/assets/images/sponsors/satellogic.png index ba80c7e1..eb23a2e6 100644 Binary files a/themes/pybcn_theme/assets/images/sponsors/satellogic.png and b/themes/pybcn_theme/assets/images/sponsors/satellogic.png differ diff --git a/themes/pybcn_theme/assets/images/sponsors/somenergia.jpg b/themes/pybcn_theme/assets/images/sponsors/somenergia.jpg index 667ef8a6..8bd68e0c 100644 Binary files a/themes/pybcn_theme/assets/images/sponsors/somenergia.jpg and b/themes/pybcn_theme/assets/images/sponsors/somenergia.jpg differ diff --git a/themes/pybcn_theme/assets/images/sponsors/travelperk.png b/themes/pybcn_theme/assets/images/sponsors/travelperk.png index 192a2f46..812550ae 100644 Binary files a/themes/pybcn_theme/assets/images/sponsors/travelperk.png and b/themes/pybcn_theme/assets/images/sponsors/travelperk.png differ diff --git a/themes/pybcn_theme/assets/images/sponsors/ub.jpg b/themes/pybcn_theme/assets/images/sponsors/ub.jpg index 5828f2f1..fca15db2 100644 Binary files a/themes/pybcn_theme/assets/images/sponsors/ub.jpg and b/themes/pybcn_theme/assets/images/sponsors/ub.jpg differ