quickthumb 0.3.0__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- quickthumb-0.3.1/.github/workflows/docs.yml +26 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/.gitignore +3 -0
- quickthumb-0.3.1/DESIGN.md +573 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/PKG-INFO +5 -1
- {quickthumb-0.3.0 → quickthumb-0.3.1}/PROGRESS.md +14 -12
- {quickthumb-0.3.0 → quickthumb-0.3.1}/README.md +4 -0
- quickthumb-0.3.1/docs/api/background.md +78 -0
- quickthumb-0.3.1/docs/api/canvas.md +141 -0
- quickthumb-0.3.1/docs/api/effects.md +195 -0
- quickthumb-0.3.1/docs/api/enums.md +194 -0
- quickthumb-0.3.1/docs/api/image.md +131 -0
- quickthumb-0.3.1/docs/api/index.md +43 -0
- quickthumb-0.3.1/docs/api/outline.md +61 -0
- quickthumb-0.3.1/docs/api/shape.md +110 -0
- quickthumb-0.3.1/docs/api/text.md +194 -0
- quickthumb-0.3.1/docs/assets/examples/podcast_interview_promo.png +0 -0
- quickthumb-0.3.1/docs/assets/examples/shorts_cover_agent.png +0 -0
- quickthumb-0.3.1/docs/assets/examples/youtube_reaction.png +0 -0
- quickthumb-0.3.1/docs/assets/examples/youtube_talking_head.png +0 -0
- quickthumb-0.3.1/docs/assets/examples/youtube_tutorial_explainer.png +0 -0
- quickthumb-0.3.1/docs/changelog.md +5 -0
- quickthumb-0.3.1/docs/concepts.md +255 -0
- quickthumb-0.3.1/docs/cookbook/ai-workflow.md +182 -0
- quickthumb-0.3.1/docs/cookbook/index.md +46 -0
- quickthumb-0.3.1/docs/cookbook/instagram-card.md +129 -0
- quickthumb-0.3.1/docs/cookbook/podcast-promo.md +214 -0
- quickthumb-0.3.1/docs/cookbook/shorts-cover.md +208 -0
- quickthumb-0.3.1/docs/cookbook/webfonts-rembg.md +199 -0
- quickthumb-0.3.1/docs/cookbook/youtube-thumbnail.md +348 -0
- quickthumb-0.3.1/docs/faq.md +170 -0
- quickthumb-0.3.1/docs/getting-started.md +206 -0
- quickthumb-0.3.1/docs/index.md +59 -0
- quickthumb-0.3.1/docs/installation.md +56 -0
- quickthumb-0.3.1/docs/json-schema.md +365 -0
- quickthumb-0.3.1/docs/stylesheets/extra.css +83 -0
- quickthumb-0.3.1/examples/README.md +155 -0
- quickthumb-0.3.1/examples/instagram_news_card.png +0 -0
- quickthumb-0.3.1/examples/podcast_interview_promo.png +0 -0
- quickthumb-0.3.1/examples/podcast_interview_promo.py +146 -0
- quickthumb-0.3.1/examples/shorts_cover_agent.json +301 -0
- quickthumb-0.3.1/examples/shorts_cover_agent.png +0 -0
- quickthumb-0.3.1/examples/shorts_cover_agent.py +33 -0
- quickthumb-0.3.1/examples/youtube_reaction.png +0 -0
- quickthumb-0.3.1/examples/youtube_reaction.py +103 -0
- quickthumb-0.3.1/examples/youtube_talking_head.png +0 -0
- quickthumb-0.3.1/examples/youtube_talking_head.py +102 -0
- quickthumb-0.3.1/examples/youtube_thumbnail_01.png +0 -0
- quickthumb-0.3.1/examples/youtube_thumbnail_02.png +0 -0
- quickthumb-0.3.1/examples/youtube_tutorial_explainer.png +0 -0
- quickthumb-0.3.1/examples/youtube_tutorial_explainer.py +136 -0
- quickthumb-0.3.1/mkdocs.yml +87 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/pyproject.toml +4 -1
- {quickthumb-0.3.0 → quickthumb-0.3.1}/quickthumb/canvas.py +81 -38
- quickthumb-0.3.1/tests/snapshots/explicit_newlines_preserved_with_max_width.png +0 -0
- quickthumb-0.3.1/tests/snapshots/image_with_drop_shadow.png +0 -0
- quickthumb-0.3.1/tests/snapshots/multiline_text_with_background.png +0 -0
- quickthumb-0.3.1/tests/snapshots/shape_with_effects.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/test_rendering.py +73 -3
- {quickthumb-0.3.0 → quickthumb-0.3.1}/uv.lock +396 -1
- quickthumb-0.3.0/DESIGN.md +0 -476
- quickthumb-0.3.0/examples/README.md +0 -78
- quickthumb-0.3.0/tests/snapshots/image_with_drop_shadow.png +0 -0
- quickthumb-0.3.0/tests/snapshots/shape_with_effects.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/.claude/settings.json +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/.claude/skills/tdd/SKILL.md +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/.github/workflows/publish.yaml +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/.vscode/extensions.json +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/.vscode/settings.json +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/CLAUDE.md +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/LICENSE +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSans-Black.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSans-BlackItalic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSans-Bold.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSans-BoldItalic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSans-ExtraBold.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSans-ExtraBoldItalic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSans-ExtraLight.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSans-ExtraLightItalic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSans-Italic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSans-Light.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSans-LightItalic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSans-Medium.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSans-MediumItalic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSans-Regular.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSans-SemiBold.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSans-SemiBoldItalic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSans-Thin.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSans-ThinItalic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSerif-Black.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSerif-BlackItalic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSerif-Bold.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSerif-BoldItalic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSerif-ExtraBold.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSerif-ExtraBoldItalic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSerif-ExtraLight.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSerif-ExtraLightItalic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSerif-Italic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSerif-Light.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSerif-LightItalic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSerif-Medium.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSerif-MediumItalic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSerif-Regular.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSerif-SemiBold.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSerif-SemiBoldItalic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSerif-Thin.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/NotoSerif-ThinItalic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/Roboto-Black.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/Roboto-BlackItalic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/Roboto-Bold.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/Roboto-BoldItalic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/Roboto-ExtraBold.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/Roboto-ExtraBoldItalic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/Roboto-ExtraLight.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/Roboto-ExtraLightItalic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/Roboto-Italic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/Roboto-Light.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/Roboto-LightItalic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/Roboto-Medium.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/Roboto-MediumItalic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/Roboto-Regular.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/Roboto-SemiBold.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/Roboto-SemiBoldItalic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/Roboto-Thin.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/fonts/Roboto-ThinItalic.ttf +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/images/c-g-JgDUVGAXsso-unsplash.jpg +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/images/denise-jans-WIRvXd1PYlg-unsplash.jpg +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/assets/images/tobias-rademacher-wnF27F85ZKw-unsplash.jpg +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1/docs/assets}/examples/instagram_news_card.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1/docs/assets}/examples/youtube_thumbnail_01.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1/docs/assets}/examples/youtube_thumbnail_02.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/examples/instagram_news_card.py +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/examples/youtube_thumbnail_01.py +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/examples/youtube_thumbnail_02.py +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/quickthumb/__init__.py +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/quickthumb/errors.py +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/quickthumb/font_cache.py +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/quickthumb/models.py +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/__init__.py +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/conftest.py +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/fixtures/ivana-cajina-_7LbC5J-jw4-unsplash.jpg +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/fixtures/sample_image.jpg +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/fixtures/tobias-rademacher-wnF27F85ZKw-unsplash.jpg +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/arial_bold.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/arial_bold_italic.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/arial_italic.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/auto_scale_rich_text.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/auto_scale_simple_text.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/background_blur_heavy.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/background_blur_soft.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/background_combined_filters.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/background_contrast_high.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/background_contrast_low.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/background_filter_effect.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/background_saturation_grayscale.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/background_saturation_vivid.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/blend_mode_darken.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/blend_mode_lighten.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/blend_mode_multiply.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/blend_mode_normal.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/blend_mode_overlay.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/blend_mode_screen.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/bold_italic_with_named_font.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/bold_with_named_font.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/custom_canvas_layer.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/font_name_works.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_background_basic.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_background_with_opacity.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_basic.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_blend_mode_darken.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_blend_mode_lighten.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_blend_mode_multiply.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_blend_mode_normal.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_blend_mode_overlay.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_blend_mode_screen.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_brightness_decrease.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_brightness_increase.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_filter_effect.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_fit_contain.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_fit_cover.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_fit_fill.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_on_background_image.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_overlay_fit_contain.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_overlay_fit_cover.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_overlay_fit_fill.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_percentage_position.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_with_border_radius.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_with_glow.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_with_opacity.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_with_remove_background.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_with_rotation.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_with_stroke.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/image_with_text_overlay.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/italic_with_named_font.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/jpeg_format.jpg +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/linear_gradient_brightness_decrease.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/linear_gradient_brightness_increase.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/linear_gradient_diagonal.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/linear_gradient_horizontal.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/linear_gradient_with_alpha.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/linear_gradient_with_opacity.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/multiline_text_with_glow.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/multiline_text_with_shadow.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/multiple_images.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/outline_basic.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/outline_with_offset.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/percentage_with_alignment.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/radial_gradient_brightness_decrease.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/radial_gradient_brightness_increase.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/radial_gradient_centered.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/radial_gradient_with_opacity.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/rich_text_advanced_styles.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/rich_text_alignment.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/rich_text_different_colors.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/rich_text_mixed_fonts.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/rich_text_mixed_styles.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/rich_text_with_background.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/rich_text_with_effects.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/roboto_bold.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/roboto_bold_italic.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/roboto_italic.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/shape_ellipse_basic.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/shape_rectangle_basic.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/shape_rectangle_with_border_radius.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/shape_with_opacity.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/shape_with_rotation.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/solid_background.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/solid_color_brightness_decrease.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/solid_color_brightness_increase.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/text_alignment.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/text_and_outline_with_opacity.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/text_bold_and_italic.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/text_no_wrapping.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/text_rendering.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/text_rotation_basic.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/text_rotation_centered.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/text_rotation_with_effects.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/text_weight_black.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/text_weight_light.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/text_with_background_and_effects.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/text_with_background_basic.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/text_with_basic_shadow.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/text_with_blurred_shadow.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/text_with_glow.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/text_with_letter_spacing.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/text_with_letter_spacing_glow.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/text_with_letter_spacing_shadow.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/text_with_line_height.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/text_with_multiple_glows.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/text_with_stroke.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/text_wrapping_center_aligned.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/text_wrapping_center_aligned_percentage.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/text_wrapping_left_aligned.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/text_wrapping_right_aligned.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/times_new_roman_bold.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/times_new_roman_bold_italic.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/times_new_roman_italic.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/url_image_background_basic.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/webfont_basic_rendering.png +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/snapshots/webp_format.webp +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/test_background_layers.py +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/test_canvas.py +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/test_font_cache.py +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/test_image_layers.py +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/test_outline_layers.py +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/test_shape_layers.py +0 -0
- {quickthumb-0.3.0 → quickthumb-0.3.1}/tests/test_text_layers.py +0 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: Deploy docs
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*.*.*"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: write
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
deploy:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: astral-sh/setup-uv@v5
|
|
19
|
+
with:
|
|
20
|
+
enable-cache: true
|
|
21
|
+
|
|
22
|
+
- name: Install docs dependencies
|
|
23
|
+
run: uv sync --group docs
|
|
24
|
+
|
|
25
|
+
- name: Deploy to GitHub Pages
|
|
26
|
+
run: uv run --group docs mkdocs gh-deploy --force
|
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
# QuickThumb API Reference
|
|
2
|
+
|
|
3
|
+
This document reflects the current implemented API in this repository.
|
|
4
|
+
It is intended to be the reliable reference for writing QuickThumb code, generating QuickThumb JSON, and validating what the library actually supports today.
|
|
5
|
+
|
|
6
|
+
## Core Model
|
|
7
|
+
|
|
8
|
+
- A `Canvas` is an ordered list of layers.
|
|
9
|
+
- Layers render in call order: first added is the backmost layer.
|
|
10
|
+
- All layer builder methods mutate the canvas and return `self`.
|
|
11
|
+
- Most canvases can round-trip through JSON with `to_json()` and `from_json()`.
|
|
12
|
+
- Custom callback layers are intentionally excluded from JSON serialization.
|
|
13
|
+
|
|
14
|
+
## Public Imports
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from quickthumb import (
|
|
18
|
+
Align,
|
|
19
|
+
Background,
|
|
20
|
+
BlendMode,
|
|
21
|
+
Canvas,
|
|
22
|
+
Filter,
|
|
23
|
+
FitMode,
|
|
24
|
+
Glow,
|
|
25
|
+
LinearGradient,
|
|
26
|
+
RadialGradient,
|
|
27
|
+
Shadow,
|
|
28
|
+
Stroke,
|
|
29
|
+
TextPart,
|
|
30
|
+
ValidationError,
|
|
31
|
+
)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Canvas API
|
|
35
|
+
|
|
36
|
+
### Creation
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from quickthumb import Canvas
|
|
40
|
+
|
|
41
|
+
canvas = Canvas(1280, 720)
|
|
42
|
+
wide = Canvas.from_aspect_ratio("16:9", base_width=1280)
|
|
43
|
+
square = Canvas.from_aspect_ratio("1:1", base_width=1080)
|
|
44
|
+
vertical = Canvas.from_aspect_ratio("9:16", base_width=1080)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Rules:
|
|
48
|
+
|
|
49
|
+
- `width` and `height` must be positive integers.
|
|
50
|
+
- `from_aspect_ratio()` expects a string like `"16:9"` plus `base_width`.
|
|
51
|
+
|
|
52
|
+
### Layer Builders
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
canvas.background(...)
|
|
56
|
+
canvas.text(...)
|
|
57
|
+
canvas.outline(...)
|
|
58
|
+
canvas.shape(...)
|
|
59
|
+
canvas.image(...)
|
|
60
|
+
canvas.custom(fn)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Export Methods
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
canvas.render("output.png")
|
|
67
|
+
canvas.render("output.webp", format="WEBP", quality=90)
|
|
68
|
+
|
|
69
|
+
png_base64 = canvas.to_base64(format="PNG")
|
|
70
|
+
jpeg_data_url = canvas.to_data_url(format="JPEG", quality=90)
|
|
71
|
+
|
|
72
|
+
json_data = canvas.to_json()
|
|
73
|
+
canvas = Canvas.from_json(json_data)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Rules:
|
|
77
|
+
|
|
78
|
+
- Supported output formats are `PNG`, `JPEG`, and `WEBP`.
|
|
79
|
+
- `quality` is only valid for `JPEG` and `WEBP`.
|
|
80
|
+
- `Canvas.from_json()` accepts a JSON string, not a Python dict.
|
|
81
|
+
|
|
82
|
+
## Layer Reference
|
|
83
|
+
|
|
84
|
+
### Background Layer
|
|
85
|
+
|
|
86
|
+
Adds a full-canvas background layer.
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from quickthumb import Canvas, Filter, LinearGradient
|
|
90
|
+
|
|
91
|
+
canvas = (
|
|
92
|
+
Canvas(1280, 720)
|
|
93
|
+
.background(color="#0F172A")
|
|
94
|
+
.background(
|
|
95
|
+
gradient=LinearGradient(
|
|
96
|
+
angle=120,
|
|
97
|
+
stops=[("#111827", 0.0), ("#11182700", 1.0)],
|
|
98
|
+
),
|
|
99
|
+
opacity=0.7,
|
|
100
|
+
)
|
|
101
|
+
.background(
|
|
102
|
+
image="https://images.unsplash.com/photo-1516321318423-f06f85e504b3",
|
|
103
|
+
fit="cover",
|
|
104
|
+
blend_mode="multiply",
|
|
105
|
+
effects=[Filter(blur=4, brightness=0.75, contrast=1.1, saturation=0.9)],
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Parameters:
|
|
111
|
+
|
|
112
|
+
- `color`: hex string (`"#RRGGBB"` or `"#RRGGBBAA"`) or RGB/RGBA tuple
|
|
113
|
+
- `gradient`: `LinearGradient` or `RadialGradient`
|
|
114
|
+
- `image`: local file path or remote URL
|
|
115
|
+
- `opacity`: float from `0.0` to `1.0`
|
|
116
|
+
- `blend_mode`: `multiply`, `overlay`, `screen`, `darken`, `lighten`, or `normal`
|
|
117
|
+
- `fit`: `cover`, `contain`, or `fill`
|
|
118
|
+
- `effects`: background effects list, currently `Filter` only
|
|
119
|
+
|
|
120
|
+
Notes:
|
|
121
|
+
|
|
122
|
+
- Background layers cover the full canvas.
|
|
123
|
+
- Background blend modes apply when compositing over previous layers.
|
|
124
|
+
- Backgrounds support local images and remote URLs.
|
|
125
|
+
|
|
126
|
+
### Text Layer
|
|
127
|
+
|
|
128
|
+
Adds a text layer using either a plain string or rich text parts.
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from quickthumb import Background, Canvas, Glow, Shadow, Stroke, TextPart
|
|
132
|
+
|
|
133
|
+
canvas = Canvas(1280, 720).text(
|
|
134
|
+
content=[
|
|
135
|
+
TextPart(text="BUILD ", color="#B8FF00", weight=900),
|
|
136
|
+
TextPart(text="FASTER", color="#FFFFFF", effects=[Stroke(width=4, color="#000000")]),
|
|
137
|
+
],
|
|
138
|
+
font="Impact",
|
|
139
|
+
size=110,
|
|
140
|
+
color="#FFFFFF",
|
|
141
|
+
position=("8%", "50%"),
|
|
142
|
+
align=("left", "middle"),
|
|
143
|
+
max_width="60%",
|
|
144
|
+
line_height=1.0,
|
|
145
|
+
letter_spacing=1,
|
|
146
|
+
effects=[
|
|
147
|
+
Background(color="#111827CC", padding=(16, 24), border_radius=14),
|
|
148
|
+
Shadow(offset_x=4, offset_y=4, color="#000000", blur_radius=8),
|
|
149
|
+
Glow(color="#B8FF00", radius=16, opacity=0.25),
|
|
150
|
+
],
|
|
151
|
+
rotation=0,
|
|
152
|
+
opacity=1.0,
|
|
153
|
+
)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Parameters:
|
|
157
|
+
|
|
158
|
+
- `content`: required; either a string or `list[TextPart]`
|
|
159
|
+
- `font`: font family name, font file path, or webfont URL
|
|
160
|
+
- `size`: positive integer font size
|
|
161
|
+
- `color`: default text color for the layer
|
|
162
|
+
- `position`: optional `(x, y)` tuple using pixels or percentage strings
|
|
163
|
+
- `align`: optional `Align`, string alias, or `(horizontal, vertical)` tuple
|
|
164
|
+
- `bold`: legacy bold flag
|
|
165
|
+
- `italic`: italic flag
|
|
166
|
+
- `weight`: CSS-style font weight as int or name
|
|
167
|
+
- `max_width`: wrap width in pixels or percentage string
|
|
168
|
+
- `effects`: any mix of `Stroke`, `Shadow`, `Glow`, and text `Background`
|
|
169
|
+
- `line_height`: positive float
|
|
170
|
+
- `letter_spacing`: integer tracking adjustment
|
|
171
|
+
- `auto_scale`: shrink text until it fits `max_width`
|
|
172
|
+
- `rotation`: degrees
|
|
173
|
+
- `opacity`: float from `0.0` to `1.0`
|
|
174
|
+
|
|
175
|
+
Notes:
|
|
176
|
+
|
|
177
|
+
- `content` is required.
|
|
178
|
+
- `align` accepts values like `"center"`, `"top-left"`, `"bottom-right"`, or tuples like `("left", "middle")`.
|
|
179
|
+
- `font` may be a remote URL; QuickThumb downloads and caches it.
|
|
180
|
+
- `weight` is supported on both full text layers and individual `TextPart` entries.
|
|
181
|
+
|
|
182
|
+
### Outline Layer
|
|
183
|
+
|
|
184
|
+
Adds a border around the full canvas.
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
canvas.outline(width=12, color="#B8FF00", offset=0, opacity=1.0)
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Parameters:
|
|
191
|
+
|
|
192
|
+
- `width`: positive integer
|
|
193
|
+
- `color`: hex string
|
|
194
|
+
- `offset`: non-negative integer inset/outset offset
|
|
195
|
+
- `opacity`: float from `0.0` to `1.0`
|
|
196
|
+
|
|
197
|
+
### Shape Layer
|
|
198
|
+
|
|
199
|
+
Adds a positioned rectangle or ellipse.
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
from quickthumb import Canvas, Shadow, Stroke
|
|
203
|
+
|
|
204
|
+
canvas = Canvas(1280, 720).shape(
|
|
205
|
+
shape="rectangle",
|
|
206
|
+
position=(64, 64),
|
|
207
|
+
width=360,
|
|
208
|
+
height=120,
|
|
209
|
+
color="#CC0000",
|
|
210
|
+
border_radius=16,
|
|
211
|
+
opacity=0.95,
|
|
212
|
+
rotation=-4,
|
|
213
|
+
align=("left", "top"),
|
|
214
|
+
effects=[
|
|
215
|
+
Stroke(width=3, color="#FFFFFF"),
|
|
216
|
+
Shadow(offset_x=0, offset_y=10, color="#000000", blur_radius=18),
|
|
217
|
+
],
|
|
218
|
+
)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Parameters:
|
|
222
|
+
|
|
223
|
+
- `shape`: `"rectangle"` or `"ellipse"`
|
|
224
|
+
- `position`: required `(x, y)` tuple using pixels or percentage strings
|
|
225
|
+
- `width`: positive integer
|
|
226
|
+
- `height`: positive integer
|
|
227
|
+
- `color`: hex string
|
|
228
|
+
- `border_radius`: non-negative integer
|
|
229
|
+
- `opacity`: float from `0.0` to `1.0`
|
|
230
|
+
- `rotation`: degrees
|
|
231
|
+
- `align`: optional `Align`, string alias, or `(horizontal, vertical)` tuple
|
|
232
|
+
- `effects`: any mix of `Stroke`, `Shadow`, and `Glow`
|
|
233
|
+
|
|
234
|
+
### Image Layer
|
|
235
|
+
|
|
236
|
+
Adds an overlay image or cutout.
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
from quickthumb import Canvas, Filter, Shadow
|
|
240
|
+
|
|
241
|
+
canvas = Canvas(1280, 720).image(
|
|
242
|
+
path="portrait.png",
|
|
243
|
+
position=("74%", "54%"),
|
|
244
|
+
width=420,
|
|
245
|
+
height=520,
|
|
246
|
+
fit="cover",
|
|
247
|
+
opacity=1.0,
|
|
248
|
+
rotation=0,
|
|
249
|
+
align=("center", "middle"),
|
|
250
|
+
remove_background=True,
|
|
251
|
+
border_radius=28,
|
|
252
|
+
blend_mode="normal",
|
|
253
|
+
effects=[
|
|
254
|
+
Filter(contrast=1.1, saturation=1.05),
|
|
255
|
+
Shadow(offset_x=0, offset_y=12, color="#000000", blur_radius=24),
|
|
256
|
+
],
|
|
257
|
+
)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Parameters:
|
|
261
|
+
|
|
262
|
+
- `path`: required local file path or remote URL
|
|
263
|
+
- `position`: required `(x, y)` tuple using pixels or percentage strings
|
|
264
|
+
- `width`: optional positive integer
|
|
265
|
+
- `height`: optional positive integer
|
|
266
|
+
- `fit`: `cover`, `contain`, or `fill` when width and height define a target box
|
|
267
|
+
- `opacity`: float from `0.0` to `1.0`
|
|
268
|
+
- `rotation`: degrees
|
|
269
|
+
- `align`: `Align`, string alias, or `(horizontal, vertical)` tuple
|
|
270
|
+
- `remove_background`: requires `quickthumb[rembg]`
|
|
271
|
+
- `border_radius`: non-negative integer
|
|
272
|
+
- `effects`: any mix of `Stroke`, `Shadow`, `Glow`, and `Filter`
|
|
273
|
+
- `blend_mode`: `multiply`, `overlay`, `screen`, `darken`, `lighten`, or `normal`
|
|
274
|
+
|
|
275
|
+
Notes:
|
|
276
|
+
|
|
277
|
+
- If only one of `width` or `height` is set, aspect ratio is preserved.
|
|
278
|
+
- `fit` matters when both `width` and `height` define a box.
|
|
279
|
+
- Image blend modes apply during compositing onto prior layers.
|
|
280
|
+
|
|
281
|
+
### Custom Layer
|
|
282
|
+
|
|
283
|
+
Adds a callback that can draw directly onto the rendered Pillow image.
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
from PIL import ImageDraw
|
|
287
|
+
from quickthumb import Canvas
|
|
288
|
+
|
|
289
|
+
def add_badge(image):
|
|
290
|
+
draw = ImageDraw.Draw(image)
|
|
291
|
+
draw.polygon([(70, 70), (240, 70), (155, 180)], fill="#FF3B30")
|
|
292
|
+
return image
|
|
293
|
+
|
|
294
|
+
canvas = Canvas(512, 512).custom(add_badge)
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Rules:
|
|
298
|
+
|
|
299
|
+
- `fn` must be callable.
|
|
300
|
+
- The callback receives a `PIL.Image.Image`.
|
|
301
|
+
- The callback may mutate and return the same image, return a new image of the same size, or return `None`.
|
|
302
|
+
- Exceptions from the callback are wrapped as `RenderingError`.
|
|
303
|
+
- Custom layers cannot be serialized to JSON.
|
|
304
|
+
|
|
305
|
+
## Helpers, Enums, and Effects
|
|
306
|
+
|
|
307
|
+
### Gradients
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
from quickthumb import LinearGradient, RadialGradient
|
|
311
|
+
|
|
312
|
+
LinearGradient(
|
|
313
|
+
angle=45,
|
|
314
|
+
stops=[("#FF5733", 0.0), ("#3333FF", 1.0)],
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
RadialGradient(
|
|
318
|
+
stops=[("#FF5733", 0.0), ("#3333FF", 1.0)],
|
|
319
|
+
center=(0.5, 0.5),
|
|
320
|
+
)
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### TextPart
|
|
324
|
+
|
|
325
|
+
`TextPart` supports per-segment overrides inside rich text content.
|
|
326
|
+
|
|
327
|
+
```python
|
|
328
|
+
from quickthumb import Stroke, TextPart
|
|
329
|
+
|
|
330
|
+
TextPart(
|
|
331
|
+
text="HOT",
|
|
332
|
+
color="#FF3B30",
|
|
333
|
+
size=56,
|
|
334
|
+
font="Impact",
|
|
335
|
+
bold=None,
|
|
336
|
+
italic=None,
|
|
337
|
+
weight=900,
|
|
338
|
+
line_height=None,
|
|
339
|
+
letter_spacing=0,
|
|
340
|
+
effects=[Stroke(width=3, color="#000000")],
|
|
341
|
+
)
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
Rules:
|
|
345
|
+
|
|
346
|
+
- `text` cannot be empty.
|
|
347
|
+
- `weight` and `bold=True` are mutually exclusive here too.
|
|
348
|
+
|
|
349
|
+
### Enums
|
|
350
|
+
|
|
351
|
+
Blend modes:
|
|
352
|
+
|
|
353
|
+
- `BlendMode.MULTIPLY`
|
|
354
|
+
- `BlendMode.OVERLAY`
|
|
355
|
+
- `BlendMode.SCREEN`
|
|
356
|
+
- `BlendMode.DARKEN`
|
|
357
|
+
- `BlendMode.LIGHTEN`
|
|
358
|
+
- `BlendMode.NORMAL`
|
|
359
|
+
|
|
360
|
+
Fit modes:
|
|
361
|
+
|
|
362
|
+
- `FitMode.COVER`
|
|
363
|
+
- `FitMode.CONTAIN`
|
|
364
|
+
- `FitMode.FILL`
|
|
365
|
+
|
|
366
|
+
Align values:
|
|
367
|
+
|
|
368
|
+
- `Align.CENTER`
|
|
369
|
+
- `Align.TOP_LEFT`
|
|
370
|
+
- `Align.TOP_CENTER`
|
|
371
|
+
- `Align.TOP_RIGHT`
|
|
372
|
+
- `Align.LEFT`
|
|
373
|
+
- `Align.RIGHT`
|
|
374
|
+
- `Align.BOTTOM_LEFT`
|
|
375
|
+
- `Align.BOTTOM_CENTER`
|
|
376
|
+
- `Align.BOTTOM_RIGHT`
|
|
377
|
+
|
|
378
|
+
### Effects by Layer Type
|
|
379
|
+
|
|
380
|
+
- Text layers: `Stroke`, `Shadow`, `Glow`, `Background`
|
|
381
|
+
- Image layers: `Stroke`, `Shadow`, `Glow`, `Filter`
|
|
382
|
+
- Shape layers: `Stroke`, `Shadow`, `Glow`
|
|
383
|
+
- Background layers: `Filter`
|
|
384
|
+
|
|
385
|
+
## JSON Schema
|
|
386
|
+
|
|
387
|
+
QuickThumb serializes canvases as a JSON object with top-level `width`, `height`, and `layers`.
|
|
388
|
+
|
|
389
|
+
```json
|
|
390
|
+
{
|
|
391
|
+
"width": 1280,
|
|
392
|
+
"height": 720,
|
|
393
|
+
"layers": [
|
|
394
|
+
{
|
|
395
|
+
"type": "background",
|
|
396
|
+
"color": "#0F172A",
|
|
397
|
+
"opacity": 1.0,
|
|
398
|
+
"blend_mode": null,
|
|
399
|
+
"fit": null,
|
|
400
|
+
"effects": []
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
"type": "shape",
|
|
404
|
+
"shape": "rectangle",
|
|
405
|
+
"position": [48, 48],
|
|
406
|
+
"width": 320,
|
|
407
|
+
"height": 96,
|
|
408
|
+
"color": "#CC0000",
|
|
409
|
+
"border_radius": 14,
|
|
410
|
+
"opacity": 1.0,
|
|
411
|
+
"rotation": 0.0,
|
|
412
|
+
"align": "top-left",
|
|
413
|
+
"effects": [
|
|
414
|
+
{ "type": "stroke", "width": 2, "color": "#FFFFFF" }
|
|
415
|
+
]
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
"type": "text",
|
|
419
|
+
"content": [
|
|
420
|
+
{ "text": "HELLO ", "color": "#FFFFFF", "effects": [] },
|
|
421
|
+
{
|
|
422
|
+
"text": "WORLD",
|
|
423
|
+
"color": "#B8FF00",
|
|
424
|
+
"weight": 900,
|
|
425
|
+
"effects": [
|
|
426
|
+
{ "type": "stroke", "width": 3, "color": "#000000" }
|
|
427
|
+
]
|
|
428
|
+
}
|
|
429
|
+
],
|
|
430
|
+
"size": 88,
|
|
431
|
+
"position": ["8%", "50%"],
|
|
432
|
+
"align": "left",
|
|
433
|
+
"max_width": "60%",
|
|
434
|
+
"auto_scale": false,
|
|
435
|
+
"rotation": 0.0,
|
|
436
|
+
"opacity": 1.0,
|
|
437
|
+
"effects": [
|
|
438
|
+
{
|
|
439
|
+
"type": "shadow",
|
|
440
|
+
"offset_x": 4,
|
|
441
|
+
"offset_y": 4,
|
|
442
|
+
"color": "#000000",
|
|
443
|
+
"blur_radius": 8
|
|
444
|
+
}
|
|
445
|
+
]
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
"type": "image",
|
|
449
|
+
"path": "portrait.png",
|
|
450
|
+
"position": ["74%", "54%"],
|
|
451
|
+
"width": 420,
|
|
452
|
+
"height": 520,
|
|
453
|
+
"opacity": 1.0,
|
|
454
|
+
"rotation": 0.0,
|
|
455
|
+
"remove_background": false,
|
|
456
|
+
"align": "center",
|
|
457
|
+
"border_radius": 24,
|
|
458
|
+
"fit": "cover",
|
|
459
|
+
"blend_mode": "normal",
|
|
460
|
+
"effects": [
|
|
461
|
+
{ "type": "filter", "blur": 0, "brightness": 1.0, "contrast": 1.05, "saturation": 1.0 }
|
|
462
|
+
]
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
"type": "outline",
|
|
466
|
+
"width": 12,
|
|
467
|
+
"color": "#B8FF00",
|
|
468
|
+
"offset": 0,
|
|
469
|
+
"opacity": 1.0
|
|
470
|
+
}
|
|
471
|
+
]
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
Serialization notes:
|
|
476
|
+
|
|
477
|
+
- Every layer uses a `type` discriminator.
|
|
478
|
+
- Enum-like fields serialize to strings.
|
|
479
|
+
- Positions serialize as JSON arrays.
|
|
480
|
+
- `align` serializes to a single string such as `"center"` or `"top-left"`.
|
|
481
|
+
- `canvas.custom(...)` layers are not serializable.
|
|
482
|
+
|
|
483
|
+
## Validation Rules and Gotchas
|
|
484
|
+
|
|
485
|
+
### Text
|
|
486
|
+
|
|
487
|
+
- `content` is required for `canvas.text(...)`.
|
|
488
|
+
- Rich text lists cannot be empty.
|
|
489
|
+
- `TextPart.text` cannot be empty.
|
|
490
|
+
- `weight` and `bold=True` are mutually exclusive on both `TextLayer` and `TextPart`.
|
|
491
|
+
- `auto_scale=True` requires `max_width`.
|
|
492
|
+
- `max_width` must be a positive integer or positive percentage string.
|
|
493
|
+
- Percentage strings are validated for `position` and `max_width`.
|
|
494
|
+
|
|
495
|
+
### Fonts
|
|
496
|
+
|
|
497
|
+
- `font` may be a font family name, font file path, or remote font URL.
|
|
498
|
+
- When `font` is a webfont URL, `bold`, `italic`, and `weight` flags are ignored.
|
|
499
|
+
- Provide separate font URLs for styled webfont variants.
|
|
500
|
+
- `QUICKTHUMB_FONT_DIR` can point QuickThumb to a custom font directory.
|
|
501
|
+
- `QUICKTHUMB_DEFAULT_FONT` can override the default fallback font.
|
|
502
|
+
|
|
503
|
+
### Images and Shapes
|
|
504
|
+
|
|
505
|
+
- `image.position` and `shape.position` are required.
|
|
506
|
+
- Positions must be 2-item tuples or lists.
|
|
507
|
+
- Percentage positions may be negative, for example `("-10%", "50%")`.
|
|
508
|
+
- `border_radius` cannot be negative.
|
|
509
|
+
- `opacity` must be between `0.0` and `1.0`.
|
|
510
|
+
|
|
511
|
+
### Rendering and Export
|
|
512
|
+
|
|
513
|
+
- `quality` is only supported for `JPEG` and `WEBP`; using it with `PNG` raises `RenderingError`.
|
|
514
|
+
- Unsupported file formats raise `RenderingError`.
|
|
515
|
+
- Invalid local paths or failed remote downloads raise rendering-time errors.
|
|
516
|
+
|
|
517
|
+
### JSON
|
|
518
|
+
|
|
519
|
+
- `Canvas.from_json()` expects a JSON string.
|
|
520
|
+
- `Canvas.to_json()` raises `ValidationError` when the canvas contains custom layers.
|
|
521
|
+
- Round-trip JSON works for supported layer types and effects only.
|
|
522
|
+
|
|
523
|
+
## End-to-End Example
|
|
524
|
+
|
|
525
|
+
```python
|
|
526
|
+
from quickthumb import Background, Canvas, Filter, Shadow, Stroke, TextPart
|
|
527
|
+
|
|
528
|
+
canvas = (
|
|
529
|
+
Canvas.from_aspect_ratio("16:9", base_width=1280)
|
|
530
|
+
.background(
|
|
531
|
+
image="https://images.unsplash.com/photo-1516321318423-f06f85e504b3",
|
|
532
|
+
fit="cover",
|
|
533
|
+
effects=[Filter(brightness=0.6)],
|
|
534
|
+
)
|
|
535
|
+
.background(color="#000000", opacity=0.35)
|
|
536
|
+
.shape(
|
|
537
|
+
shape="rectangle",
|
|
538
|
+
position=(52, 52),
|
|
539
|
+
width=360,
|
|
540
|
+
height=96,
|
|
541
|
+
color="#CC0000",
|
|
542
|
+
border_radius=14,
|
|
543
|
+
)
|
|
544
|
+
.text(
|
|
545
|
+
content=[
|
|
546
|
+
TextPart(text="AI ", color="#B8FF00", weight=900),
|
|
547
|
+
TextPart(text="THUMBNAILS", color="#FFFFFF", weight=900),
|
|
548
|
+
],
|
|
549
|
+
size=108,
|
|
550
|
+
position=("8%", "52%"),
|
|
551
|
+
align=("left", "middle"),
|
|
552
|
+
max_width="58%",
|
|
553
|
+
effects=[
|
|
554
|
+
Stroke(width=6, color="#000000"),
|
|
555
|
+
Shadow(offset_x=4, offset_y=4, color="#000000", blur_radius=8),
|
|
556
|
+
Background(color="#111827CC", padding=(16, 22), border_radius=12),
|
|
557
|
+
],
|
|
558
|
+
)
|
|
559
|
+
.image(
|
|
560
|
+
path="portrait.png",
|
|
561
|
+
position=("75%", "55%"),
|
|
562
|
+
width=430,
|
|
563
|
+
height=540,
|
|
564
|
+
fit="cover",
|
|
565
|
+
align=("center", "middle"),
|
|
566
|
+
border_radius=24,
|
|
567
|
+
effects=[Shadow(offset_x=0, offset_y=14, color="#000000", blur_radius=24)],
|
|
568
|
+
)
|
|
569
|
+
.outline(width=12, color="#B8FF00")
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
canvas.render("thumbnail.png")
|
|
573
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quickthumb
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Programmatic thumbnail and social image generation with layered Python and JSON APIs
|
|
5
5
|
Project-URL: Homepage, https://github.com/sjquant/quickthumb
|
|
6
6
|
Project-URL: Repository, https://github.com/sjquant/quickthumb
|
|
@@ -40,6 +40,10 @@ It is designed for code-first and JSON-first workflows, with a layer-based API t
|
|
|
40
40
|
| --- | --- | --- |
|
|
41
41
|
|  |  |  |
|
|
42
42
|
|
|
43
|
+
| Talking Head | Reaction / Commentary | Tutorial / Explainer |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
|  |  |  |
|
|
46
|
+
|
|
43
47
|
## Why QuickThumb
|
|
44
48
|
|
|
45
49
|
- Built for thumbnails and social graphics, not just generic image composition
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
- ✅ Stroke, Shadow (with blur), Glow (outer glow), Background (with padding and border radius)
|
|
26
26
|
- ✅ Rich text with `TextPart` (per-segment styling)
|
|
27
27
|
- ✅ Rotation support for text layers (simple and rich text)
|
|
28
|
+
- ✅ Multiline text background effects render as a single grouped background block
|
|
29
|
+
- ✅ Explicit newlines are preserved even when `max_width` / `auto_scale` wrapping is enabled
|
|
28
30
|
|
|
29
31
|
### Font System
|
|
30
32
|
|
|
@@ -80,24 +82,24 @@
|
|
|
80
82
|
|
|
81
83
|
#### API Reference Cleanup
|
|
82
84
|
|
|
83
|
-
- [
|
|
84
|
-
- [
|
|
85
|
-
- [
|
|
86
|
-
- [
|
|
85
|
+
- [x] Update `DESIGN.md` to reflect the actual implemented API surface
|
|
86
|
+
- [x] Remove stale examples from `DESIGN.md` that no longer run against the codebase
|
|
87
|
+
- [x] Expand `DESIGN.md` into a reliable reference for layer types, effects, enums, JSON schema, and validation rules
|
|
88
|
+
- [x] Add an explicit "gotchas" section (e.g. `weight` vs `bold`, `auto_scale` requires `max_width`, custom layers are not JSON-serializable, webfont styling flags are ignored)
|
|
87
89
|
|
|
88
90
|
#### Example Expansion
|
|
89
91
|
|
|
90
92
|
- [x] Expand `examples/README.md` so it documents every example currently shipped
|
|
91
|
-
- [
|
|
92
|
-
- [
|
|
93
|
-
- [
|
|
94
|
-
- [
|
|
93
|
+
- [x] Add more end-to-end examples for real thumbnail use cases:
|
|
94
|
+
- [x] YouTube talking-head thumbnail
|
|
95
|
+
- [x] YouTube reaction / commentary thumbnail
|
|
96
|
+
- [x] YouTube tutorial / explainer thumbnail
|
|
95
97
|
- [x] Instagram / X / social news card
|
|
96
|
-
- [
|
|
97
|
-
- [
|
|
98
|
+
- [x] Podcast / interview promo card
|
|
99
|
+
- [x] Shorts / vertical cover design
|
|
98
100
|
- [x] Add example outputs/screenshots for every example script
|
|
99
|
-
- [
|
|
100
|
-
- [
|
|
101
|
+
- [x] Add examples showing JSON-first workflows for AI agents that emit specs instead of Python
|
|
102
|
+
- [x] Add examples showing remote image URLs, webfont URLs, and background removal workflows
|
|
101
103
|
|
|
102
104
|
#### Package Discoverability
|
|
103
105
|
|
|
@@ -9,6 +9,10 @@ It is designed for code-first and JSON-first workflows, with a layer-based API t
|
|
|
9
9
|
| --- | --- | --- |
|
|
10
10
|
|  |  |  |
|
|
11
11
|
|
|
12
|
+
| Talking Head | Reaction / Commentary | Tutorial / Explainer |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
|  |  |  |
|
|
15
|
+
|
|
12
16
|
## Why QuickThumb
|
|
13
17
|
|
|
14
18
|
- Built for thumbnails and social graphics, not just generic image composition
|