File size: 2,950 Bytes
a5cb443
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#!/usr/bin/env python3
"""
Dump ELA map to a PNG for visual inspection.

Usage:
    python scripts/dump_ela_map.py --image path/to/image.jpg --out ela_map.png

All parameters match Sherloq defaults. Adjust as needed:
    python scripts/dump_ela_map.py --image img.jpg --out ela.png --quality 75 --scale 50 --contrast 20
"""

import argparse
import base64
import json
import sys
from io import BytesIO
from pathlib import Path

from PIL import Image

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from src.tools.forensic import perform_ela  # noqa: E402


def decode_and_save(ela_map: str, out_path: Path):
    """Decode data:image/png;base64,... to a file."""
    if not ela_map or not ela_map.startswith("data:image/png;base64,"):
        raise ValueError("ELA map missing or not a base64 PNG data URL.")
    b64 = ela_map.split(",", 1)[1]
    data = base64.b64decode(b64)
    out_path.parent.mkdir(parents=True, exist_ok=True)
    out_path.write_bytes(data)


def main():
    parser = argparse.ArgumentParser(description="Dump ELA map to PNG (Sherloq-compatible).")
    parser.add_argument("--image", required=True, help="Path to source image.")
    parser.add_argument("--out", required=True, help="Output PNG path.")
    # Defaults match Sherloq
    parser.add_argument("--quality", type=int, default=75, help="JPEG recompression quality (1-100). Default: 75")
    parser.add_argument("--max-size", type=int, default=0, help="Max side length (0=no resize). Default: 0")
    parser.add_argument("--scale", type=int, default=50, help="Scale factor (1-100). Default: 50")
    parser.add_argument("--contrast", type=int, default=20, help="Contrast adjustment %% (0-100). Default: 20")
    parser.add_argument("--linear", action="store_true", help="Use linear mode (default: non-linear with sqrt)")
    parser.add_argument("--grayscale", action="store_true", help="Output grayscale (default: color RGB)")
    args = parser.parse_args()

    payload = {
        "path": args.image,
        "quality": args.quality,
        "max_size": args.max_size,
        "scale": args.scale,
        "contrast": args.contrast,
        "linear": args.linear,
        "grayscale": args.grayscale,
        "return_map": True,
    }
    result = json.loads(perform_ela(json.dumps(payload)))
    if result.get("status") != "completed":
        raise RuntimeError(f"ELA failed: {result}")
    ela_map = result.get("ela_map")
    if ela_map is None:
        raise RuntimeError("ELA map missing in result.")
    decode_and_save(ela_map, Path(args.out))
    reported_size = result.get("ela_map_size")
    mode = "linear" if args.linear else "non-linear (sqrt)"
    color = "grayscale" if args.grayscale else "color"
    print(f"Saved ELA map to {args.out}")
    print(f"  Size: {reported_size}, Quality: {args.quality}, Scale: {args.scale}, Contrast: {args.contrast}%")
    print(f"  Mode: {mode}, Output: {color}")


if __name__ == "__main__":
    main()