Cap workers at half the cores to stop host freezes
This commit is contained in:
1 parent
724449f3f0
commit
a08151337b
1 file changed
+28
-11
+28
-11
@@ -219,7 +219,6 @@ def render_bitmap(ly: dict, img_path: Path, cw: int, ch: int
|
||||
Image.LANCZOS)
|
||||
L, T = math.floor(l), math.floor(t)
|
||||
layer = layer.crop((ix0 - L, iy0 - T, ix1 - L, iy1 - T))
|
||||
apply_opacity(layer, ly.get("alpha", 255))
|
||||
return layer, (ix0, iy0)
|
||||
|
||||
|
||||
@@ -269,7 +268,6 @@ def render_shape(ly: dict, cw: int, ch: int) -> Optional[Tuple[Image.Image, Tupl
|
||||
outline=255, width=thickness)
|
||||
img.paste(stroke_img, (0, 0), ring)
|
||||
|
||||
apply_opacity(img, ly.get("alpha", 255))
|
||||
# shadow (ppl renders a soft offset shadow per layer)
|
||||
sd = float(ly.get("shadowDistance", "0") or 0)
|
||||
sb = float(ly.get("shadowBlur", "0") or 0)
|
||||
@@ -352,7 +350,11 @@ def render_text(ly: dict, cw: int, ch: int) -> Optional[Tuple[Image.Image, Tuple
|
||||
return adv * c
|
||||
|
||||
grad = parse_gradient(ly.get("textColor", ""))
|
||||
fill = grad["colors"][0][1] if grad["colors"] else (0, 0, 0, 255)
|
||||
if grad["type"] == "gradient":
|
||||
fill = (255, 255, 255, 255) # base color; gradient is applied after
|
||||
grad = grad # keep for the mask pass
|
||||
else:
|
||||
fill = grad["colors"][0][1] if grad["colors"] else (0, 0, 0, 255)
|
||||
|
||||
# --- measure natural text size (multi-line aware, per-char spacing) ---
|
||||
lines = text.split("\n")
|
||||
@@ -428,7 +430,11 @@ def render_text(ly: dict, cw: int, ch: int) -> Optional[Tuple[Image.Image, Tuple
|
||||
return None
|
||||
img = img.crop((ix0 - left, iy0 - top, ix1 - left, iy1 - top))
|
||||
|
||||
apply_opacity(img, ly.get("alpha", 255))
|
||||
|
||||
# text gradient fill: paint a gradient over the glyph alpha
|
||||
if grad["type"] == "gradient":
|
||||
gimg = make_linear_gradient(img.width, img.height, grad)
|
||||
img.paste(gimg, (0, 0), img.getchannel("A"))
|
||||
|
||||
# soft shadow (used by rasterized output / previews)
|
||||
sd = float(ly.get("shadowDistance", "0") or 0)
|
||||
@@ -453,6 +459,16 @@ def apply_opacity(img: Image.Image, alpha: str) -> None:
|
||||
img.putalpha(alpha_ch)
|
||||
|
||||
|
||||
def _opacity_paste(canvas: Image.Image, img: Image.Image, pos, opacity: int) -> None:
|
||||
"""Composite a layer onto the merged/preview canvas, applying the layer
|
||||
opacity (PSD opacity field is applied by Photoshop itself, but the
|
||||
in-tool merged preview must apply it manually)."""
|
||||
if opacity < 255:
|
||||
img = img.copy()
|
||||
img.putalpha(img.getchannel("A").point(lambda p: p * opacity // 255))
|
||||
canvas.paste(img, pos, img)
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# PSD writer
|
||||
# --------------------------------------------------------------------------
|
||||
@@ -744,7 +760,7 @@ def build_psd(layers, width: int, height: int, out_path: str, workers: int,
|
||||
for layer in layers:
|
||||
name, opacity, visible, (lx, ly), img = layer[:5]
|
||||
if img is not None and visible and opacity > 0:
|
||||
merged.paste(img, (lx, ly), img)
|
||||
_opacity_paste(merged, img, (lx, ly), opacity)
|
||||
merged_rgb = None # keep the RGBA merged image for the 4-channel output
|
||||
# Merged image data is written RAW with a single compression field and
|
||||
# all planes in planar order (R G B A). 4 channels because the document
|
||||
@@ -784,9 +800,10 @@ def build_psd(layers, width: int, height: int, out_path: str, workers: int,
|
||||
# Main
|
||||
# --------------------------------------------------------------------------
|
||||
def _safe_workers(workers: int) -> int:
|
||||
"""Never use every CPU core: leave one for the system, otherwise the
|
||||
RLE-encoding workers can stall the whole device."""
|
||||
available = max(1, (os.cpu_count() or 2) - 1)
|
||||
"""Keep at least half the CPU cores free - on phones the RLE-encoding
|
||||
workers plus PIL's internal threading can otherwise stall the whole
|
||||
device (a previous cores-1 cap still froze the host)."""
|
||||
available = max(1, (os.cpu_count() or 2) // 2)
|
||||
return max(1, min(workers, available))
|
||||
|
||||
|
||||
@@ -919,7 +936,7 @@ def convert(ppl_path: str, out_path: str, preview_path: Optional[str] = None,
|
||||
for layer in rendered:
|
||||
name, opacity, visible, (lx, ly), img = layer[:5]
|
||||
if img is not None and visible and opacity > 0:
|
||||
canvas.paste(img, (lx, ly), img)
|
||||
_opacity_paste(canvas, img, (lx, ly), opacity)
|
||||
# downscale for easy viewing
|
||||
preview = canvas.convert("RGB")
|
||||
if preview.width > 2000:
|
||||
@@ -937,8 +954,8 @@ def main():
|
||||
ap.add_argument("-o", "--output", help="output .psd path")
|
||||
ap.add_argument("--preview", help="also write a downscaled composite PNG")
|
||||
ap.add_argument("-j", "--workers", type=int,
|
||||
default=max(1, (os.cpu_count() or 2) - 1),
|
||||
help="parallel workers for RLE encoding (default: cores-1)")
|
||||
default=max(1, (os.cpu_count() or 2) // 2),
|
||||
help="parallel workers for RLE encoding (default: cores/2)")
|
||||
ap.add_argument("--type-layer", action="store_true",
|
||||
help="convert text layers to editable TypeLayers (TySh) "
|
||||
"instead of rasterized pixels")
|
||||
|
||||
Reference in new issue
Block a user