Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ device-frames-output/
│ └── template.json
├── index.json
```
where **category** is either `Apple iPhone, Android Phone, Android Tablet, Apple iPad`,
**model** is the type of device `(ex: 17 Pro Max, iPad mini 8.3, Pixel 9 Pro XL)`, and
where **category** is either `Apple iPhone, Apple Watch, Android Phone, Android Tablet, Apple iPad`,
**model** is the type of device `(ex: 17 Pro Max, iPad mini 8.3, Series 7-9 45mm, Pixel 9 Pro XL)`, and
**variant** is the different colors `(ex: Cosmic Orange, Blue, Titanium)`,
and [`index.json`](https://raw.githubusercontent.com/jonnyjackson26/device-frames-media/main/device-frames-output/index.json) is a JSON file which contains all frames in a nested lookup structure, each variant in kebab-case with hosted URLs and template metadata:
```json
Expand Down Expand Up @@ -122,6 +122,11 @@ Please add more device frames to expand the dataset. See [CONTRIBUTING.md](CONTR
- iPad Pro 13 M4 & M5
- [Landscape - Silver](https://github.com/jonnyjackson26/device-frames-media/tree/main/device-frames-output/Apple%20iPad/iPad%20Pro%2013%20M4%20%26%20M5/Landscape%20-%20Silver), [Landscape - Space Black](https://github.com/jonnyjackson26/device-frames-media/tree/main/device-frames-output/Apple%20iPad/iPad%20Pro%2013%20M4%20%26%20M5/Landscape%20-%20Space%20Black), [Portrait - Silver](https://github.com/jonnyjackson26/device-frames-media/tree/main/device-frames-output/Apple%20iPad/iPad%20Pro%2013%20M4%20%26%20M5/Portrait%20-%20Silver), [Portrait - Space Black](https://github.com/jonnyjackson26/device-frames-media/tree/main/device-frames-output/Apple%20iPad/iPad%20Pro%2013%20M4%20%26%20M5/Portrait%20-%20Space%20Black)

**Apple Watch:**

- Series 7-9 45mm
- [Gold Stainless Steel](https://github.com/jonnyjackson26/device-frames-media/tree/main/device-frames-output/Apple%20Watch/Series%207-9%2045mm/Gold%20Stainless%20Steel), [Graphite Stainless Steel](https://github.com/jonnyjackson26/device-frames-media/tree/main/device-frames-output/Apple%20Watch/Series%207-9%2045mm/Graphite%20Stainless%20Steel), [Midnight Aluminum](https://github.com/jonnyjackson26/device-frames-media/tree/main/device-frames-output/Apple%20Watch/Series%207-9%2045mm/Midnight%20Aluminum), [Silver Aluminum](https://github.com/jonnyjackson26/device-frames-media/tree/main/device-frames-output/Apple%20Watch/Series%207-9%2045mm/Silver%20Aluminum), [Silver Stainless Steel](https://github.com/jonnyjackson26/device-frames-media/tree/main/device-frames-output/Apple%20Watch/Series%207-9%2045mm/Silver%20Stainless%20Steel), [Starlight Aluminum](https://github.com/jonnyjackson26/device-frames-media/tree/main/device-frames-output/Apple%20Watch/Series%207-9%2045mm/Starlight%20Aluminum)

**Android Phone:**

- Pixel 8
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"frame": "frame.png",
"mask": "mask.png",
"screen": {
"x": 72,
"y": 188,
"width": 396,
"height": 484
},
"frameSize": {
"width": 540,
"height": 860
},
"hexColor": "#D6CCC3"
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"frame": "frame.png",
"mask": "mask.png",
"screen": {
"x": 72,
"y": 188,
"width": 396,
"height": 484
},
"frameSize": {
"width": 540,
"height": 860
},
"hexColor": "#35353A"
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"frame": "frame.png",
"mask": "mask.png",
"screen": {
"x": 72,
"y": 188,
"width": 396,
"height": 484
},
"frameSize": {
"width": 540,
"height": 860
},
"hexColor": "#33343A"
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"frame": "frame.png",
"mask": "mask.png",
"screen": {
"x": 72,
"y": 188,
"width": 396,
"height": 484
},
"frameSize": {
"width": 540,
"height": 860
},
"hexColor": "#E9E7E6"
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"frame": "frame.png",
"mask": "mask.png",
"screen": {
"x": 72,
"y": 188,
"width": 396,
"height": 484
},
"frameSize": {
"width": 540,
"height": 860
},
"hexColor": "#E9E7E5"
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"frame": "frame.png",
"mask": "mask.png",
"screen": {
"x": 72,
"y": 188,
"width": 396,
"height": 484
},
"frameSize": {
"width": 540,
"height": 860
},
"hexColor": "#D6CCC4"
}
158 changes: 126 additions & 32 deletions device-frames-output/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -669,6 +669,38 @@
"hexColor": "#827D75"
}
},
"ipad-mini-8-3-a17-pro": {
"starlight": {
"frame": "https://raw.githubusercontent.com/jonnyjackson26/device-frames-media/main/device-frames-output/Apple%20iPad/iPad%20mini%208.3%20A17%20Pro/Starlight/frame.png",
"mask": "https://raw.githubusercontent.com/jonnyjackson26/device-frames-media/main/device-frames-output/Apple%20iPad/iPad%20mini%208.3%20A17%20Pro/Starlight/mask.png",
"screen": {
"x": 201,
"y": 201,
"width": 2264,
"height": 1487
},
"frameSize": {
"width": 2666,
"height": 1888
},
"hexColor": "#ACA496"
},
"starlight2": {
"frame": "https://raw.githubusercontent.com/jonnyjackson26/device-frames-media/main/device-frames-output/Apple%20iPad/iPad%20mini%208.3%20A17%20Pro/Starlight2/frame.png",
"mask": "https://raw.githubusercontent.com/jonnyjackson26/device-frames-media/main/device-frames-output/Apple%20iPad/iPad%20mini%208.3%20A17%20Pro/Starlight2/mask.png",
"screen": {
"x": 200,
"y": 201,
"width": 1487,
"height": 2264
},
"frameSize": {
"width": 1888,
"height": 2666
},
"hexColor": "#ACA496"
}
},
"ipad-pro-11-a12x-to-m2": {
"landscape-silver": {
"frame": "https://raw.githubusercontent.com/jonnyjackson26/device-frames-media/main/device-frames-output/Apple%20iPad/iPad%20Pro%2011%20A12X%20to%20M2/Landscape%20-%20Silver/frame.png",
Expand Down Expand Up @@ -1036,38 +1068,6 @@
},
"hexColor": "#393639"
}
},
"ipad-mini-8-3-a17-pro": {
"starlight": {
"frame": "https://raw.githubusercontent.com/jonnyjackson26/device-frames-media/main/device-frames-output/Apple%20iPad/iPad%20mini%208.3%20A17%20Pro/Starlight/frame.png",
"mask": "https://raw.githubusercontent.com/jonnyjackson26/device-frames-media/main/device-frames-output/Apple%20iPad/iPad%20mini%208.3%20A17%20Pro/Starlight/mask.png",
"screen": {
"x": 201,
"y": 201,
"width": 2264,
"height": 1487
},
"frameSize": {
"width": 2666,
"height": 1888
},
"hexColor": "#ACA496"
},
"starlight2": {
"frame": "https://raw.githubusercontent.com/jonnyjackson26/device-frames-media/main/device-frames-output/Apple%20iPad/iPad%20mini%208.3%20A17%20Pro/Starlight2/frame.png",
"mask": "https://raw.githubusercontent.com/jonnyjackson26/device-frames-media/main/device-frames-output/Apple%20iPad/iPad%20mini%208.3%20A17%20Pro/Starlight2/mask.png",
"screen": {
"x": 200,
"y": 201,
"width": 1487,
"height": 2264
},
"frameSize": {
"width": 1888,
"height": 2666
},
"hexColor": "#ACA496"
}
}
},
"apple-iphone": {
Expand Down Expand Up @@ -2251,5 +2251,99 @@
"hexColor": "#999798"
}
}
},
"apple-watch": {
"series-7-9-45mm": {
"gold-stainless-steel": {
"frame": "https://raw.githubusercontent.com/jonnyjackson26/device-frames-media/main/device-frames-output/Apple%20Watch/Series%207-9%2045mm/Gold%20Stainless%20Steel/frame.png",
"mask": "https://raw.githubusercontent.com/jonnyjackson26/device-frames-media/main/device-frames-output/Apple%20Watch/Series%207-9%2045mm/Gold%20Stainless%20Steel/mask.png",
"screen": {
"x": 72,
"y": 188,
"width": 396,
"height": 484
},
"frameSize": {
"width": 540,
"height": 860
},
"hexColor": "#D6CCC3"
},
"graphite-stainless-steel": {
"frame": "https://raw.githubusercontent.com/jonnyjackson26/device-frames-media/main/device-frames-output/Apple%20Watch/Series%207-9%2045mm/Graphite%20Stainless%20Steel/frame.png",
"mask": "https://raw.githubusercontent.com/jonnyjackson26/device-frames-media/main/device-frames-output/Apple%20Watch/Series%207-9%2045mm/Graphite%20Stainless%20Steel/mask.png",
"screen": {
"x": 72,
"y": 188,
"width": 396,
"height": 484
},
"frameSize": {
"width": 540,
"height": 860
},
"hexColor": "#35353A"
},
"midnight-aluminum": {
"frame": "https://raw.githubusercontent.com/jonnyjackson26/device-frames-media/main/device-frames-output/Apple%20Watch/Series%207-9%2045mm/Midnight%20Aluminum/frame.png",
"mask": "https://raw.githubusercontent.com/jonnyjackson26/device-frames-media/main/device-frames-output/Apple%20Watch/Series%207-9%2045mm/Midnight%20Aluminum/mask.png",
"screen": {
"x": 72,
"y": 188,
"width": 396,
"height": 484
},
"frameSize": {
"width": 540,
"height": 860
},
"hexColor": "#33343A"
},
"silver-aluminum": {
"frame": "https://raw.githubusercontent.com/jonnyjackson26/device-frames-media/main/device-frames-output/Apple%20Watch/Series%207-9%2045mm/Silver%20Aluminum/frame.png",
"mask": "https://raw.githubusercontent.com/jonnyjackson26/device-frames-media/main/device-frames-output/Apple%20Watch/Series%207-9%2045mm/Silver%20Aluminum/mask.png",
"screen": {
"x": 72,
"y": 188,
"width": 396,
"height": 484
},
"frameSize": {
"width": 540,
"height": 860
},
"hexColor": "#E9E7E6"
},
"silver-stainless-steel": {
"frame": "https://raw.githubusercontent.com/jonnyjackson26/device-frames-media/main/device-frames-output/Apple%20Watch/Series%207-9%2045mm/Silver%20Stainless%20Steel/frame.png",
"mask": "https://raw.githubusercontent.com/jonnyjackson26/device-frames-media/main/device-frames-output/Apple%20Watch/Series%207-9%2045mm/Silver%20Stainless%20Steel/mask.png",
"screen": {
"x": 72,
"y": 188,
"width": 396,
"height": 484
},
"frameSize": {
"width": 540,
"height": 860
},
"hexColor": "#E9E7E5"
},
"starlight-aluminum": {
"frame": "https://raw.githubusercontent.com/jonnyjackson26/device-frames-media/main/device-frames-output/Apple%20Watch/Series%207-9%2045mm/Starlight%20Aluminum/frame.png",
"mask": "https://raw.githubusercontent.com/jonnyjackson26/device-frames-media/main/device-frames-output/Apple%20Watch/Series%207-9%2045mm/Starlight%20Aluminum/mask.png",
"screen": {
"x": 72,
"y": 188,
"width": 396,
"height": 484
},
"frameSize": {
"width": 540,
"height": 860
},
"hexColor": "#D6CCC4"
}
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 10 additions & 8 deletions docs/PROCESS_FRAMES_ALGORITHM.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,17 +91,19 @@ Chooses the region with:
- Aspect ratio within 1.3–2.5 (covers phones and tablets)
- Fully enclosed by opaque pixels

#### Step 5–6: Extract Bounds & Contour
- Calculate minX, minY, maxX, maxY of the selected region
- Generate bounding box (stored in `template.json` as `screen`)
- Extract precise screen contour using edge detection

#### Step 7: Generate Screen Mask
#### Step 5–6: Generate Screen Mask
- Grow the selected region through connected mostly-transparent pixels
(α < 128) so the mask covers the full glass area — anti-aliased edges and
semi-transparent glare overlays (e.g. Apple Watch bezel art). The opaque
bezel bounds the growth.
- Create blank image (same size as frame)
- Fill detected contour with white (255)
- Fill background with black (0)
- Fill grown region with white (255), background with black (0)
- Feather inward by ~1 px to avoid edge bleed

#### Step 7: Extract Bounds
- Calculate minX, minY, maxX, maxY of the final mask
- Generate bounding box (stored in `template.json` as `screen`)

#### Step 8: Validate
Checks that:
- Mask coverage is 50–90% of total frame area
Expand Down
Binary file modified docs/cover.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions frame_processor/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
ALPHA_CLEAR = 10 # Transparent threshold

# Constraints
# Aspect ratio bounds (portrait phones: 1.7-2.4, tablets: 1.3-1.6)
MIN_SCREEN_RATIO = 1.3 # Aspect ratio bounds
# Aspect ratio bounds (portrait phones: 1.7-2.4, tablets: 1.3-1.6, watches: 1.15-1.25)
MIN_SCREEN_RATIO = 1.1 # Aspect ratio bounds
MAX_SCREEN_RATIO = 2.5
MIN_MASK_COVERAGE = 0.5 # Mask area / frame area
MIN_MASK_COVERAGE = 0.3 # Mask area / frame area (watch frames include the band, so the screen is a smaller share)
MAX_MASK_COVERAGE = 0.9
MIN_REGION_AREA = 5000 # Minimum region size (pixels)
19 changes: 12 additions & 7 deletions frame_processor/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,13 @@ def process(self) -> bool:
logger.warning("No valid screen region found")
return False

screen_mask_binary = (labeled_array == screen_label).astype(np.uint8)

bounds = self._extract_bounds(screen_mask_binary)
logger.info(f"Screen bounds: {bounds}")

final_mask = self._generate_screen_mask(
labeled_array, screen_label, frame_width, frame_height
labeled_array, screen_label, alpha, frame_width, frame_height
)

bounds = self._extract_bounds(final_mask)
logger.info(f"Screen bounds: {bounds}")

if not self._validate(final_mask, frame_width, frame_height, bounds):
logger.warning("Validation failed - frame may need manual review")
return False
Expand Down Expand Up @@ -172,14 +170,21 @@ def _generate_screen_mask(
self,
labeled_array: np.ndarray,
screen_label: int,
alpha: np.ndarray,
frame_width: int,
frame_height: int,
) -> np.ndarray:
"""Generate binary screen mask with contour."""
from scipy.ndimage import binary_dilation, binary_erosion
from scipy.ndimage import binary_dilation, binary_erosion, binary_propagation

screen_binary = labeled_array == screen_label

# Grow through connected mostly-transparent pixels so the mask covers
# the full glass area — anti-aliased edges and semi-transparent glare
# overlays (e.g. Apple Watch bezel art). The opaque bezel bounds the growth.
semi_transparent = alpha < 128
screen_binary = binary_propagation(screen_binary, mask=semi_transparent)

eroded = binary_erosion(screen_binary, iterations=1)
dilated = binary_dilation(eroded, iterations=1)

Expand Down
1 change: 1 addition & 0 deletions frame_processor/readme_updater.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
CATEGORY_ORDER = [
("Apple iPhone", "Apple iPhone"),
("Apple iPad", "Apple iPad"),
("Apple Watch", "Apple Watch"),
("Android Phone", "Android Phone"),
("Android Tablet", "Android Tablet"),
]
Expand Down
Loading