Refine desktop onboarding and NSIS branding
This commit is contained in:
parent
36f34d81d3
commit
5f7dccff71
6 changed files with 100 additions and 20 deletions
|
|
@ -123,15 +123,16 @@ def parse_png(path: Path):
|
||||||
return width, height, image
|
return width, height, image
|
||||||
|
|
||||||
|
|
||||||
def resize_with_letterbox(image, width, height, target_w, target_h, background):
|
def resize_with_letterbox(image, width, height, target_w, target_h, background, scale_factor=1.0):
|
||||||
if width == target_w and height == target_h:
|
if width == target_w and height == target_h and abs(scale_factor - 1.0) < 1e-6:
|
||||||
return image, width, height
|
return image, width, height
|
||||||
|
|
||||||
bg_r, bg_g, bg_b = background
|
bg_r, bg_g, bg_b = background
|
||||||
scale = min(target_w / width, target_h / height)
|
base_scale = min(target_w / width, target_h / height)
|
||||||
scale = max(scale, 1 / max(width, height)) # avoid zero
|
base_scale *= scale_factor
|
||||||
scaled_w = max(1, int(round(width * scale)))
|
base_scale = max(base_scale, 1 / max(width, height)) # avoid zero / collapse
|
||||||
scaled_h = max(1, int(round(height * scale)))
|
scaled_w = max(1, int(round(width * base_scale)))
|
||||||
|
scaled_h = max(1, int(round(height * base_scale)))
|
||||||
|
|
||||||
output = bytearray(target_w * target_h * 4)
|
output = bytearray(target_w * target_h * 4)
|
||||||
# Fill background
|
# Fill background
|
||||||
|
|
@ -142,9 +143,9 @@ def resize_with_letterbox(image, width, height, target_w, target_h, background):
|
||||||
offset_y = (target_h - scaled_h) // 2
|
offset_y = (target_h - scaled_h) // 2
|
||||||
|
|
||||||
for y in range(scaled_h):
|
for y in range(scaled_h):
|
||||||
src_y = min(height - 1, int(round(y / scale)))
|
src_y = min(height - 1, int(round(y / base_scale)))
|
||||||
for x in range(scaled_w):
|
for x in range(scaled_w):
|
||||||
src_x = min(width - 1, int(round(x / scale)))
|
src_x = min(width - 1, int(round(x / base_scale)))
|
||||||
src_idx = (src_y * width + src_x) * 4
|
src_idx = (src_y * width + src_x) * 4
|
||||||
dst_idx = ((y + offset_y) * target_w + (x + offset_x)) * 4
|
dst_idx = ((y + offset_y) * target_w + (x + offset_x)) * 4
|
||||||
output[dst_idx : dst_idx + 4] = image[src_idx : src_idx + 4]
|
output[dst_idx : dst_idx + 4] = image[src_idx : src_idx + 4]
|
||||||
|
|
@ -206,6 +207,12 @@ def main():
|
||||||
parser.add_argument("output", type=Path)
|
parser.add_argument("output", type=Path)
|
||||||
parser.add_argument("--width", type=int, help="Target width (px)")
|
parser.add_argument("--width", type=int, help="Target width (px)")
|
||||||
parser.add_argument("--height", type=int, help="Target height (px)")
|
parser.add_argument("--height", type=int, help="Target height (px)")
|
||||||
|
parser.add_argument(
|
||||||
|
"--scale",
|
||||||
|
type=float,
|
||||||
|
default=1.0,
|
||||||
|
help="Optional multiplier applied to the fitted image size (e.g. 0.7 adds padding).",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--background",
|
"--background",
|
||||||
type=str,
|
type=str,
|
||||||
|
|
@ -218,7 +225,9 @@ def main():
|
||||||
width, height, image = parse_png(args.input)
|
width, height, image = parse_png(args.input)
|
||||||
if args.width and args.height:
|
if args.width and args.height:
|
||||||
bg = tuple(int(args.background[i : i + 2], 16) for i in (0, 2, 4))
|
bg = tuple(int(args.background[i : i + 2], 16) for i in (0, 2, 4))
|
||||||
image, width, height = resize_with_letterbox(image, width, height, args.width, args.height, bg)
|
image, width, height = resize_with_letterbox(
|
||||||
|
image, width, height, args.width, args.height, bg, max(args.scale, 0.05)
|
||||||
|
)
|
||||||
rgb = blend_to_rgb(image)
|
rgb = blend_to_rgb(image)
|
||||||
write_bmp(args.output, width, height, rgb)
|
write_bmp(args.output, width, height, rgb)
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
|
|
@ -228,4 +237,3 @@ def main():
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|
|
||||||
80
apps/desktop/scripts/pngs_to_ico.py
Normal file
80
apps/desktop/scripts/pngs_to_ico.py
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Utility to build an .ico file from a list of PNGs of different sizes.
|
||||||
|
|
||||||
|
Uses only Python's standard library so it can run in restricted environments.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import struct
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n"
|
||||||
|
|
||||||
|
|
||||||
|
def read_png_dimensions(data: bytes) -> tuple[int, int]:
|
||||||
|
if not data.startswith(PNG_SIGNATURE):
|
||||||
|
raise ValueError("All inputs must be PNG files.")
|
||||||
|
width, height = struct.unpack(">II", data[16:24])
|
||||||
|
return width, height
|
||||||
|
|
||||||
|
|
||||||
|
def build_icon(png_paths: list[Path], output: Path) -> None:
|
||||||
|
png_data = [p.read_bytes() for p in png_paths]
|
||||||
|
entries = []
|
||||||
|
offset = 6 + 16 * len(png_data) # icon header + entries
|
||||||
|
|
||||||
|
for data in png_data:
|
||||||
|
width, height = read_png_dimensions(data)
|
||||||
|
entry = {
|
||||||
|
"width": width if width < 256 else 0,
|
||||||
|
"height": height if height < 256 else 0,
|
||||||
|
"colors": 0,
|
||||||
|
"reserved": 0,
|
||||||
|
"planes": 1,
|
||||||
|
"bit_count": 32,
|
||||||
|
"size": len(data),
|
||||||
|
"offset": offset,
|
||||||
|
"data": data,
|
||||||
|
}
|
||||||
|
entries.append(entry)
|
||||||
|
offset += entry["size"]
|
||||||
|
|
||||||
|
header = struct.pack("<HHH", 0, 1, len(entries))
|
||||||
|
table = bytearray()
|
||||||
|
for entry in entries:
|
||||||
|
table.extend(
|
||||||
|
struct.pack(
|
||||||
|
"<BBBBHHII",
|
||||||
|
entry["width"],
|
||||||
|
entry["height"],
|
||||||
|
entry["colors"],
|
||||||
|
entry["reserved"],
|
||||||
|
entry["planes"],
|
||||||
|
entry["bit_count"],
|
||||||
|
entry["size"],
|
||||||
|
entry["offset"],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
payload = header + table + b"".join(entry["data"] for entry in entries)
|
||||||
|
output.write_bytes(payload)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("output", type=Path)
|
||||||
|
parser.add_argument("inputs", nargs="+", type=Path)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if not args.inputs:
|
||||||
|
raise SystemExit("Provide at least one PNG input.")
|
||||||
|
|
||||||
|
build_icon(args.inputs, args.output)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 69 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 151 KiB After Width: | Height: | Size: 151 KiB |
|
|
@ -757,7 +757,7 @@ function App() {
|
||||||
alt="Logotipo Raven"
|
alt="Logotipo Raven"
|
||||||
width={160}
|
width={160}
|
||||||
height={160}
|
height={160}
|
||||||
className="h-16 w-auto md:h-20"
|
className="h-14 w-auto md:h-16"
|
||||||
onError={() => {
|
onError={() => {
|
||||||
if (logoFallbackRef.current) return
|
if (logoFallbackRef.current) return
|
||||||
logoFallbackRef.current = true
|
logoFallbackRef.current = true
|
||||||
|
|
@ -766,15 +766,7 @@ function App() {
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col items-center gap-2">
|
<div className="flex flex-col items-center gap-2">
|
||||||
<span className="text-xs uppercase tracking-[0.18em] text-neutral-500">Portal do Cliente</span>
|
<span className="text-xs uppercase tracking-[0.18em] text-neutral-500">Portal do Cliente</span>
|
||||||
<div className="flex items-center gap-3">
|
<span className="text-2xl font-semibold text-neutral-900">Agente Desktop</span>
|
||||||
<span className="flex size-10 items-center justify-center rounded-xl bg-neutral-900 text-white shadow-sm">
|
|
||||||
<GalleryVerticalEnd className="size-5" />
|
|
||||||
</span>
|
|
||||||
<span className="text-2xl font-semibold text-neutral-900">Raven</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-wrap items-center justify-center gap-3">
|
|
||||||
<h1 className="text-lg font-semibold text-slate-800">Agente Desktop</h1>
|
|
||||||
<StatusBadge status={status} />
|
<StatusBadge status={status} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue