Spaces:
Sleeping
Sleeping
Delete screenshot_annotator.py
Browse files- screenshot_annotator.py +0 -219
screenshot_annotator.py
DELETED
|
@@ -1,219 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Screenshot Annotator
|
| 3 |
-
Generates annotated screenshots with red circles highlighting visual differences
|
| 4 |
-
"""
|
| 5 |
-
|
| 6 |
-
from typing import List, Dict, Any
|
| 7 |
-
from state_schema import VisualDifference
|
| 8 |
-
import os
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
class ScreenshotAnnotator:
|
| 12 |
-
"""Annotates screenshots with visual difference indicators."""
|
| 13 |
-
|
| 14 |
-
@staticmethod
|
| 15 |
-
def annotate_screenshot(
|
| 16 |
-
screenshot_path: str,
|
| 17 |
-
differences: List[VisualDifference],
|
| 18 |
-
output_path: str,
|
| 19 |
-
viewport_name: str = "desktop"
|
| 20 |
-
) -> bool:
|
| 21 |
-
"""
|
| 22 |
-
Annotate screenshot with red circles for differences.
|
| 23 |
-
|
| 24 |
-
Args:
|
| 25 |
-
screenshot_path: Path to original screenshot
|
| 26 |
-
differences: List of visual differences
|
| 27 |
-
output_path: Path to save annotated screenshot
|
| 28 |
-
viewport_name: Viewport name (desktop/mobile)
|
| 29 |
-
|
| 30 |
-
Returns:
|
| 31 |
-
True if successful
|
| 32 |
-
"""
|
| 33 |
-
try:
|
| 34 |
-
from PIL import Image, ImageDraw
|
| 35 |
-
|
| 36 |
-
if not os.path.exists(screenshot_path):
|
| 37 |
-
return False
|
| 38 |
-
|
| 39 |
-
# Load image
|
| 40 |
-
img = Image.open(screenshot_path).convert('RGB')
|
| 41 |
-
draw = ImageDraw.Draw(img, 'RGBA')
|
| 42 |
-
|
| 43 |
-
# Filter differences for this viewport
|
| 44 |
-
viewport_diffs = [d for d in differences if d.viewport == viewport_name]
|
| 45 |
-
|
| 46 |
-
# Draw circles and labels for each difference
|
| 47 |
-
circle_radius = 30
|
| 48 |
-
label_offset = 40
|
| 49 |
-
|
| 50 |
-
for idx, diff in enumerate(viewport_diffs):
|
| 51 |
-
if not diff.location:
|
| 52 |
-
# Generate random location if not provided
|
| 53 |
-
x = (idx % 3) * 300 + 150
|
| 54 |
-
y = (idx // 3) * 300 + 150
|
| 55 |
-
else:
|
| 56 |
-
x = diff.location.get("x", 150)
|
| 57 |
-
y = diff.location.get("y", 150)
|
| 58 |
-
|
| 59 |
-
# Draw circle
|
| 60 |
-
circle_color = ScreenshotAnnotator._get_color_by_severity(diff.severity)
|
| 61 |
-
draw.ellipse(
|
| 62 |
-
[(x - circle_radius, y - circle_radius),
|
| 63 |
-
(x + circle_radius, y + circle_radius)],
|
| 64 |
-
outline=circle_color,
|
| 65 |
-
width=3
|
| 66 |
-
)
|
| 67 |
-
|
| 68 |
-
# Draw number label
|
| 69 |
-
label_number = str(idx + 1)
|
| 70 |
-
draw.text(
|
| 71 |
-
(x - 8, y - 8),
|
| 72 |
-
label_number,
|
| 73 |
-
fill=(255, 255, 255),
|
| 74 |
-
font=None
|
| 75 |
-
)
|
| 76 |
-
|
| 77 |
-
# Create output directory
|
| 78 |
-
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
| 79 |
-
|
| 80 |
-
# Save annotated image
|
| 81 |
-
img.save(output_path)
|
| 82 |
-
return True
|
| 83 |
-
|
| 84 |
-
except Exception as e:
|
| 85 |
-
print(f"Error annotating screenshot: {str(e)}")
|
| 86 |
-
return False
|
| 87 |
-
|
| 88 |
-
@staticmethod
|
| 89 |
-
def _get_color_by_severity(severity: str) -> tuple:
|
| 90 |
-
"""Get color based on severity level."""
|
| 91 |
-
if severity == "High":
|
| 92 |
-
return (255, 0, 0, 200) # Red
|
| 93 |
-
elif severity == "Medium":
|
| 94 |
-
return (255, 165, 0, 200) # Orange
|
| 95 |
-
else:
|
| 96 |
-
return (0, 255, 0, 200) # Green
|
| 97 |
-
|
| 98 |
-
@staticmethod
|
| 99 |
-
def create_comparison_image(
|
| 100 |
-
figma_screenshot: str,
|
| 101 |
-
website_screenshot: str,
|
| 102 |
-
figma_annotated: str,
|
| 103 |
-
website_annotated: str,
|
| 104 |
-
output_path: str
|
| 105 |
-
) -> bool:
|
| 106 |
-
"""
|
| 107 |
-
Create side-by-side comparison image.
|
| 108 |
-
|
| 109 |
-
Args:
|
| 110 |
-
figma_screenshot: Original Figma screenshot
|
| 111 |
-
website_screenshot: Original website screenshot
|
| 112 |
-
figma_annotated: Annotated Figma screenshot
|
| 113 |
-
website_annotated: Annotated website screenshot
|
| 114 |
-
output_path: Path to save comparison
|
| 115 |
-
|
| 116 |
-
Returns:
|
| 117 |
-
True if successful
|
| 118 |
-
"""
|
| 119 |
-
try:
|
| 120 |
-
from PIL import Image
|
| 121 |
-
|
| 122 |
-
# Load annotated images
|
| 123 |
-
figma_img = Image.open(figma_annotated)
|
| 124 |
-
website_img = Image.open(website_annotated)
|
| 125 |
-
|
| 126 |
-
# Resize to same height
|
| 127 |
-
max_height = max(figma_img.height, website_img.height)
|
| 128 |
-
figma_img = figma_img.resize(
|
| 129 |
-
(int(figma_img.width * max_height / figma_img.height), max_height)
|
| 130 |
-
)
|
| 131 |
-
website_img = website_img.resize(
|
| 132 |
-
(int(website_img.width * max_height / website_img.height), max_height)
|
| 133 |
-
)
|
| 134 |
-
|
| 135 |
-
# Create side-by-side image
|
| 136 |
-
total_width = figma_img.width + website_img.width + 20
|
| 137 |
-
comparison = Image.new('RGB', (total_width, max_height), (255, 255, 255))
|
| 138 |
-
|
| 139 |
-
comparison.paste(figma_img, (0, 0))
|
| 140 |
-
comparison.paste(website_img, (figma_img.width + 20, 0))
|
| 141 |
-
|
| 142 |
-
# Create output directory
|
| 143 |
-
os.makedirs(os.path.dirname(output_path), exist_ok=True)
|
| 144 |
-
|
| 145 |
-
# Save comparison
|
| 146 |
-
comparison.save(output_path)
|
| 147 |
-
return True
|
| 148 |
-
|
| 149 |
-
except Exception as e:
|
| 150 |
-
print(f"Error creating comparison image: {str(e)}")
|
| 151 |
-
return False
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
def annotate_all_screenshots(state: Dict[str, Any]) -> Dict[str, Any]:
|
| 155 |
-
"""
|
| 156 |
-
Annotate all screenshots with visual differences.
|
| 157 |
-
|
| 158 |
-
Args:
|
| 159 |
-
state: Current workflow state
|
| 160 |
-
|
| 161 |
-
Returns:
|
| 162 |
-
Updated state with annotated screenshots
|
| 163 |
-
"""
|
| 164 |
-
print("\n📸 Generating Annotated Screenshots...")
|
| 165 |
-
|
| 166 |
-
annotator = ScreenshotAnnotator()
|
| 167 |
-
|
| 168 |
-
figma_screenshots = state.get("figma_screenshots", {})
|
| 169 |
-
website_screenshots = state.get("website_screenshots", {})
|
| 170 |
-
visual_differences = state.get("visual_differences", [])
|
| 171 |
-
execution_id = state.get("execution_id", "unknown")
|
| 172 |
-
|
| 173 |
-
# Convert dict differences back to VisualDifference objects for the annotator
|
| 174 |
-
vd_objects = []
|
| 175 |
-
for d in visual_differences:
|
| 176 |
-
if isinstance(d, dict):
|
| 177 |
-
vd_objects.append(VisualDifference(
|
| 178 |
-
category=d.get("category", "visual"),
|
| 179 |
-
severity=d.get("severity", "Medium"),
|
| 180 |
-
issue_id=d.get("issue_id", "unknown"),
|
| 181 |
-
title=d.get("title", "Difference"),
|
| 182 |
-
description=d.get("description", ""),
|
| 183 |
-
design_value=d.get("design_value", ""),
|
| 184 |
-
website_value=d.get("website_value", ""),
|
| 185 |
-
viewport=d.get("viewport", "desktop"),
|
| 186 |
-
location=d.get("location")
|
| 187 |
-
))
|
| 188 |
-
else:
|
| 189 |
-
vd_objects.append(d)
|
| 190 |
-
|
| 191 |
-
annotated_screenshots = state.get("annotated_screenshots", {})
|
| 192 |
-
|
| 193 |
-
for viewport_name, figma_path in figma_screenshots.items():
|
| 194 |
-
if viewport_name not in website_screenshots:
|
| 195 |
-
continue
|
| 196 |
-
|
| 197 |
-
print(f" 📝 Annotating {viewport_name} screenshots...")
|
| 198 |
-
|
| 199 |
-
website_path = website_screenshots[viewport_name]
|
| 200 |
-
|
| 201 |
-
figma_annotated = f"data/annotated/{execution_id}_figma_{viewport_name}.png"
|
| 202 |
-
website_annotated = f"data/annotated/{execution_id}_website_{viewport_name}.png"
|
| 203 |
-
|
| 204 |
-
# Annotate Figma screenshot
|
| 205 |
-
if annotator.annotate_screenshot(figma_path, vd_objects, figma_annotated, viewport_name):
|
| 206 |
-
print(f" ✓ Figma annotated: {figma_annotated}")
|
| 207 |
-
|
| 208 |
-
# Annotate website screenshot
|
| 209 |
-
if annotator.annotate_screenshot(website_path, vd_objects, website_annotated, viewport_name):
|
| 210 |
-
print(f" ✓ Website annotated: {website_annotated}")
|
| 211 |
-
|
| 212 |
-
# Create comparison
|
| 213 |
-
comparison_path = f"data/comparisons/{execution_id}_{viewport_name}_comparison.png"
|
| 214 |
-
if annotator.create_comparison_image(figma_path, website_path, figma_annotated, website_annotated, comparison_path):
|
| 215 |
-
print(f" ✓ Comparison created: {comparison_path}")
|
| 216 |
-
annotated_screenshots[f"{viewport_name}_comparison"] = comparison_path
|
| 217 |
-
|
| 218 |
-
state["annotated_screenshots"] = annotated_screenshots
|
| 219 |
-
return state
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|