2025, Dec 26 13:00

Creating a PSD with an editable text layer from PNG using psd-tools and Pillow: what works and why it fails in Krita/GIMP

Learn why psd-tools + Pillow create rasterized text layers in PSDs opened in Krita/GIMP, and what options exist to keep editable text or switch to open formats

Packaging a PNG together with an editable text layer into a PSD sounds trivial until you try to open the result in Krita or GIMP and discover the text isn’t actually editable. If you’re using psd-tools with Pillow, the text often ends up rasterized, which defeats the purpose of keeping a live text layer for later editing.

Repro: a minimal program that produces a non‑editable text layer

The following code builds a PSD from a PNG, draws text with Pillow, and writes both layers via psd-tools. The result opens, but the “text” is just pixels.

from PIL import Image, ImageDraw, ImageFont
from psd_tools.api.psd_image import PSDImage
from psd_tools.api.layers import PixelLayer
from psd_tools.constants import Compression

# Load base PNG artwork
src_png = Image.open('fire.png').convert('RGBA')

# Initialize a new PSD document
doc = PSDImage.new(mode='RGBA', size=src_png.size)

# Add the PNG as a bitmap layer
base_layer = PixelLayer.frompil(
    pil_im=src_png,
    psd_file=doc,
    layer_name='Base Art',
    compression=Compression.RLE
)
doc.append(base_layer)

# Text content and font size
label = "Random Text Example"
pt = 40

try:
    typeface = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", pt)
except IOError:
    typeface = ImageFont.load_default()

# Render text into a transparent bitmap
txt_bitmap = Image.new("RGBA", src_png.size, (0, 0, 0, 0))
pen = ImageDraw.Draw(txt_bitmap)
pen.text((50, 50), label, font=typeface, fill=(255, 0, 0, 255))

# Add the rendered text as another bitmap layer
caption_layer = PixelLayer.frompil(
    pil_im=txt_bitmap,
    psd_file=doc,
    layer_name='Caption',
    compression=Compression.RLE
)
doc.append(caption_layer)

# Save the PSD
doc.save('output_with_text.psd')

Why the text isn’t editable

The approach above draws glyphs into a bitmap and then inserts that bitmap into a PSD as a PixelLayer. That preserves the look but drops editability: Krita and GIMP see just pixels, not type information.

The deeper reason is structural. PSD is not an openly specified interchange format. As described in Krita’s documentation:

.psd, unlike actual interchange formats like *.pdf, *.tiff, *.exr, *.ora and *.svg doesn’t have an official spec online. Which means that it needs to be reverse engineered. Furthermore, as an internal file format, it doesn’t have much of a philosophy to its structure, as it’s only purpose is to save what Photoshop is busy with, or rather, what all the past versions of Photoshop have been busy with. This means that the inside of a PSD looks somewhat like Photoshop’s virtual brains, and PSD is in general a very disliked file-format.

Due to .psd being used as an interchange format, this leads to confusion amongst people using these programs, as to why not all programs support opening these. Sometimes, you might even see users saying that a certain program is terrible because it doesn’t support opening PSDs properly. But as PSD is an internal file-format without online specs, it is impossible to have any program outside it support it 100%.

That context explains why many non‑Adobe tools either don’t write true editable PSD text layers or, when they do, other apps can’t reliably read them as editable text. In practice, converting text to pixels is the common denominator that opens everywhere, but it sacrifices editability.

What actually solves it

If you specifically need an editable text layer inside a PSD, your options are constrained. A commercial SDK was mentioned as supporting editable text layers in PSD: aspose-psd, but this capability is a paid feature in its Python library and requires a license. Outside of that, the general guidance from modern editors is that PSD’s internals make vector and text layers “too difficult to program properly” for full compatibility across third‑party apps. One alternative that was considered is to switch to an open interchange format such as .ora (OpenRaster) instead of PSD. The Krita documentation explicitly lists *.pdf, *.tiff, *.exr, *.ora and *.svg as “actual interchange formats.” There was also a suggestion to use an online editor like Photopea, which perhaps offers an API for scripting vector text into a PSD, though that falls into the realm of proprietary tooling.

There is no code-only tweak to make the snippet produce editable text

Because the text is drawn into a bitmap and imported as a PixelLayer, there is no parameter change in psd-tools or Pillow that will transform it into a live, editable type layer understood by Krita/GIMP. The behavior is expected given the constraints above. If you accept rasterized text, the snippet is correct as-is.

Why this matters for engineering teams

Asset pipelines live or die by interoperability. Expecting PSD to behave like a clean, well-documented interchange format will lead to brittle integrations and unpleasant surprises. Editable text is a canary: if that fails across tools, other layer semantics won’t fare better. Picking formats based on documented guarantees avoids churn and keeps automation predictable.

Conclusion

If the goal is an editable text layer inside PSD that opens as editable in non‑Adobe editors, there isn’t a reliable open solution in this stack. Keep the current raster approach if visual fidelity is enough. If editability is a hard requirement, consider a licensed library that explicitly supports PSD text layers, or pivot to an actual interchange format like .ora as part of the pipeline. That simple choice up front prevents a lot of downstream friction.