quickthumb 0.4.0__tar.gz → 0.5.0__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.5.0/.claude/skills/next-task/SKILL.md +15 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/.claude/skills/tdd/SKILL.md +1 -2
- {quickthumb-0.4.0 → quickthumb-0.5.0}/.github/workflows/publish.yaml +1 -1
- {quickthumb-0.4.0 → quickthumb-0.5.0}/PKG-INFO +106 -13
- {quickthumb-0.4.0 → quickthumb-0.5.0}/README.md +102 -12
- quickthumb-0.5.0/assets/brand/apple-touch-icon.png +0 -0
- quickthumb-0.5.0/assets/brand/favicon.ico +0 -0
- quickthumb-0.5.0/assets/brand/favicon.png +0 -0
- quickthumb-0.5.0/assets/brand/quickthumb-icon-1024.png +0 -0
- quickthumb-0.5.0/assets/brand/quickthumb-icon-152.png +0 -0
- quickthumb-0.5.0/assets/brand/quickthumb-icon-16.png +0 -0
- quickthumb-0.5.0/assets/brand/quickthumb-icon-167.png +0 -0
- quickthumb-0.5.0/assets/brand/quickthumb-icon-180.png +0 -0
- quickthumb-0.5.0/assets/brand/quickthumb-icon-192.png +0 -0
- quickthumb-0.5.0/assets/brand/quickthumb-icon-256.png +0 -0
- quickthumb-0.5.0/assets/brand/quickthumb-icon-32.png +0 -0
- quickthumb-0.5.0/assets/brand/quickthumb-icon-48.png +0 -0
- quickthumb-0.5.0/assets/brand/quickthumb-icon-512.png +0 -0
- quickthumb-0.5.0/assets/brand/quickthumb-icon-64.png +0 -0
- quickthumb-0.5.0/assets/brand/quickthumb-icon-master-transparent.png +0 -0
- quickthumb-0.5.0/assets/brand/quickthumb-icon-master.png +0 -0
- quickthumb-0.5.0/assets/brand/social-preview.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/api/background.md +6 -2
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/api/canvas.md +4 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/api/effects.md +52 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/api/enums.md +53 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/api/image.md +5 -1
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/api/index.md +9 -3
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/api/outline.md +4 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/api/shape.md +4 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/api/text.md +57 -0
- quickthumb-0.5.0/docs/assets/brand/apple-touch-icon.png +0 -0
- quickthumb-0.5.0/docs/assets/brand/favicon.ico +0 -0
- quickthumb-0.5.0/docs/assets/brand/favicon.png +0 -0
- quickthumb-0.5.0/docs/assets/brand/quickthumb-icon-192.png +0 -0
- quickthumb-0.5.0/docs/assets/brand/quickthumb-icon-512.png +0 -0
- quickthumb-0.5.0/docs/assets/brand/quickthumb-icon.png +0 -0
- quickthumb-0.5.0/docs/assets/brand/social-preview.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/changelog.md +4 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/concepts.md +5 -1
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/cookbook/ai-workflow.md +10 -6
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/cookbook/index.md +7 -3
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/cookbook/instagram-card.md +4 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/cookbook/podcast-promo.md +5 -1
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/cookbook/shorts-cover.md +4 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/cookbook/webfonts-rembg.md +6 -2
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/cookbook/youtube-thumbnail.md +6 -2
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/faq.md +6 -2
- quickthumb-0.5.0/docs/favicon.ico +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/getting-started.md +6 -2
- quickthumb-0.5.0/docs/googleafa40cbb47cddae5.html +1 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/index.md +13 -2
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/installation.md +9 -4
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/json-schema.md +56 -7
- quickthumb-0.5.0/docs/overrides/main.html +31 -0
- quickthumb-0.5.0/docs/robots.txt +4 -0
- quickthumb-0.5.0/docs/site.webmanifest +19 -0
- quickthumb-0.5.0/docs/stylesheets/extra.css +452 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/examples/README.md +3 -3
- {quickthumb-0.4.0 → quickthumb-0.5.0}/examples/shorts_cover_agent.py +1 -1
- {quickthumb-0.4.0 → quickthumb-0.5.0}/mkdocs.yml +8 -1
- quickthumb-0.5.0/overrides/main.html +9 -0
- quickthumb-0.5.0/overrides/partials/header.html +80 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/pyproject.toml +8 -1
- {quickthumb-0.4.0 → quickthumb-0.5.0}/quickthumb/__init__.py +6 -1
- quickthumb-0.5.0/quickthumb/_entry.py +18 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/quickthumb/canvas.py +537 -35
- quickthumb-0.5.0/quickthumb/cli.py +200 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/quickthumb/models.py +68 -19
- quickthumb-0.5.0/quickthumb/templates/instagram-square.json +18 -0
- quickthumb-0.5.0/quickthumb/templates/og-image.json +18 -0
- quickthumb-0.5.0/quickthumb/templates/twitter-card.json +18 -0
- quickthumb-0.5.0/quickthumb/templates/youtube-16x9.json +18 -0
- quickthumb-0.5.0/specs/TASKS.md +86 -0
- quickthumb-0.5.0/tests/snapshots/auto_scale_rich_text.png +0 -0
- quickthumb-0.5.0/tests/snapshots/auto_scale_simple_text.png +0 -0
- quickthumb-0.5.0/tests/snapshots/custom_canvas_layer.png +0 -0
- quickthumb-0.5.0/tests/snapshots/explicit_newlines_preserved_with_max_width.png +0 -0
- quickthumb-0.5.0/tests/snapshots/grain_effect_on_background.png +0 -0
- quickthumb-0.5.0/tests/snapshots/image_overlay_fit_contain.png +0 -0
- quickthumb-0.5.0/tests/snapshots/image_overlay_fit_cover.png +0 -0
- quickthumb-0.5.0/tests/snapshots/image_overlay_fit_fill.png +0 -0
- quickthumb-0.5.0/tests/snapshots/image_with_text_overlay.png +0 -0
- quickthumb-0.5.0/tests/snapshots/jpeg_format.jpg +0 -0
- quickthumb-0.5.0/tests/snapshots/multiline_text_with_background.png +0 -0
- quickthumb-0.5.0/tests/snapshots/multiline_text_with_fill.png +0 -0
- quickthumb-0.5.0/tests/snapshots/percentage_with_alignment.png +0 -0
- quickthumb-0.5.0/tests/snapshots/rich_text_advanced_styles.png +0 -0
- quickthumb-0.5.0/tests/snapshots/rich_text_alignment.png +0 -0
- quickthumb-0.5.0/tests/snapshots/rich_text_different_colors.png +0 -0
- quickthumb-0.5.0/tests/snapshots/rich_text_mixed_fonts.png +0 -0
- quickthumb-0.5.0/tests/snapshots/rich_text_mixed_styles.png +0 -0
- quickthumb-0.5.0/tests/snapshots/rich_text_with_background.png +0 -0
- quickthumb-0.5.0/tests/snapshots/rich_text_with_effects.png +0 -0
- quickthumb-0.5.0/tests/snapshots/rich_text_with_per_part_fill.png +0 -0
- quickthumb-0.5.0/tests/snapshots/rich_text_wrapping.png +0 -0
- quickthumb-0.5.0/tests/snapshots/roboto_bold.png +0 -0
- quickthumb-0.5.0/tests/snapshots/roboto_bold_italic.png +0 -0
- quickthumb-0.5.0/tests/snapshots/roboto_italic.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_alignment.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_bold_and_italic.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_fill_with_letter_spacing.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_fill_with_stroke.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_no_wrapping.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_rendering.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_rotation_basic.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_rotation_centered.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_rotation_with_effects.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_weight_black.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_weight_light.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_with_background_and_effects.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_with_background_basic.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_with_basic_shadow.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_with_blurred_shadow.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_with_glow.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_with_image_fill.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_with_line_height.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_with_linear_gradient_fill.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_with_multiple_glows.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_with_radial_gradient_fill.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_with_stroke.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_wrapping_center_aligned.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_wrapping_center_aligned_percentage.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_wrapping_left_aligned.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_wrapping_long_word_overflow.png +0 -0
- quickthumb-0.5.0/tests/snapshots/text_wrapping_right_aligned.png +0 -0
- quickthumb-0.5.0/tests/snapshots/webfont_basic_rendering.png +0 -0
- quickthumb-0.5.0/tests/snapshots/webp_format.webp +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/test_background_layers.py +134 -12
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/test_canvas.py +238 -12
- quickthumb-0.5.0/tests/test_cli.py +337 -0
- quickthumb-0.5.0/tests/test_docs_seo.py +55 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/test_rendering.py +461 -9
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/test_text_layers.py +161 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/uv.lock +158 -2
- quickthumb-0.4.0/CLAUDE.md +0 -30
- quickthumb-0.4.0/DESIGN.md +0 -606
- quickthumb-0.4.0/docs/stylesheets/extra.css +0 -83
- quickthumb-0.4.0/tests/snapshots/auto_scale_rich_text.png +0 -0
- quickthumb-0.4.0/tests/snapshots/auto_scale_simple_text.png +0 -0
- quickthumb-0.4.0/tests/snapshots/custom_canvas_layer.png +0 -0
- quickthumb-0.4.0/tests/snapshots/explicit_newlines_preserved_with_max_width.png +0 -0
- quickthumb-0.4.0/tests/snapshots/image_overlay_fit_contain.png +0 -0
- quickthumb-0.4.0/tests/snapshots/image_overlay_fit_cover.png +0 -0
- quickthumb-0.4.0/tests/snapshots/image_overlay_fit_fill.png +0 -0
- quickthumb-0.4.0/tests/snapshots/image_with_text_overlay.png +0 -0
- quickthumb-0.4.0/tests/snapshots/jpeg_format.jpg +0 -0
- quickthumb-0.4.0/tests/snapshots/multiline_text_with_background.png +0 -0
- quickthumb-0.4.0/tests/snapshots/percentage_with_alignment.png +0 -0
- quickthumb-0.4.0/tests/snapshots/rich_text_advanced_styles.png +0 -0
- quickthumb-0.4.0/tests/snapshots/rich_text_alignment.png +0 -0
- quickthumb-0.4.0/tests/snapshots/rich_text_different_colors.png +0 -0
- quickthumb-0.4.0/tests/snapshots/rich_text_mixed_fonts.png +0 -0
- quickthumb-0.4.0/tests/snapshots/rich_text_mixed_styles.png +0 -0
- quickthumb-0.4.0/tests/snapshots/rich_text_with_background.png +0 -0
- quickthumb-0.4.0/tests/snapshots/rich_text_with_effects.png +0 -0
- quickthumb-0.4.0/tests/snapshots/roboto_bold.png +0 -0
- quickthumb-0.4.0/tests/snapshots/roboto_bold_italic.png +0 -0
- quickthumb-0.4.0/tests/snapshots/roboto_italic.png +0 -0
- quickthumb-0.4.0/tests/snapshots/text_alignment.png +0 -0
- quickthumb-0.4.0/tests/snapshots/text_bold_and_italic.png +0 -0
- quickthumb-0.4.0/tests/snapshots/text_no_wrapping.png +0 -0
- quickthumb-0.4.0/tests/snapshots/text_rendering.png +0 -0
- quickthumb-0.4.0/tests/snapshots/text_rotation_basic.png +0 -0
- quickthumb-0.4.0/tests/snapshots/text_rotation_centered.png +0 -0
- quickthumb-0.4.0/tests/snapshots/text_rotation_with_effects.png +0 -0
- quickthumb-0.4.0/tests/snapshots/text_weight_black.png +0 -0
- quickthumb-0.4.0/tests/snapshots/text_weight_light.png +0 -0
- quickthumb-0.4.0/tests/snapshots/text_with_background_and_effects.png +0 -0
- quickthumb-0.4.0/tests/snapshots/text_with_background_basic.png +0 -0
- quickthumb-0.4.0/tests/snapshots/text_with_basic_shadow.png +0 -0
- quickthumb-0.4.0/tests/snapshots/text_with_blurred_shadow.png +0 -0
- quickthumb-0.4.0/tests/snapshots/text_with_glow.png +0 -0
- quickthumb-0.4.0/tests/snapshots/text_with_line_height.png +0 -0
- quickthumb-0.4.0/tests/snapshots/text_with_multiple_glows.png +0 -0
- quickthumb-0.4.0/tests/snapshots/text_with_stroke.png +0 -0
- quickthumb-0.4.0/tests/snapshots/text_wrapping_center_aligned.png +0 -0
- quickthumb-0.4.0/tests/snapshots/text_wrapping_center_aligned_percentage.png +0 -0
- quickthumb-0.4.0/tests/snapshots/text_wrapping_left_aligned.png +0 -0
- quickthumb-0.4.0/tests/snapshots/text_wrapping_right_aligned.png +0 -0
- quickthumb-0.4.0/tests/snapshots/webfont_basic_rendering.png +0 -0
- quickthumb-0.4.0/tests/snapshots/webp_format.webp +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/.claude/settings.json +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/.github/workflows/docs.yml +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/.gitignore +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/.vscode/extensions.json +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/.vscode/settings.json +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/LICENSE +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSans-Black.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSans-BlackItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSans-Bold.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSans-BoldItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSans-ExtraBold.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSans-ExtraBoldItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSans-ExtraLight.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSans-ExtraLightItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSans-Italic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSans-Light.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSans-LightItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSans-Medium.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSans-MediumItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSans-Regular.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSans-SemiBold.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSans-SemiBoldItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSans-Thin.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSans-ThinItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSerif-Black.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSerif-BlackItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSerif-Bold.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSerif-BoldItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSerif-ExtraBold.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSerif-ExtraBoldItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSerif-ExtraLight.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSerif-ExtraLightItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSerif-Italic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSerif-Light.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSerif-LightItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSerif-Medium.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSerif-MediumItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSerif-Regular.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSerif-SemiBold.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSerif-SemiBoldItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSerif-Thin.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/NotoSerif-ThinItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/Roboto-Black.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/Roboto-BlackItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/Roboto-Bold.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/Roboto-BoldItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/Roboto-ExtraBold.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/Roboto-ExtraBoldItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/Roboto-ExtraLight.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/Roboto-ExtraLightItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/Roboto-Italic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/Roboto-Light.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/Roboto-LightItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/Roboto-Medium.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/Roboto-MediumItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/Roboto-Regular.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/Roboto-SemiBold.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/Roboto-SemiBoldItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/Roboto-Thin.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/fonts/Roboto-ThinItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/images/c-g-JgDUVGAXsso-unsplash.jpg +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/images/denise-jans-WIRvXd1PYlg-unsplash.jpg +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/assets/images/tobias-rademacher-wnF27F85ZKw-unsplash.jpg +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/assets/examples/instagram_news_card.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/assets/examples/podcast_interview_promo.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/assets/examples/shorts_cover_agent.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/assets/examples/youtube_reaction.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/assets/examples/youtube_talking_head.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/assets/examples/youtube_thumbnail_01.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/assets/examples/youtube_thumbnail_02.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/docs/assets/examples/youtube_tutorial_explainer.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/examples/instagram_news_card.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/examples/instagram_news_card.py +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/examples/podcast_interview_promo.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/examples/podcast_interview_promo.py +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/examples/shorts_cover_agent.json +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/examples/shorts_cover_agent.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/examples/youtube_reaction.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/examples/youtube_reaction.py +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/examples/youtube_talking_head.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/examples/youtube_talking_head.py +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/examples/youtube_thumbnail_01.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/examples/youtube_thumbnail_01.py +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/examples/youtube_thumbnail_02.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/examples/youtube_thumbnail_02.py +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/examples/youtube_tutorial_explainer.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/examples/youtube_tutorial_explainer.py +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/quickthumb/errors.py +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/quickthumb/font_cache.py +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/__init__.py +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/conftest.py +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/fixtures/ivana-cajina-_7LbC5J-jw4-unsplash.jpg +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/fixtures/sample_image.jpg +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/fixtures/tobias-rademacher-wnF27F85ZKw-unsplash.jpg +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/arial_bold.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/arial_bold_italic.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/arial_italic.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/background_blur_heavy.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/background_blur_soft.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/background_combined_filters.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/background_contrast_high.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/background_contrast_low.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/background_filter_effect.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/background_saturation_grayscale.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/background_saturation_vivid.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/blend_mode_darken.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/blend_mode_lighten.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/blend_mode_multiply.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/blend_mode_normal.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/blend_mode_overlay.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/blend_mode_screen.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/bold_italic_with_named_font.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/bold_with_named_font.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/custom_layer_with_kwargs.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/font_name_works.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/image_background_basic.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/image_background_with_opacity.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/image_basic.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/image_blend_mode_darken.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/image_blend_mode_lighten.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/image_blend_mode_multiply.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/image_blend_mode_normal.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/image_blend_mode_overlay.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/image_blend_mode_screen.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/image_brightness_decrease.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/image_brightness_increase.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/image_filter_effect.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/image_fit_contain.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/image_fit_cover.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/image_fit_fill.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/image_on_background_image.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/image_percentage_position.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/image_with_border_radius.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/image_with_drop_shadow.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/image_with_glow.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/image_with_opacity.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/image_with_remove_background.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/image_with_rotation.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/image_with_stroke.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/italic_with_named_font.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/linear_gradient_brightness_decrease.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/linear_gradient_brightness_increase.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/linear_gradient_diagonal.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/linear_gradient_horizontal.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/linear_gradient_with_alpha.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/linear_gradient_with_opacity.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/multiline_text_with_glow.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/multiline_text_with_shadow.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/multiple_images.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/outline_basic.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/outline_with_offset.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/radial_gradient_brightness_decrease.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/radial_gradient_brightness_increase.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/radial_gradient_centered.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/radial_gradient_with_opacity.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/shape_ellipse_basic.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/shape_rectangle_basic.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/shape_rectangle_with_border_radius.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/shape_with_effects.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/shape_with_opacity.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/shape_with_rotation.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/solid_background.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/solid_color_brightness_decrease.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/solid_color_brightness_increase.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/text_and_outline_with_opacity.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/text_with_letter_spacing.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/text_with_letter_spacing_glow.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/text_with_letter_spacing_shadow.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/times_new_roman_bold.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/times_new_roman_bold_italic.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/times_new_roman_italic.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/snapshots/url_image_background_basic.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/test_font_cache.py +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/test_image_layers.py +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/test_outline_layers.py +0 -0
- {quickthumb-0.4.0 → quickthumb-0.5.0}/tests/test_shape_layers.py +0 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: next-task
|
|
3
|
+
description: Read TASKS.md and begin working on the next TODO task in order.
|
|
4
|
+
disable-model-invocation: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Read @specs/TASKS.md and begin working on the next [TODO] task.
|
|
8
|
+
|
|
9
|
+
- TODO → DOING when starting
|
|
10
|
+
- Continue to the next task if the total is < 200 lines. Set to [REVIEW] once finished.
|
|
11
|
+
- If code changes exceed ~200 lines: stop and set status to [REVIEW]
|
|
12
|
+
- Wait for user confirmation
|
|
13
|
+
- After confirmation: set status to [DONE] **before** committing or pushing
|
|
14
|
+
|
|
15
|
+
Do not reorder tasks. Only update the status.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tdd
|
|
3
|
-
description:
|
|
4
|
-
disable-model-invocation: true
|
|
3
|
+
description: Follow the TDD cycle. Use this before writing any implementation code, whether fixing a bug, adding a feature, or making any code change.
|
|
5
4
|
---
|
|
6
5
|
|
|
7
6
|
## Workflow
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quickthumb
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
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
|
|
@@ -24,14 +24,17 @@ Requires-Python: >=3.10
|
|
|
24
24
|
Requires-Dist: pillow<11.0.0,>=10.4.0
|
|
25
25
|
Requires-Dist: pydantic<3.0.0,>=2.12.5
|
|
26
26
|
Requires-Dist: typing-extensions<5.0.0,>=4.15.0
|
|
27
|
+
Provides-Extra: cli
|
|
28
|
+
Requires-Dist: typer<1.0,>=0.24.1; extra == 'cli'
|
|
29
|
+
Requires-Dist: watchfiles<2.0.0,>=1.0.0; extra == 'cli'
|
|
27
30
|
Provides-Extra: rembg
|
|
28
31
|
Requires-Dist: onnxruntime<2.0.0,>=1.24.1; extra == 'rembg'
|
|
29
32
|
Requires-Dist: rembg<3.0.0,>=2.0.50; extra == 'rembg'
|
|
30
33
|
Description-Content-Type: text/markdown
|
|
31
34
|
|
|
32
|
-
#
|
|
35
|
+
# quickthumb
|
|
33
36
|
|
|
34
|
-
|
|
37
|
+
quickthumb is a Python library for programmatic thumbnail, social card, and promo image generation.
|
|
35
38
|
It is designed for code-first and JSON-first workflows, with a layer-based API that works well for human-authored scripts and AI-generated specs.
|
|
36
39
|
|
|
37
40
|
## Gallery
|
|
@@ -44,7 +47,7 @@ It is designed for code-first and JSON-first workflows, with a layer-based API t
|
|
|
44
47
|
| --- | --- | --- |
|
|
45
48
|
|  |  |  |
|
|
46
49
|
|
|
47
|
-
## Why
|
|
50
|
+
## Why quickthumb
|
|
48
51
|
|
|
49
52
|
- Built for thumbnails and social graphics, not just generic image composition
|
|
50
53
|
- Works with Python method chaining and JSON serialization/deserialization
|
|
@@ -156,6 +159,56 @@ canvas = Canvas(1280, 720).text(
|
|
|
156
159
|
)
|
|
157
160
|
```
|
|
158
161
|
|
|
162
|
+
### Gradient and image-filled text
|
|
163
|
+
|
|
164
|
+
Fill text with a gradient or image instead of a flat color. Works on the whole text layer or per `TextPart`.
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from quickthumb import Canvas, LinearGradient, RadialGradient, TextFillImage, TextPart
|
|
168
|
+
|
|
169
|
+
# Gradient headline
|
|
170
|
+
canvas = Canvas(1280, 720).text(
|
|
171
|
+
content="GRADIENT TITLE",
|
|
172
|
+
size=120,
|
|
173
|
+
fill=LinearGradient(
|
|
174
|
+
angle=90,
|
|
175
|
+
stops=[("#FF6B6B", 0.0), ("#FFE66D", 0.5), ("#4ECDC4", 1.0)],
|
|
176
|
+
),
|
|
177
|
+
position=("50%", "50%"),
|
|
178
|
+
align="center",
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
# Image-filled text
|
|
182
|
+
canvas = Canvas(1280, 720).text(
|
|
183
|
+
content="TEXTURE",
|
|
184
|
+
size=140,
|
|
185
|
+
fill=TextFillImage(path="fire_texture.jpg", fit="cover"),
|
|
186
|
+
position=("50%", "50%"),
|
|
187
|
+
align="center",
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# Per-segment fills using TextPart
|
|
191
|
+
canvas = Canvas(1280, 720).text(
|
|
192
|
+
content=[
|
|
193
|
+
TextPart(
|
|
194
|
+
text="HOT ",
|
|
195
|
+
fill=LinearGradient(angle=45, stops=[("#FF4500", 0.0), ("#FFD700", 1.0)]),
|
|
196
|
+
weight=900,
|
|
197
|
+
),
|
|
198
|
+
TextPart(
|
|
199
|
+
text="COLD",
|
|
200
|
+
fill=LinearGradient(angle=45, stops=[("#00BFFF", 0.0), ("#8A2BE2", 1.0)]),
|
|
201
|
+
weight=900,
|
|
202
|
+
),
|
|
203
|
+
],
|
|
204
|
+
size=110,
|
|
205
|
+
position=("50%", "50%"),
|
|
206
|
+
align="center",
|
|
207
|
+
)
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`fill` accepts `LinearGradient`, `RadialGradient`, or `TextFillImage`. It is mutually independent of `color` — when `fill` is set it takes visual precedence. A `fill` on a `TextPart` overrides the layer-level `fill` for that segment only.
|
|
211
|
+
|
|
159
212
|
### Image layers
|
|
160
213
|
|
|
161
214
|
```python
|
|
@@ -194,6 +247,42 @@ canvas = Canvas(1280, 720).shape(
|
|
|
194
247
|
)
|
|
195
248
|
```
|
|
196
249
|
|
|
250
|
+
### Grain / noise effect
|
|
251
|
+
|
|
252
|
+
Add film-grain noise to background or image layers via `effects=[Grain(...)]`.
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
from quickthumb import Canvas, Grain
|
|
256
|
+
|
|
257
|
+
canvas = (
|
|
258
|
+
Canvas(1280, 720)
|
|
259
|
+
.background(
|
|
260
|
+
color="#1A1A2E",
|
|
261
|
+
effects=[Grain(intensity=0.12, monochrome=True)],
|
|
262
|
+
)
|
|
263
|
+
.image(
|
|
264
|
+
path="portrait.png",
|
|
265
|
+
position=("70%", "50%"),
|
|
266
|
+
width=400,
|
|
267
|
+
height=500,
|
|
268
|
+
align=("center", "middle"),
|
|
269
|
+
effects=[Grain(intensity=0.08, monochrome=False, blend_mode="overlay", opacity=0.6)],
|
|
270
|
+
)
|
|
271
|
+
)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
`Grain` parameters:
|
|
275
|
+
|
|
276
|
+
| Parameter | Type | Default | Description |
|
|
277
|
+
| --- | --- | --- | --- |
|
|
278
|
+
| `intensity` | `float` | required | Noise amplitude, `0.0`–`1.0` |
|
|
279
|
+
| `monochrome` | `bool` | `True` | `True` = luminance noise; `False` = per-channel color noise |
|
|
280
|
+
| `blend_mode` | `str` | `"overlay"` | `"overlay"`, `"screen"`, `"multiply"`, or `"normal"` |
|
|
281
|
+
| `opacity` | `float` | `1.0` | Overall grain strength, `0.0`–`1.0` |
|
|
282
|
+
| `seed` | `int \| None` | `None` | Optional RNG seed for deterministic output |
|
|
283
|
+
|
|
284
|
+
`Grain` is valid in `effects` on **background** and **image** layers. It is serialized with `"type": "grain"` in JSON.
|
|
285
|
+
|
|
197
286
|
### Export helpers
|
|
198
287
|
|
|
199
288
|
```python
|
|
@@ -204,7 +293,7 @@ canvas.render("output.webp", format="WEBP", quality=90)
|
|
|
204
293
|
|
|
205
294
|
## JSON-First Workflow
|
|
206
295
|
|
|
207
|
-
|
|
296
|
+
quickthumb can round-trip most canvases through JSON:
|
|
208
297
|
|
|
209
298
|
```python
|
|
210
299
|
from quickthumb import Canvas
|
|
@@ -220,7 +309,7 @@ config = """
|
|
|
220
309
|
},
|
|
221
310
|
{
|
|
222
311
|
"type": "text",
|
|
223
|
-
"content": "Hello
|
|
312
|
+
"content": "Hello quickthumb",
|
|
224
313
|
"size": 72,
|
|
225
314
|
"color": "#FFFFFF",
|
|
226
315
|
"align": "center",
|
|
@@ -249,12 +338,12 @@ Notes:
|
|
|
249
338
|
|
|
250
339
|
## AI-Friendly Workflows
|
|
251
340
|
|
|
252
|
-
|
|
341
|
+
quickthumb is a good target when you want an LLM to generate image specs that are deterministic and easy to validate.
|
|
253
342
|
|
|
254
343
|
Prompt pattern for Python generation:
|
|
255
344
|
|
|
256
345
|
```text
|
|
257
|
-
Generate
|
|
346
|
+
Generate quickthumb Python code for a 1280x720 YouTube thumbnail.
|
|
258
347
|
Use layered composition only.
|
|
259
348
|
Keep text on the left, subject image on the right, and use high-contrast typography.
|
|
260
349
|
Return runnable code that ends with canvas.render("thumbnail.png").
|
|
@@ -263,20 +352,20 @@ Return runnable code that ends with canvas.render("thumbnail.png").
|
|
|
263
352
|
Prompt pattern for JSON generation:
|
|
264
353
|
|
|
265
354
|
```text
|
|
266
|
-
Generate a
|
|
355
|
+
Generate a quickthumb JSON config with top-level width, height, and layers.
|
|
267
356
|
Use one background image layer, one dark overlay background layer, two text layers, and one outline layer.
|
|
268
|
-
Only use valid
|
|
357
|
+
Only use valid quickthumb layer types and effect names.
|
|
269
358
|
```
|
|
270
359
|
|
|
271
360
|
Recommended workflow:
|
|
272
361
|
|
|
273
|
-
1. Have the model produce
|
|
362
|
+
1. Have the model produce quickthumb Python or JSON.
|
|
274
363
|
2. Validate or render it locally.
|
|
275
364
|
3. Adjust only the content, colors, and assets instead of rewriting layout logic from scratch.
|
|
276
365
|
|
|
277
366
|
## Environment Variables
|
|
278
367
|
|
|
279
|
-
|
|
368
|
+
quickthumb looks for fonts using these environment variables:
|
|
280
369
|
|
|
281
370
|
- `QUICKTHUMB_FONT_DIR`: directory that contains font files
|
|
282
371
|
- `QUICKTHUMB_DEFAULT_FONT`: default font family/name to use when `font` is omitted
|
|
@@ -299,10 +388,12 @@ os.environ["QUICKTHUMB_DEFAULT_FONT"] = "Roboto"
|
|
|
299
388
|
| Background controls | Opacity, blend modes, fit modes, blur, brightness, contrast, saturation |
|
|
300
389
|
| Text | Positioning, alignment, wrapping, letter spacing, line height, rotation, auto-scale |
|
|
301
390
|
| Rich text | Per-segment `TextPart` styling |
|
|
391
|
+
| Text fills | Gradient (`LinearGradient`, `RadialGradient`) and image (`TextFillImage`) fills; per-`TextPart` override |
|
|
302
392
|
| Text effects | Stroke, shadow, glow, background fill |
|
|
303
393
|
| Fonts | Local fonts, CSS-style weights, italic/bold flags, webfont URLs, fallback mapping |
|
|
304
394
|
| Images | Local/remote images, sizing, fit modes, alignment, opacity, rotation |
|
|
305
395
|
| Image effects | Stroke, shadow, glow, filter effects, border radius, background removal |
|
|
396
|
+
| Grain / noise | Per-layer `Grain` effect on background and image layers; monochrome or color noise |
|
|
306
397
|
| Shapes | Rectangle and ellipse primitives with stroke/shadow/glow support |
|
|
307
398
|
| Export | PNG, JPEG, WebP, file output, base64, data URLs |
|
|
308
399
|
| Serialization | `to_json()` / `from_json()` for built-in layer types and named custom layers |
|
|
@@ -320,7 +411,10 @@ See the shipped examples in [`examples/README.md`](examples/README.md):
|
|
|
320
411
|
- `weight` and `bold=True` are mutually exclusive on text layers and `TextPart`
|
|
321
412
|
- `auto_scale=True` requires `max_width`
|
|
322
413
|
- `position` percentage values must be strings like `"50%"`
|
|
414
|
+
- `fill` and `color` are independent fields; when `fill` is set it takes visual precedence over `color`
|
|
323
415
|
- `canvas.custom(fn)` without a `name` runs during render order but cannot be serialized to JSON; pass `name=` and register the function with `Canvas.register_layer_fn()` to enable serialization
|
|
416
|
+
- `Grain` is valid only on background and image layer `effects`; it is not a valid text or shape effect
|
|
417
|
+
- `Grain(intensity=0.0)` is a no-op (no noise is generated or composited)
|
|
324
418
|
|
|
325
419
|
## Development
|
|
326
420
|
|
|
@@ -333,5 +427,4 @@ uv run ty quickthumb/
|
|
|
333
427
|
|
|
334
428
|
## Reference
|
|
335
429
|
|
|
336
|
-
- Design notes: [DESIGN.md](DESIGN.md)
|
|
337
430
|
- License: [LICENSE](LICENSE)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# quickthumb
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
quickthumb is a Python library for programmatic thumbnail, social card, and promo image generation.
|
|
4
4
|
It is designed for code-first and JSON-first workflows, with a layer-based API that works well for human-authored scripts and AI-generated specs.
|
|
5
5
|
|
|
6
6
|
## Gallery
|
|
@@ -13,7 +13,7 @@ It is designed for code-first and JSON-first workflows, with a layer-based API t
|
|
|
13
13
|
| --- | --- | --- |
|
|
14
14
|
|  |  |  |
|
|
15
15
|
|
|
16
|
-
## Why
|
|
16
|
+
## Why quickthumb
|
|
17
17
|
|
|
18
18
|
- Built for thumbnails and social graphics, not just generic image composition
|
|
19
19
|
- Works with Python method chaining and JSON serialization/deserialization
|
|
@@ -125,6 +125,56 @@ canvas = Canvas(1280, 720).text(
|
|
|
125
125
|
)
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
+
### Gradient and image-filled text
|
|
129
|
+
|
|
130
|
+
Fill text with a gradient or image instead of a flat color. Works on the whole text layer or per `TextPart`.
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from quickthumb import Canvas, LinearGradient, RadialGradient, TextFillImage, TextPart
|
|
134
|
+
|
|
135
|
+
# Gradient headline
|
|
136
|
+
canvas = Canvas(1280, 720).text(
|
|
137
|
+
content="GRADIENT TITLE",
|
|
138
|
+
size=120,
|
|
139
|
+
fill=LinearGradient(
|
|
140
|
+
angle=90,
|
|
141
|
+
stops=[("#FF6B6B", 0.0), ("#FFE66D", 0.5), ("#4ECDC4", 1.0)],
|
|
142
|
+
),
|
|
143
|
+
position=("50%", "50%"),
|
|
144
|
+
align="center",
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Image-filled text
|
|
148
|
+
canvas = Canvas(1280, 720).text(
|
|
149
|
+
content="TEXTURE",
|
|
150
|
+
size=140,
|
|
151
|
+
fill=TextFillImage(path="fire_texture.jpg", fit="cover"),
|
|
152
|
+
position=("50%", "50%"),
|
|
153
|
+
align="center",
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Per-segment fills using TextPart
|
|
157
|
+
canvas = Canvas(1280, 720).text(
|
|
158
|
+
content=[
|
|
159
|
+
TextPart(
|
|
160
|
+
text="HOT ",
|
|
161
|
+
fill=LinearGradient(angle=45, stops=[("#FF4500", 0.0), ("#FFD700", 1.0)]),
|
|
162
|
+
weight=900,
|
|
163
|
+
),
|
|
164
|
+
TextPart(
|
|
165
|
+
text="COLD",
|
|
166
|
+
fill=LinearGradient(angle=45, stops=[("#00BFFF", 0.0), ("#8A2BE2", 1.0)]),
|
|
167
|
+
weight=900,
|
|
168
|
+
),
|
|
169
|
+
],
|
|
170
|
+
size=110,
|
|
171
|
+
position=("50%", "50%"),
|
|
172
|
+
align="center",
|
|
173
|
+
)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`fill` accepts `LinearGradient`, `RadialGradient`, or `TextFillImage`. It is mutually independent of `color` — when `fill` is set it takes visual precedence. A `fill` on a `TextPart` overrides the layer-level `fill` for that segment only.
|
|
177
|
+
|
|
128
178
|
### Image layers
|
|
129
179
|
|
|
130
180
|
```python
|
|
@@ -163,6 +213,42 @@ canvas = Canvas(1280, 720).shape(
|
|
|
163
213
|
)
|
|
164
214
|
```
|
|
165
215
|
|
|
216
|
+
### Grain / noise effect
|
|
217
|
+
|
|
218
|
+
Add film-grain noise to background or image layers via `effects=[Grain(...)]`.
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from quickthumb import Canvas, Grain
|
|
222
|
+
|
|
223
|
+
canvas = (
|
|
224
|
+
Canvas(1280, 720)
|
|
225
|
+
.background(
|
|
226
|
+
color="#1A1A2E",
|
|
227
|
+
effects=[Grain(intensity=0.12, monochrome=True)],
|
|
228
|
+
)
|
|
229
|
+
.image(
|
|
230
|
+
path="portrait.png",
|
|
231
|
+
position=("70%", "50%"),
|
|
232
|
+
width=400,
|
|
233
|
+
height=500,
|
|
234
|
+
align=("center", "middle"),
|
|
235
|
+
effects=[Grain(intensity=0.08, monochrome=False, blend_mode="overlay", opacity=0.6)],
|
|
236
|
+
)
|
|
237
|
+
)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
`Grain` parameters:
|
|
241
|
+
|
|
242
|
+
| Parameter | Type | Default | Description |
|
|
243
|
+
| --- | --- | --- | --- |
|
|
244
|
+
| `intensity` | `float` | required | Noise amplitude, `0.0`–`1.0` |
|
|
245
|
+
| `monochrome` | `bool` | `True` | `True` = luminance noise; `False` = per-channel color noise |
|
|
246
|
+
| `blend_mode` | `str` | `"overlay"` | `"overlay"`, `"screen"`, `"multiply"`, or `"normal"` |
|
|
247
|
+
| `opacity` | `float` | `1.0` | Overall grain strength, `0.0`–`1.0` |
|
|
248
|
+
| `seed` | `int \| None` | `None` | Optional RNG seed for deterministic output |
|
|
249
|
+
|
|
250
|
+
`Grain` is valid in `effects` on **background** and **image** layers. It is serialized with `"type": "grain"` in JSON.
|
|
251
|
+
|
|
166
252
|
### Export helpers
|
|
167
253
|
|
|
168
254
|
```python
|
|
@@ -173,7 +259,7 @@ canvas.render("output.webp", format="WEBP", quality=90)
|
|
|
173
259
|
|
|
174
260
|
## JSON-First Workflow
|
|
175
261
|
|
|
176
|
-
|
|
262
|
+
quickthumb can round-trip most canvases through JSON:
|
|
177
263
|
|
|
178
264
|
```python
|
|
179
265
|
from quickthumb import Canvas
|
|
@@ -189,7 +275,7 @@ config = """
|
|
|
189
275
|
},
|
|
190
276
|
{
|
|
191
277
|
"type": "text",
|
|
192
|
-
"content": "Hello
|
|
278
|
+
"content": "Hello quickthumb",
|
|
193
279
|
"size": 72,
|
|
194
280
|
"color": "#FFFFFF",
|
|
195
281
|
"align": "center",
|
|
@@ -218,12 +304,12 @@ Notes:
|
|
|
218
304
|
|
|
219
305
|
## AI-Friendly Workflows
|
|
220
306
|
|
|
221
|
-
|
|
307
|
+
quickthumb is a good target when you want an LLM to generate image specs that are deterministic and easy to validate.
|
|
222
308
|
|
|
223
309
|
Prompt pattern for Python generation:
|
|
224
310
|
|
|
225
311
|
```text
|
|
226
|
-
Generate
|
|
312
|
+
Generate quickthumb Python code for a 1280x720 YouTube thumbnail.
|
|
227
313
|
Use layered composition only.
|
|
228
314
|
Keep text on the left, subject image on the right, and use high-contrast typography.
|
|
229
315
|
Return runnable code that ends with canvas.render("thumbnail.png").
|
|
@@ -232,20 +318,20 @@ Return runnable code that ends with canvas.render("thumbnail.png").
|
|
|
232
318
|
Prompt pattern for JSON generation:
|
|
233
319
|
|
|
234
320
|
```text
|
|
235
|
-
Generate a
|
|
321
|
+
Generate a quickthumb JSON config with top-level width, height, and layers.
|
|
236
322
|
Use one background image layer, one dark overlay background layer, two text layers, and one outline layer.
|
|
237
|
-
Only use valid
|
|
323
|
+
Only use valid quickthumb layer types and effect names.
|
|
238
324
|
```
|
|
239
325
|
|
|
240
326
|
Recommended workflow:
|
|
241
327
|
|
|
242
|
-
1. Have the model produce
|
|
328
|
+
1. Have the model produce quickthumb Python or JSON.
|
|
243
329
|
2. Validate or render it locally.
|
|
244
330
|
3. Adjust only the content, colors, and assets instead of rewriting layout logic from scratch.
|
|
245
331
|
|
|
246
332
|
## Environment Variables
|
|
247
333
|
|
|
248
|
-
|
|
334
|
+
quickthumb looks for fonts using these environment variables:
|
|
249
335
|
|
|
250
336
|
- `QUICKTHUMB_FONT_DIR`: directory that contains font files
|
|
251
337
|
- `QUICKTHUMB_DEFAULT_FONT`: default font family/name to use when `font` is omitted
|
|
@@ -268,10 +354,12 @@ os.environ["QUICKTHUMB_DEFAULT_FONT"] = "Roboto"
|
|
|
268
354
|
| Background controls | Opacity, blend modes, fit modes, blur, brightness, contrast, saturation |
|
|
269
355
|
| Text | Positioning, alignment, wrapping, letter spacing, line height, rotation, auto-scale |
|
|
270
356
|
| Rich text | Per-segment `TextPart` styling |
|
|
357
|
+
| Text fills | Gradient (`LinearGradient`, `RadialGradient`) and image (`TextFillImage`) fills; per-`TextPart` override |
|
|
271
358
|
| Text effects | Stroke, shadow, glow, background fill |
|
|
272
359
|
| Fonts | Local fonts, CSS-style weights, italic/bold flags, webfont URLs, fallback mapping |
|
|
273
360
|
| Images | Local/remote images, sizing, fit modes, alignment, opacity, rotation |
|
|
274
361
|
| Image effects | Stroke, shadow, glow, filter effects, border radius, background removal |
|
|
362
|
+
| Grain / noise | Per-layer `Grain` effect on background and image layers; monochrome or color noise |
|
|
275
363
|
| Shapes | Rectangle and ellipse primitives with stroke/shadow/glow support |
|
|
276
364
|
| Export | PNG, JPEG, WebP, file output, base64, data URLs |
|
|
277
365
|
| Serialization | `to_json()` / `from_json()` for built-in layer types and named custom layers |
|
|
@@ -289,7 +377,10 @@ See the shipped examples in [`examples/README.md`](examples/README.md):
|
|
|
289
377
|
- `weight` and `bold=True` are mutually exclusive on text layers and `TextPart`
|
|
290
378
|
- `auto_scale=True` requires `max_width`
|
|
291
379
|
- `position` percentage values must be strings like `"50%"`
|
|
380
|
+
- `fill` and `color` are independent fields; when `fill` is set it takes visual precedence over `color`
|
|
292
381
|
- `canvas.custom(fn)` without a `name` runs during render order but cannot be serialized to JSON; pass `name=` and register the function with `Canvas.register_layer_fn()` to enable serialization
|
|
382
|
+
- `Grain` is valid only on background and image layer `effects`; it is not a valid text or shape effect
|
|
383
|
+
- `Grain(intensity=0.0)` is a no-op (no noise is generated or composited)
|
|
293
384
|
|
|
294
385
|
## Development
|
|
295
386
|
|
|
@@ -302,5 +393,4 @@ uv run ty quickthumb/
|
|
|
302
393
|
|
|
303
394
|
## Reference
|
|
304
395
|
|
|
305
|
-
- Design notes: [DESIGN.md](DESIGN.md)
|
|
306
396
|
- License: [LICENSE](LICENSE)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Reference for quickthumb background layers, including colors, gradients, images, opacity, effects, and render order.
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# Background
|
|
2
6
|
|
|
3
7
|
`.background()` adds a full-canvas background layer. Multiple background calls stack in render order — first call is backmost.
|
|
@@ -26,7 +30,7 @@ canvas.background(
|
|
|
26
30
|
| `opacity` | `float` | `1.0` | Layer opacity from `0.0` (transparent) to `1.0` (opaque). |
|
|
27
31
|
| `blend_mode` | `str \| BlendMode \| None` | `None` | How this layer composites over previous layers. See [BlendMode](enums.md#blendmode). |
|
|
28
32
|
| `fit` | `str \| FitMode \| None` | `None` | How an image fills the canvas. See [FitMode](enums.md#fitmode). |
|
|
29
|
-
| `effects` | `list
|
|
33
|
+
| `effects` | `list \| None` | `[]` | Background effects. Accepts `Filter` and `Grain`. |
|
|
30
34
|
|
|
31
35
|
## Examples
|
|
32
36
|
|
|
@@ -75,4 +79,4 @@ canvas.background(color="#000000", opacity=0.4)
|
|
|
75
79
|
- `color`, `gradient`, and `image` can each be used independently or together within one layer.
|
|
76
80
|
- `blend_mode` applies when compositing this layer over previous layers.
|
|
77
81
|
- Supports both local file paths and remote URLs for `image`.
|
|
78
|
-
- For available effects, see [Filter](effects.md#filter).
|
|
82
|
+
- For available effects, see [Filter](effects.md#filter) and [Grain](effects.md#grain).
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Reference for quickthumb effects, including filters, shadows, strokes, blend modes, opacity, and layer compatibility.
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# Effects
|
|
2
6
|
|
|
3
7
|
Effects are modifiers applied to layers. Each layer type accepts a specific set.
|
|
@@ -9,6 +13,7 @@ Effects are modifiers applied to layers. Each layer type accepts a specific set.
|
|
|
9
13
|
| `Glow` | ✓ | ✓ | ✓ | — |
|
|
10
14
|
| `Filter` | — | ✓ | — | ✓ |
|
|
11
15
|
| `Background` | ✓ | — | — | — |
|
|
16
|
+
| `Grain` | — | ✓ | — | ✓ |
|
|
12
17
|
|
|
13
18
|
Pass effects as a list to the `effects` parameter of any layer:
|
|
14
19
|
|
|
@@ -193,3 +198,50 @@ canvas.text(
|
|
|
193
198
|
|
|
194
199
|
!!! note
|
|
195
200
|
`Background` as an effect is separate from the `.background()` layer builder. The effect applies behind a text block; the layer covers the full canvas.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Grain
|
|
205
|
+
|
|
206
|
+
Adds film-grain noise to a background or image layer.
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
from quickthumb import Grain
|
|
210
|
+
|
|
211
|
+
Grain(intensity=0.12, monochrome=True, blend_mode="overlay", opacity=1.0)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
| Parameter | Type | Default | Description |
|
|
215
|
+
| --- | --- | --- | --- |
|
|
216
|
+
| `intensity` | `float` | **required** | Noise amplitude from `0.0` (none) to `1.0` (maximum). |
|
|
217
|
+
| `monochrome` | `bool` | `True` | `True` = luminance noise (grey grain); `False` = independent per-channel color noise. |
|
|
218
|
+
| `blend_mode` | `str` | `"overlay"` | How noise composites onto the layer. One of `"overlay"`, `"screen"`, `"multiply"`, `"normal"`. |
|
|
219
|
+
| `opacity` | `float` | `1.0` | Overall grain strength from `0.0` (invisible) to `1.0` (full). |
|
|
220
|
+
| `seed` | `int \| None` | `None` | RNG seed for deterministic output. `None` = random each render. |
|
|
221
|
+
|
|
222
|
+
`intensity=0.0` is a no-op — no noise is generated.
|
|
223
|
+
|
|
224
|
+
### Example
|
|
225
|
+
|
|
226
|
+
```python
|
|
227
|
+
from quickthumb import Canvas, Grain
|
|
228
|
+
|
|
229
|
+
canvas = (
|
|
230
|
+
Canvas(1280, 720)
|
|
231
|
+
.background(
|
|
232
|
+
color="#1A1A2E",
|
|
233
|
+
effects=[Grain(intensity=0.12, monochrome=True)],
|
|
234
|
+
)
|
|
235
|
+
.image(
|
|
236
|
+
path="portrait.png",
|
|
237
|
+
position=("70%", "50%"),
|
|
238
|
+
width=400,
|
|
239
|
+
height=500,
|
|
240
|
+
align=("center", "middle"),
|
|
241
|
+
effects=[Grain(intensity=0.08, monochrome=False, blend_mode="overlay", opacity=0.6)],
|
|
242
|
+
)
|
|
243
|
+
)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
!!! note
|
|
247
|
+
`Grain` is valid only on **background** and **image** layers. It is not available on text or shape layers.
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Reference for quickthumb enums and gradient models used for alignment, fit modes, shapes, filters, and color transitions.
|
|
3
|
+
---
|
|
4
|
+
|
|
1
5
|
# Enums & Gradients
|
|
2
6
|
|
|
3
7
|
## Align
|
|
@@ -192,3 +196,52 @@ canvas.background(
|
|
|
192
196
|
),
|
|
193
197
|
)
|
|
194
198
|
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## TextFillImage
|
|
203
|
+
|
|
204
|
+
An image used as a fill for text glyphs. The image is scaled to the text bounding box and masked to the glyph shapes.
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
from quickthumb import TextFillImage
|
|
208
|
+
|
|
209
|
+
TextFillImage(
|
|
210
|
+
path="fire_texture.jpg",
|
|
211
|
+
fit="cover",
|
|
212
|
+
)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
| Parameter | Type | Default | Description |
|
|
216
|
+
| --- | --- | --- | --- |
|
|
217
|
+
| `path` | `str` | **required** | Local file path or remote URL for the fill image. |
|
|
218
|
+
| `fit` | `str \| FitMode` | `"cover"` | How the image is scaled to the text bounding box. `"cover"`, `"contain"`, or `"fill"`. |
|
|
219
|
+
|
|
220
|
+
### Examples
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
from quickthumb import Canvas, TextFillImage
|
|
224
|
+
|
|
225
|
+
# Local file
|
|
226
|
+
canvas.text(
|
|
227
|
+
content="FIRE",
|
|
228
|
+
size=140,
|
|
229
|
+
fill=TextFillImage(path="assets/fire_texture.jpg", fit="cover"),
|
|
230
|
+
position=("50%", "50%"),
|
|
231
|
+
align="center",
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
# Remote URL
|
|
235
|
+
canvas.text(
|
|
236
|
+
content="MARBLE",
|
|
237
|
+
size=140,
|
|
238
|
+
fill=TextFillImage(
|
|
239
|
+
path="https://example.com/textures/marble.jpg",
|
|
240
|
+
fit="cover",
|
|
241
|
+
),
|
|
242
|
+
position=("50%", "50%"),
|
|
243
|
+
align="center",
|
|
244
|
+
)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Pass `TextFillImage` to the `fill` parameter of `.text()` or `TextPart`. See [Gradient and image fills](text.md#gradient-and-image-fills) for usage in context.
|