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

Documentation

  • docs/README.md
  • docs/getting-started.md
  • docs/config-reference.md
  • docs/renderer-architecture.md
  • docs/examples.md

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
tables List[TableElementConfig] Global table elements
charts List[ChartElementConfig] Global chart elements
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, tables, charts, texts). Default values: shapes=1, images=5, tables=6, charts=7, texts=10.

Tables and charts

from postcanvas.models import (
    TextAlign,
    TableCellAlignmentConfig,
    TableElementConfig,
    ChartElementConfig,
    ChartSeriesConfig,
    ChartType,
)

tables = [
    TableElementConfig(
        headers=["Metric", "Jan", "Feb", "Mar"],
        rows=[
            ["Reach", "28K", "31K", "37K"],
            ["Saves", "940", "1106", "1483"],
        ],
        text_align=TextAlign.LEFT,
        column_alignments=[TextAlign.LEFT, TextAlign.CENTER, TextAlign.CENTER, TextAlign.CENTER],
        cell_alignments=[
            TableCellAlignmentConfig(section="header", row=0, col=0, align=TextAlign.LEFT),
            TableCellAlignmentConfig(section="body", row=1, col=3, align=TextAlign.RIGHT),
        ],
        x="50%", y="56%", width="88%", height="52%", anchor="center",
    )
]

charts = [
    ChartElementConfig(
        type=ChartType.BAR,
        labels=["Reels", "Carousel", "Static"],
        series=[
            ChartSeriesConfig(name="Current", values=[8.9, 7.2, 4.1]),
            ChartSeriesConfig(name="Previous", values=[6.2, 5.8, 3.4]),
        ],
        x="50%", y="56%", width="90%", height="60%", anchor="center",
    )
]

Supported chart types: ChartType.BAR and ChartType.LINE.

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.2.0.tar.gz (29.5 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.2.0-py3-none-any.whl (36.9 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for postcanvas-0.2.0.tar.gz
Algorithm Hash digest
SHA256 9047ab25b5f33f5535f9f120ceedb8f54565da527049da8651e1ad12bcc4b798
MD5 f7f61d35928cafc79b9267dd22d95aea
BLAKE2b-256 02748e1fb3163ad805a340381c61960f78dd5d173ed513313f683bd04c46bee3

See more details on using hashes here.

Provenance

The following attestation bundles were made for postcanvas-0.2.0.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.2.0-py3-none-any.whl.

File metadata

  • Download URL: postcanvas-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 36.9 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.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 152d5d1992f051b2c4eff4984d7bec5d5290db803a2d96d159eff8e61ead5449
MD5 3e971fc95f5f8d4c063d5936de8e8cba
BLAKE2b-256 e6d55225374523300bc44650bf3649c0ef76e523cda0731d532228a704747d96

See more details on using hashes here.

Provenance

The following attestation bundles were made for postcanvas-0.2.0-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