Skip to content
Merged
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: 9 additions & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Optional. Sponsor button on the repo page wires here.
# Uncomment whichever platforms you've set up and put your handle in.
# Leaving everything commented means no Sponsor button shows — fine
# for now; flip on when you're ready to accept support.

# github: epalosh # https://github.com/sponsors enrolled?
# ko_fi: epalosh # https://ko-fi.com/epalosh
# patreon: epalosh
# custom: ["https://your-site"] # any other URL (e.g. PayPal.me)
16 changes: 16 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Hide the "Open a blank issue" link at the bottom of the New Issue
# picker. Users who don't fit either template land in Discussions
# (questions/help) or the dedicated security path instead — keeps the
# issue tracker focused on actionable bug reports and feature requests.
blank_issues_enabled: false

contact_links:
- name: Question or general help
url: https://github.com/epalosh/openfov/discussions/categories/q-a
about: Ask in Discussions if you're not sure something's a bug. Faster help, and the answer helps other users too.
- name: Show off your setup
url: https://github.com/epalosh/openfov/discussions/categories/show-and-tell
about: Got OpenFOV working with a specific webcam, monitor, or rig? Share what worked so others can follow.
- name: Security vulnerability
url: https://github.com/epalosh/openfov/security/advisories/new
about: Please report security issues privately, not as public bugs. See SECURITY.md for what's in scope.
Binary file added .github/social-preview.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: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# OpenFOV

[![CI](https://github.com/epalosh/openfov/actions/workflows/ci.yml/badge.svg)](https://github.com/epalosh/openfov/actions/workflows/ci.yml)
[![Latest release](https://img.shields.io/github/v/release/epalosh/openfov?display_name=tag&sort=semver)](https://github.com/epalosh/openfov/releases)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![Python: 3.11+](https://img.shields.io/badge/Python-3.11%2B-blue)](https://www.python.org/downloads/)
[![Platform: Windows](https://img.shields.io/badge/Platform-Windows%2010%2F11-blue)](#install)

Webcam head tracking for iRacing.
One installer, one launch — head tracking working.

Expand Down
136 changes: 136 additions & 0 deletions tools/build_social_preview.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
"""Generate the 1280×640 social preview banner for GitHub / X / Discord.

GitHub uses this exact size; it's also what most other link-preview
consumers crop to. The banner has the FOV-cone mark on the left, the
project name + one-line tagline in the middle, and the repo URL in the
bottom-right corner. Mid-grey background matches the icon tile so the
brand stays consistent across the icon, taskbar, and social shares.

Saved to `.github/social-preview.png`. Upload via repo Settings →
General → Social preview → Edit.

Run via:
python tools/build_social_preview.py
"""

from __future__ import annotations

from pathlib import Path

from PIL import Image, ImageDraw, ImageFont


# Output size — GitHub's recommended dimensions for repo social preview.
_W, _H = 1280, 640

# Match the icon palette.
_BG = (82, 90, 100, 255) # #525a64 — same tile color as the .ico
_HEAD_FILL = (10, 13, 16, 255)
_HEAD_STROKE = (255, 255, 255, 255)
_CONE_FILL = (52, 174, 158, 255) # solid teal
_CONE_STROKE = (31, 119, 104, 255) # darker teal
_TITLE_FG = (240, 244, 248, 255)
_TAGLINE_FG = (180, 188, 196, 255)
_FOOTER_FG = (120, 130, 140, 255)


def _try_font(size: int, bold: bool = False) -> ImageFont.FreeTypeFont:
"""Find a sensible system font. Falls back to Pillow's default if
nothing is available (rare on Windows). We try Segoe UI first to
match the in-app Qt look."""
candidates = [
"C:/Windows/Fonts/segoeuib.ttf" if bold else "C:/Windows/Fonts/segoeui.ttf",
"C:/Windows/Fonts/arialbd.ttf" if bold else "C:/Windows/Fonts/arial.ttf",
"/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf" if bold
else "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
]
for p in candidates:
if Path(p).exists():
return ImageFont.truetype(p, size)
return ImageFont.load_default()


def _draw_logo(img: Image.Image, cx: int, cy: int, scale: float) -> None:
"""Render the FOV-cone mark centered on (cx, cy). Geometry mirrors
build_icon.py but at a different scale; we keep them in lockstep
by hand since the icon canvas and the banner have different
aspect ratios and absolute size."""

# Coordinates in the original 256-px icon viewBox.
head_cx, head_cy, head_r = 86, 128, 52
cone_apex = (99, 128)
cone_top = (226, 45)
cone_bot = (226, 211)
# Center the design at (128, 128) within the viewBox; offset to (cx, cy).
def s(x: float, y: float) -> tuple[float, float]:
return (cx + (x - 128) * scale, cy + (y - 128) * scale)

cone = Image.new("RGBA", img.size, (0, 0, 0, 0))
d = ImageDraw.Draw(cone)
apex = s(*cone_apex)
top = s(*cone_top)
bot = s(*cone_bot)
d.polygon([apex, top, bot], fill=_CONE_FILL)
stroke_w = max(3, int(round(8 * scale)))
d.line([apex, top], fill=_CONE_STROKE, width=stroke_w)
d.line([apex, bot], fill=_CONE_STROKE, width=stroke_w)

head = Image.new("RGBA", img.size, (0, 0, 0, 0))
d = ImageDraw.Draw(head)
hx, hy = s(head_cx, head_cy)
hr = head_r * scale
head_stroke_w = max(3, int(round(7 * scale)))
d.ellipse(
(hx - hr, hy - hr, hx + hr, hy + hr),
fill=_HEAD_FILL,
outline=_HEAD_STROKE,
width=head_stroke_w,
)

img.alpha_composite(cone)
img.alpha_composite(head)


def main() -> None:
img = Image.new("RGBA", (_W, _H), _BG)

# Left third: FOV-cone logo. Scale 1.3 makes it ~440 px wide,
# leaving ~770 px for text on the right.
_draw_logo(img, cx=320, cy=320, scale=1.6)

# Right two-thirds: title + tagline. Anchored at x=620 so the text
# column has consistent left alignment.
d = ImageDraw.Draw(img)
text_x = 620

title = "OpenFOV"
title_font = _try_font(124, bold=True)
d.text((text_x, 200), title, fill=_TITLE_FG, font=title_font)

# Tagline: one line, sized to comfortably fit the column to the
# right of the logo. 32pt with Segoe UI fits "Webcam head tracking
# for iRacing" in well under 600 px.
tagline = "Webcam head tracking for iRacing"
tagline_font = _try_font(34)
d.text((text_x, 370), tagline, fill=_TAGLINE_FG, font=tagline_font)

# Footer URL — small, lower-right.
footer_font = _try_font(24)
footer = "github.com/epalosh/openfov"
footer_bbox = d.textbbox((0, 0), footer, font=footer_font)
footer_w = footer_bbox[2] - footer_bbox[0]
d.text(
(_W - footer_w - 40, _H - 50),
footer,
fill=_FOOTER_FG,
font=footer_font,
)

out = Path(__file__).resolve().parent.parent / ".github" / "social-preview.png"
out.parent.mkdir(parents=True, exist_ok=True)
img.convert("RGB").save(out, format="PNG", optimize=True)
print(f"Wrote {out} ({out.stat().st_size} bytes)")


if __name__ == "__main__":
main()
Loading