#!/usr/bin/env python3 """Gera a imagem e o som que trocam os que o FreeCol tira do David Rumsey Map Collection e do Freesound. Uso: make-art.py Escreve em /: landing.png (a imagem do primeiro desembarque no Novo Mundo, que o FreeCol pega do David Rumsey Map Collection, de licenca nao comercial; as outras cinco imagens do Rumsey, dos tipos de pai fundador, o BrowserCol nem usa) e alert.wav (o som de alerta, derivado de uma amostra do Freesound sob CC BY-NC). Tudo desenhado e sintetizado aqui, sem nenhum arquivo de terceiros; sempre os mesmos bytes (nada aleatorio sem semente). Copyright (C) 2026 FredyGames. GNU General Public License versao 2 ou posterior, como o FreeCol. """ import math import os import random import struct import sys import wave from PIL import Image, ImageDraw, ImageFilter out = sys.argv[1] os.makedirs(out, exist_ok=True) PAPER = (226, 208, 170) INK = (74, 52, 32) INK_LIGHT = (122, 94, 62) SS = 3 # supersampling def parchment(w, h, seed): rnd = random.Random(seed) im = Image.new("RGB", (w, h), PAPER) px = im.load() for y in range(h): for x in range(w): n = rnd.randint(-7, 7) # mancha suave nas bordas (vinheta) dx = (x - w / 2) / (w / 2) dy = (y - h / 2) / (h / 2) v = int(26 * (dx * dx + dy * dy) ** 1.2) r, g, b = PAPER px[x, y] = (max(0, r + n - v), max(0, g + n - v), max(0, b + n - int(v * 1.2))) return im.filter(ImageFilter.GaussianBlur(0.6)) def canvas(w, h, seed): base = parchment(w, h, seed) big = Image.new("RGBA", (w * SS, h * SS), (0, 0, 0, 0)) return base, big, ImageDraw.Draw(big) def finish(base, big, name): w, h = base.size layer = big.resize((w, h), Image.LANCZOS) base = base.convert("RGBA") base.alpha_composite(layer) d = ImageDraw.Draw(base) d.rectangle([0, 0, w - 1, h - 1], outline=INK_LIGHT) d.rectangle([3, 3, w - 4, h - 4], outline=INK_LIGHT) base.convert("RGB").save(os.path.join(out, name), optimize=False) def P(pts): return [(x * SS, y * SS) for x, y in pts] def line(d, pts, width=1.5, fill=INK): d.line(P(pts), fill=fill, width=max(1, int(width * SS)), joint="curve") def hatch(d, box, step, fill=INK_LIGHT, width=0.7, slant=0.0): x0, y0, x1, y1 = box y = y0 while y <= y1: line(d, [(x0, y), (x1, y + slant)], width, fill) y += step def ship(d, cx, base_y, scale, fill=INK): s = scale hull = [(cx - 52 * s, base_y - 10 * s), (cx + 55 * s, base_y - 14 * s), (cx + 40 * s, base_y + 6 * s), (cx - 38 * s, base_y + 8 * s)] d.polygon(P(hull), fill=INK) for mx, mh in ((-28, 62), (0, 78), (26, 58)): line(d, [(cx + mx * s, base_y - 10 * s), (cx + mx * s, base_y - 10 * s - mh * s)], 1.6 * s, INK) top = base_y - 10 * s - mh * s for k in range(2): y0 = top + (6 + k * 24) * s y1 = y0 + 20 * s d.polygon(P([(cx + (mx - 14) * s, y0), (cx + (mx + 14) * s, y0), (cx + (mx + 11) * s, y1), (cx + (mx - 11) * s, y1)]), fill=(238, 226, 196), outline=INK) line(d, [(cx - 52 * s, base_y - 10 * s), (cx - 78 * s, base_y - 24 * s)], 1.4 * s, INK) # gurupes def landing(): w, h = 525, 400 base, big, d = canvas(w, h, 1) hatch(d, (10, 205, w - 10, 330), 4.2, INK_LIGHT, 0.6) line(d, [(10, 205), (w - 10, 205)], 1.2, INK) # costa distante d.polygon(P([(300, 205), (360, 178), (420, 190), (470, 172), (515, 205)]), fill=INK_LIGHT) ship(d, 200, 205, 1.6) for i in range(14): x = 20 + i * 36 y = 225 + (i * 17) % 90 line(d, [(x, y), (x + 12, y - 3), (x + 24, y)], 1.0, INK) # praia com caixas e barris d.polygon(P([(10, 330), (160, 312), (330, 322), (515, 308), (515, 390), (10, 390)]), fill=(214, 190, 142)) for x, y in ((60, 345), (110, 355), (360, 340)): d.rectangle(P([(x, y), (x + 40, y + 28)]), outline=INK, width=2 * SS, fill=(205, 176, 128)) line(d, [(x, y + 10), (x + 40, y + 10)], 1.2) for x, y in ((250, 352), (300, 362), (440, 350)): d.ellipse(P([(x, y), (x + 34, y + 34)]), outline=INK, width=2 * SS, fill=(196, 160, 108)) line(d, [(x + 3, y + 11), (x + 31, y + 11)], 1.2) line(d, [(x + 3, y + 23), (x + 31, y + 23)], 1.2) finish(base, big, "landing.png") def star(cx, cy, r_out, r_in, n, rot=0.0): pts = [] for i in range(n * 2): a = rot + i * math.pi / n - math.pi / 2 r = r_out if i % 2 == 0 else r_in pts.append((cx + r * math.cos(a), cy + r * math.sin(a))) return pts def alert(): rate = 44100 n = int(rate * 0.9) frames = bytearray() for i in range(n): t = i / rate s = 0.0 for f, a, dec in ((880.0, 0.5, 5.5), (1318.5, 0.3, 7.0), (1760.0, 0.18, 9.0), (2349.3, 0.08, 12.0)): s += a * math.sin(2 * math.pi * f * t) * math.exp(-dec * t) s *= min(1.0, t / 0.004) v = int(max(-1, min(1, s)) * 0.6 * 32767) frames += struct.pack("