Пережаты картинки
@@ -70,7 +70,10 @@
|
|||||||
## Команды
|
## Команды
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd Report && bash scripts/render_puml.sh
|
cd Report && bash scripts/render_puml.sh # PlantUML → PNG (dpi 72 + pngquant 28–45)
|
||||||
cd Report && python scripts/check_images.py
|
cd Report && python3 scripts/compress_screenshots.py # скриншоты: JPEG q28, pngquant 25–40
|
||||||
|
cd Report && python3 scripts/check_images.py
|
||||||
typst compile --root .. "Пояснительная_записка_ПытковРЕ.typ"
|
typst compile --root .. "Пояснительная_записка_ПытковРЕ.typ"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Сначала `render_puml.sh`, затем `compress_screenshots.py` — диаграммы не пережимаются повторно.
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 354 KiB After Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 221 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 222 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 237 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 294 KiB After Width: | Height: | Size: 75 KiB |
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 399 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 199 KiB After Width: | Height: | Size: 54 KiB |
|
Before Width: | Height: | Size: 278 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 424 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 407 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 427 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 7.7 KiB |
|
Before Width: | Height: | Size: 254 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 171 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 26 KiB |
@@ -3,8 +3,9 @@
|
|||||||
skinparam monochrome true
|
skinparam monochrome true
|
||||||
skinparam shadowing false
|
skinparam shadowing false
|
||||||
skinparam roundcorner 0
|
skinparam roundcorner 0
|
||||||
|
skinparam dpi 72
|
||||||
skinparam defaultFontName "Liberation Serif"
|
skinparam defaultFontName "Liberation Serif"
|
||||||
skinparam defaultFontSize 12
|
skinparam defaultFontSize 11
|
||||||
skinparam defaultFontColor black
|
skinparam defaultFontColor black
|
||||||
skinparam backgroundColor white
|
skinparam backgroundColor white
|
||||||
skinparam ArrowColor black
|
skinparam ArrowColor black
|
||||||
|
|||||||
132
Report/scripts/compress_screenshots.py
Executable file
@@ -0,0 +1,132 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Сжатие только скриншотов в Report/images/ (не PlantUML PNG).
|
||||||
|
|
||||||
|
JPEG: quality=28 (ImageMagick).
|
||||||
|
PNG-скриншоты (Gradle, ручная схема): pngquant quality 25–40.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Имена из IMAGES_REGISTRY: Скриншот UI, Gradle, Ручная схема
|
||||||
|
SCREENSHOT_STEMS = {
|
||||||
|
"fig_05_local_vaults",
|
||||||
|
"fig_06_encrypt_dialog",
|
||||||
|
"fig_07_open_close_dialog",
|
||||||
|
"fig_08_rename_delete_dialog",
|
||||||
|
"fig_09_remote_vaults",
|
||||||
|
"fig_10_yandex_oauth",
|
||||||
|
"fig_12_tasks_screen",
|
||||||
|
"fig_13_tasks_notification",
|
||||||
|
"fig_33_storage_secrets_2fa",
|
||||||
|
"fig_34_2fa_single_token",
|
||||||
|
"fig_24_domain_class_manual",
|
||||||
|
"fig_27_gradle_domain_test",
|
||||||
|
"fig_28_gradle_usecases_test",
|
||||||
|
"fig_29_gradle_ui_test",
|
||||||
|
"fig_30_gradle_test_summary",
|
||||||
|
"fig_31_gradle_connected_test",
|
||||||
|
}
|
||||||
|
|
||||||
|
JPEG_QUALITY = "28"
|
||||||
|
PNGQUANT_QUALITY = "25-40"
|
||||||
|
|
||||||
|
|
||||||
|
def stem(path: Path) -> str:
|
||||||
|
return path.stem
|
||||||
|
|
||||||
|
|
||||||
|
def is_screenshot(path: Path) -> bool:
|
||||||
|
if path.suffix.lower() == ".jpg":
|
||||||
|
return stem(path) in SCREENSHOT_STEMS or path.name.startswith("fig_")
|
||||||
|
if path.suffix.lower() == ".png":
|
||||||
|
return stem(path) in SCREENSHOT_STEMS
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def size_kb(path: Path) -> float:
|
||||||
|
return path.stat().st_size / 1024
|
||||||
|
|
||||||
|
|
||||||
|
def compress_jpeg(path: Path, magick: str) -> None:
|
||||||
|
tmp = path.with_suffix(path.suffix + ".tmp")
|
||||||
|
subprocess.run(
|
||||||
|
[magick, str(path), "-strip", "-quality", JPEG_QUALITY, str(tmp)],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
)
|
||||||
|
tmp.replace(path)
|
||||||
|
|
||||||
|
|
||||||
|
def compress_png(path: Path, pngquant: str) -> bool:
|
||||||
|
tmp = path.with_suffix(".pngquant.tmp.png")
|
||||||
|
for quality in (PNGQUANT_QUALITY, "15-55", "10-80"):
|
||||||
|
try:
|
||||||
|
subprocess.run(
|
||||||
|
[
|
||||||
|
pngquant,
|
||||||
|
f"--quality={quality}",
|
||||||
|
"--force",
|
||||||
|
"--output",
|
||||||
|
str(tmp),
|
||||||
|
str(path),
|
||||||
|
],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
)
|
||||||
|
tmp.replace(path)
|
||||||
|
return True
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
if tmp.exists():
|
||||||
|
tmp.unlink()
|
||||||
|
print(f" WARN: pngquant skip {path.name}", file=sys.stderr)
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
report = Path(__file__).resolve().parents[1]
|
||||||
|
images = report / "images"
|
||||||
|
magick = shutil.which("magick") or shutil.which("convert")
|
||||||
|
pngquant = shutil.which("pngquant")
|
||||||
|
if not magick:
|
||||||
|
print("ERROR: ImageMagick (magick/convert) not found", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
if not pngquant:
|
||||||
|
print("ERROR: pngquant not found", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
targets = sorted(
|
||||||
|
p for p in images.iterdir() if p.is_file() and is_screenshot(p)
|
||||||
|
)
|
||||||
|
if not targets:
|
||||||
|
print("No screenshots to compress", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
total_before = 0.0
|
||||||
|
total_after = 0.0
|
||||||
|
for path in targets:
|
||||||
|
before = size_kb(path)
|
||||||
|
total_before += before
|
||||||
|
if path.suffix.lower() in {".jpg", ".jpeg"}:
|
||||||
|
compress_jpeg(path, magick)
|
||||||
|
else:
|
||||||
|
compress_png(path, pngquant)
|
||||||
|
after = size_kb(path)
|
||||||
|
total_after += after
|
||||||
|
pct = (1 - after / before) * 100 if before else 0
|
||||||
|
print(f" {path.name}: {before:.0f} KiB → {after:.0f} KiB (−{pct:.0f}%)")
|
||||||
|
|
||||||
|
saved = (1 - total_after / total_before) * 100 if total_before else 0
|
||||||
|
print(
|
||||||
|
f"OK: {len(targets)} screenshots, "
|
||||||
|
f"{total_before:.0f} KiB → {total_after:.0f} KiB (−{saved:.0f}%)"
|
||||||
|
)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -48,4 +48,22 @@ export PLANTUML_LIMIT_SIZE="${PLANTUML_LIMIT_SIZE:-16384}"
|
|||||||
${GOST_CFG:+-config "$GOST_CFG"} \
|
${GOST_CFG:+-config "$GOST_CFG"} \
|
||||||
-o "$IMG_DIR" "${PUML_FILES[@]}"
|
-o "$IMG_DIR" "${PUML_FILES[@]}"
|
||||||
|
|
||||||
echo "Done. $(ls -1 "$IMG_DIR"/fig_*.png 2>/dev/null | wc -l) PNG in images/"
|
# Лёгкое сжатие только свежесгенерированных PNG из .puml (скриншоты не трогаем).
|
||||||
|
PNGQUANT="${PNGQUANT:-pngquant}"
|
||||||
|
PUML_PNG_QUALITY="${PUML_PNG_QUALITY:-28-45}"
|
||||||
|
if command -v "$PNGQUANT" >/dev/null 2>&1; then
|
||||||
|
for puml in "${PUML_FILES[@]}"; do
|
||||||
|
base="$(basename "$puml" .puml)"
|
||||||
|
png="$IMG_DIR/${base}.png"
|
||||||
|
if [[ -f "$png" ]]; then
|
||||||
|
tmp="${png}.pquant.tmp"
|
||||||
|
"$PNGQUANT" --quality="$PUML_PNG_QUALITY" --force --output "$tmp" "$png"
|
||||||
|
mv -f "$tmp" "$png"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "PNGquant ($PUML_PNG_QUALITY) applied to PlantUML outputs."
|
||||||
|
else
|
||||||
|
echo "WARN: pngquant not found, PlantUML PNG left uncompressed" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Done. $(ls -1 "$IMG_DIR"/fig_*.png 2>/dev/null | wc -l) PNG in images/ (dpi 72, see plantuml-gost.cfg)."
|
||||||
|
|||||||