Skip to main content

Generate pixel-perfect social media images from Python Pydantic models

Project description

postcanvas 🎨

Generate pixel-perfect social-media images from Python — just describe what you want.

Install

pip install postcanvas

Quick start

from postcanvas import generate
from postcanvas.presets import instagram_post
from postcanvas.models import BackgroundConfig, TextConfig, ShadowConfig

post = instagram_post(
    background=BackgroundConfig(color="#1a1a2e"),
    texts=[
        TextConfig(
            content="Hello World!",
            y="50%",
            font_size=96,
            color="#e94560",
            shadow=ShadowConfig(blur_radius=12),
        )
    ],
    output_dir="./output",
)

generate(post)   # → ./output/post.png

Cloud Storage & Return Raw Images

Generate images without saving to disk for direct cloud uploads:

from postcanvas import generate, image_to_bytes, GenerateResult
from postcanvas.models import OutputFormat

# Get raw PIL Images without saving to disk
images = generate(post, save=False, return_images=True)

# Convert to bytes for cloud storage
for img in images:
    data = image_to_bytes(img, format=OutputFormat.PNG)
    # s3_client.put_object(Bucket='bucket', Key='image.png', Body=data)

# Or save to custom locations
from postcanvas import save_image_to_path
save_image_to_path(images[0], "./custom/path/image.png")

# Get both: save locally AND return images
result = generate(post, save=True, return_images=True)  # Returns GenerateResult
print(result.paths)    # List of saved file paths
print(result.images)   # List of PIL Image objects

Generate Parameters & Return Types

generate(
    post,
    save=True,              # Save to disk
    return_images=False     # Return PIL Image objects
)

Return types:

  • save=True, return_images=False (default) → List[str] (file paths only)
  • save=False, return_images=TrueList[Image.Image] (PIL Images only)
  • save=True, return_images=TrueGenerateResult (consistent dataclass with both)

Error handling:

  • save=False, return_images=False → raises ValueError (must specify at least one output type)

Platforms & formats

Helper Size
instagram_post() 1080 × 1080
instagram_portrait() 1080 × 1350
instagram_story() 1080 × 1920
x_post() 1600 × 900
reddit_post() 1920 × 1080
blog_og() 1200 × 628
linkedin_post() 1080 × 1080
youtube_thumbnail() 1280 × 720
facebook_post() 1080 × 1080
tiktok_story() 1080 × 1920

Use preset(Platform.CUSTOM, PostFormat.CUSTOM, width=800, height=600) for custom sizes.

Carousel / multi-image

from postcanvas.models import CanvasConfig, BackgroundConfig

post = instagram_post(
    canvases=[
        CanvasConfig(background=BackgroundConfig(color="#e94560"),
                     texts=[TextConfig(content="Slide 1", ...)]),
        CanvasConfig(background=BackgroundConfig(color="#0f3460"),
                     texts=[TextConfig(content="Slide 2", ...)]),
    ]
)

Key model reference

PostConfig (root)

Field Type Description
platform Platform Target platform
width / height int Canvas size in px
background BackgroundConfig Global background
padding PaddingConfig Safe-area insets
texts List[TextConfig] Global text elements
images List[ImageElementConfig] Global image elements
shapes List[ShapeConfig] Global shapes
canvases List[CanvasConfig] Slides (carousel)
watermark WatermarkConfig Applied to every slide
output_dir str Where to save files
output_format OutputFormat png / jpeg / webp

Positioning

Every x, y, width, height accepts:

  • Absolute pixels: 540, 200
  • Relative string: "50%", "80%"

Anchors

anchor can be: topleft, topcenter, topright, left, center, right, bottomleft, bottomcenter, bottomright

z_index

Elements are composited in ascending z_index order across all types (shapes, images, texts). Default values: shapes=1, images=5, texts=10.

Text inside images and shapes

Both ImageElementConfig and ShapeConfig now support a texts list:

from postcanvas.models import ImageElementConfig, ShapeConfig, ShapeType, TextConfig

ShapeConfig(
    type=ShapeType.ROUNDED_RECTANGLE,
    x="50%", y="35%", width="70%", height="30%", anchor="center",
    fill_color="#1f3b4d",
    texts=[
        TextConfig(content="Inside Shape", x="50%", y="50%", anchor="center")
    ],
)

ImageElementConfig(
    src="assets/photo.jpg",
    x="50%", y="70%", width="60%", height="35%", anchor="center",
    texts=[
        TextConfig(content="Inside Image", x="50%", y="88%", anchor="bottomcenter")
    ],
)

Nested text coordinates are resolved relative to the element's own box, not the full canvas.

Font inheritance (Post > Canvas > Text override)

You can define default text font at post level, override it per canvas, and still override per text:

from postcanvas.presets import instagram_post
from postcanvas.models import CanvasConfig, TextConfig

post = instagram_post(
    text_font_path="Roboto/static/Roboto-Regular.ttf",   # default for whole post
    texts=[
        TextConfig(content="Uses post default", x="50%", y="15%"),
        TextConfig(content="Custom text font", x="50%", y="25%", font_path="Roboto/static/Roboto-Bold.ttf"),
    ],
    canvases=[
        CanvasConfig(
            text_font_path="Roboto/static/Roboto-Italic.ttf",  # overrides post default on this slide
            texts=[
                TextConfig(content="Uses canvas override", x="50%", y="50%"),
                TextConfig(content="Text-level still wins", x="50%", y="60%", font_path="Roboto/static/Roboto-Medium.ttf"),
            ],
        )
    ],
)

Precedence: TextConfig > CanvasConfig > PostConfig > internal Arial fallback.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

postcanvas-0.1.1.tar.gz (22.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

postcanvas-0.1.1-py3-none-any.whl (28.3 kB view details)

Uploaded Python 3

File details

Details for the file postcanvas-0.1.1.tar.gz.

File metadata

  • Download URL: postcanvas-0.1.1.tar.gz
  • Upload date:
  • Size: 22.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for postcanvas-0.1.1.tar.gz
Algorithm Hash digest
SHA256 5147b64fde37d9a9e68f9e34c331bbffdb2bfb9fe1501c331c87c5ee9032099c
MD5 99555bf555a66e8f48f522bf66693c6e
BLAKE2b-256 0b11ae23fdf78f63b87b35bfc96e85e33e548f3a5afd3175341881320955149d

See more details on using hashes here.

Provenance

The following attestation bundles were made for postcanvas-0.1.1.tar.gz:

Publisher: publish-pypi.yml on ghedo44/postcanvas

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file postcanvas-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: postcanvas-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 28.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for postcanvas-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a8140b69f05cf58d04964373b2f2e36a3982bbceb068be26cf762685b2cdad97
MD5 0c328731eba1cfa495393a0eacb07e1e
BLAKE2b-256 fcf4cfb70374fccee52ca1654f9db51a8b643535ba75ea6d64cc309a59e15ebe

See more details on using hashes here.

Provenance

The following attestation bundles were made for postcanvas-0.1.1-py3-none-any.whl:

Publisher: publish-pypi.yml on ghedo44/postcanvas

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page