Files
tattoo-test/prep.gd
T
Can Narin ce4c4e7d13 Tattoo test bed: Quaternius v3-v6 bakes + Lena pipeline (bake, de-mirror, re-UV, viewers)
Session 2026-07-23/24 additions on top of the existing Quaternius bed:
- bake_lena.gd: 6-piece bake on QuatSkin Lena (chiefs-mark/iron-spine arms,
  voyagers/storm-bearer legs, star-eye chest, hearth-warmth back); global-axis
  wrap frame + 3cm junction blend kill the elbow split line
- demirror_lena.gd: tri-level split of mirror-shared limb UVs (arms 100%->0%,
  legs 99%->5% crotch-only residual)
- reuv_lena.gd: Quaternius-style semantic re-UV (16 islands, cylinder limbs +
  cylinder head/torso with planar caps) + texture transfer; fixes armpit/chin/
  cheek planar smears; single 4096 atlas, per-side tattoos native
- main_lena / anim_lena viewers (18 UAL clips, U light toggle) + probes + shots

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 00:47:12 +03:00

66 lines
2.4 KiB
GDScript

# Builds 1024² gen canvases from the per-part UV masks: the island silhouette
# (padded bbox, aspect-true, centered) as parchment white on black. The exact
# same transform is recomputed in composite.gd to map results back into UV space.
# Run headless:
# tinqs.console.exe --headless --path tattoo-test -s res://prep.gd
extends SceneTree
const MASK_DIR := "C:/Users/CAN/tinqs-ltd/tattoo-test/masks/"
const OUT_DIR := "C:/Users/CAN/tinqs-ltd/tattoo-test/gen-v4/"
const PARTS := ["arm_r", "arm_l", "leg_r", "leg_l", "chest", "back"]
const CANVAS := 1024
const MARGIN := 48 # silhouette keeps this distance from canvas edges
const PAD := 24 # px padding around the mask bbox in UV space
const PARCHMENT := Color(0.97, 0.94, 0.88)
func _init() -> void:
DirAccess.make_dir_recursive_absolute(OUT_DIR)
for part: String in PARTS:
var mask := Image.load_from_file(MASK_DIR + "mask_" + part + ".png")
var t := transform_for(mask)
var rect: Rect2i = t["rect"]
var s: float = t["scale"]
var off: Vector2 = t["offset"]
var canvas := Image.create(CANVAS, CANVAS, false, Image.FORMAT_RGBA8)
canvas.fill(Color(0, 0, 0))
for cy in CANVAS:
for cx in CANVAS:
var mx := int((cx - off.x) / s) + rect.position.x
var my := int((cy - off.y) / s) + rect.position.y
if mx < rect.position.x or my < rect.position.y \
or mx >= rect.end.x or my >= rect.end.y:
continue
if mask.get_pixel(mx, my).r > 0.5:
canvas.set_pixel(cx, cy, PARCHMENT)
var path := OUT_DIR + "canvas_" + part + ".png"
canvas.save_png(path)
print("CANVAS %s rect=%s scale=%.4f offset=%.1f,%.1f saved=%s" %
[part, rect, s, off.x, off.y, path])
quit()
# shared with composite.gd — keep in sync
static func transform_for(mask: Image) -> Dictionary:
var size := mask.get_width()
var minx := size
var maxx := -1
var miny := size
var maxy := -1
for y in size:
for x in size:
if mask.get_pixel(x, y).r > 0.5:
minx = mini(minx, x)
maxx = maxi(maxx, x)
miny = mini(miny, y)
maxy = maxi(maxy, y)
var rect := Rect2i(
maxi(minx - PAD, 0), maxi(miny - PAD, 0),
mini(maxx + PAD + 1, size) - maxi(minx - PAD, 0),
mini(maxy + PAD + 1, size) - maxi(miny - PAD, 0))
var s := minf(
float(CANVAS - 2 * MARGIN) / rect.size.x,
float(CANVAS - 2 * MARGIN) / rect.size.y)
var off := Vector2(
(CANVAS - rect.size.x * s) * 0.5,
(CANVAS - rect.size.y * s) * 0.5)
return {"rect": rect, "scale": s, "offset": off}