quickthumb 0.4.0__tar.gz → 0.6.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.4.0 → quickthumb-0.6.0}/.github/workflows/publish.yaml +1 -1
- quickthumb-0.6.0/PKG-INFO +563 -0
- quickthumb-0.6.0/README.md +525 -0
- quickthumb-0.6.0/assets/brand/apple-touch-icon.png +0 -0
- quickthumb-0.6.0/assets/brand/favicon.ico +0 -0
- quickthumb-0.6.0/assets/brand/favicon.png +0 -0
- quickthumb-0.6.0/assets/brand/quickthumb-icon-1024.png +0 -0
- quickthumb-0.6.0/assets/brand/quickthumb-icon-152.png +0 -0
- quickthumb-0.6.0/assets/brand/quickthumb-icon-16.png +0 -0
- quickthumb-0.6.0/assets/brand/quickthumb-icon-167.png +0 -0
- quickthumb-0.6.0/assets/brand/quickthumb-icon-180.png +0 -0
- quickthumb-0.6.0/assets/brand/quickthumb-icon-192.png +0 -0
- quickthumb-0.6.0/assets/brand/quickthumb-icon-256.png +0 -0
- quickthumb-0.6.0/assets/brand/quickthumb-icon-32.png +0 -0
- quickthumb-0.6.0/assets/brand/quickthumb-icon-48.png +0 -0
- quickthumb-0.6.0/assets/brand/quickthumb-icon-512.png +0 -0
- quickthumb-0.6.0/assets/brand/quickthumb-icon-64.png +0 -0
- quickthumb-0.6.0/assets/brand/quickthumb-icon-master-transparent.png +0 -0
- quickthumb-0.6.0/assets/brand/quickthumb-icon-master.png +0 -0
- quickthumb-0.6.0/assets/brand/social-preview.png +0 -0
- quickthumb-0.6.0/assets/images/spark.svg +4 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/api/background.md +6 -2
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/api/canvas.md +38 -2
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/api/effects.md +52 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/api/enums.md +53 -0
- quickthumb-0.6.0/docs/api/group.md +148 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/api/image.md +5 -1
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/api/index.md +14 -5
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/api/outline.md +4 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/api/shape.md +85 -2
- quickthumb-0.6.0/docs/api/svg.md +95 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/api/text.md +57 -0
- quickthumb-0.6.0/docs/assets/brand/apple-touch-icon.png +0 -0
- quickthumb-0.6.0/docs/assets/brand/favicon.ico +0 -0
- quickthumb-0.6.0/docs/assets/brand/favicon.png +0 -0
- quickthumb-0.6.0/docs/assets/brand/quickthumb-icon-192.png +0 -0
- quickthumb-0.6.0/docs/assets/brand/quickthumb-icon-512.png +0 -0
- quickthumb-0.6.0/docs/assets/brand/quickthumb-icon.png +0 -0
- quickthumb-0.6.0/docs/assets/brand/social-preview.png +0 -0
- quickthumb-0.6.0/docs/assets/examples/launch_announcement.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/changelog.md +4 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/concepts.md +62 -4
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/cookbook/ai-workflow.md +10 -6
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/cookbook/index.md +10 -5
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/cookbook/instagram-card.md +4 -0
- quickthumb-0.6.0/docs/cookbook/launch-announcement.md +258 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/cookbook/podcast-promo.md +5 -1
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/cookbook/shorts-cover.md +4 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/cookbook/webfonts-rembg.md +6 -2
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/cookbook/youtube-thumbnail.md +6 -2
- quickthumb-0.6.0/docs/diagnostics.md +112 -0
- quickthumb-0.6.0/docs/exports.md +76 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/faq.md +44 -2
- quickthumb-0.6.0/docs/favicon.ico +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/getting-started.md +20 -3
- quickthumb-0.6.0/docs/googleafa40cbb47cddae5.html +1 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/index.md +20 -3
- quickthumb-0.6.0/docs/installation.md +99 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/json-schema.md +181 -15
- quickthumb-0.6.0/docs/overrides/main.html +31 -0
- quickthumb-0.6.0/docs/robots.txt +4 -0
- quickthumb-0.6.0/docs/site.webmanifest +19 -0
- quickthumb-0.6.0/docs/stylesheets/extra.css +452 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/examples/README.md +25 -4
- quickthumb-0.6.0/examples/launch_announcement.json +190 -0
- quickthumb-0.6.0/examples/launch_announcement.png +0 -0
- quickthumb-0.6.0/examples/launch_announcement.py +48 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/examples/shorts_cover_agent.py +1 -1
- {quickthumb-0.4.0 → quickthumb-0.6.0}/mkdocs.yml +13 -1
- quickthumb-0.6.0/overrides/main.html +9 -0
- quickthumb-0.6.0/overrides/partials/header.html +80 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/pyproject.toml +18 -1
- {quickthumb-0.4.0 → quickthumb-0.6.0}/quickthumb/__init__.py +12 -1
- quickthumb-0.6.0/quickthumb/_base.py +108 -0
- quickthumb-0.6.0/quickthumb/_diagnostics.py +269 -0
- quickthumb-0.6.0/quickthumb/_effects.py +374 -0
- quickthumb-0.6.0/quickthumb/_entry.py +18 -0
- quickthumb-0.6.0/quickthumb/_export_base.py +609 -0
- quickthumb-0.6.0/quickthumb/_export_pptx.py +636 -0
- quickthumb-0.6.0/quickthumb/_export_svg.py +615 -0
- quickthumb-0.6.0/quickthumb/_fonts.py +128 -0
- quickthumb-0.6.0/quickthumb/_groups.py +213 -0
- quickthumb-0.6.0/quickthumb/_images.py +294 -0
- quickthumb-0.6.0/quickthumb/_shapes.py +90 -0
- quickthumb-0.6.0/quickthumb/_text.py +1548 -0
- quickthumb-0.6.0/quickthumb/canvas.py +847 -0
- quickthumb-0.6.0/quickthumb/cli.py +225 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/quickthumb/models.py +240 -20
- quickthumb-0.6.0/quickthumb/templates/instagram-square.json +18 -0
- quickthumb-0.6.0/quickthumb/templates/og-image.json +18 -0
- quickthumb-0.6.0/quickthumb/templates/twitter-card.json +18 -0
- quickthumb-0.6.0/quickthumb/templates/youtube-16x9.json +18 -0
- quickthumb-0.6.0/tests/_optional.py +14 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/conftest.py +19 -0
- quickthumb-0.6.0/tests/fixtures/sample.svg +5 -0
- quickthumb-0.6.0/tests/snapshots/auto_scale_rich_text.png +0 -0
- quickthumb-0.6.0/tests/snapshots/auto_scale_simple_text.png +0 -0
- quickthumb-0.6.0/tests/snapshots/custom_canvas_layer.png +0 -0
- quickthumb-0.6.0/tests/snapshots/explicit_newlines_preserved_with_max_width.png +0 -0
- quickthumb-0.6.0/tests/snapshots/export_svg_group_layout.png +0 -0
- quickthumb-0.6.0/tests/snapshots/export_svg_image_and_blend.png +0 -0
- quickthumb-0.6.0/tests/snapshots/export_svg_rich_text.png +0 -0
- quickthumb-0.6.0/tests/snapshots/export_svg_text_styles.png +0 -0
- quickthumb-0.6.0/tests/snapshots/export_svg_vector_shapes.png +0 -0
- quickthumb-0.6.0/tests/snapshots/grain_effect_on_background.png +0 -0
- quickthumb-0.6.0/tests/snapshots/group_thumbnail_layout.png +0 -0
- quickthumb-0.6.0/tests/snapshots/image_overlay_fit_contain.png +0 -0
- quickthumb-0.6.0/tests/snapshots/image_overlay_fit_cover.png +0 -0
- quickthumb-0.6.0/tests/snapshots/image_overlay_fit_fill.png +0 -0
- quickthumb-0.6.0/tests/snapshots/image_with_text_overlay.png +0 -0
- quickthumb-0.6.0/tests/snapshots/jpeg_format.jpg +0 -0
- quickthumb-0.6.0/tests/snapshots/multiline_text_with_background.png +0 -0
- quickthumb-0.6.0/tests/snapshots/multiline_text_with_fill.png +0 -0
- quickthumb-0.6.0/tests/snapshots/percentage_with_alignment.png +0 -0
- quickthumb-0.6.0/tests/snapshots/rich_text_advanced_styles.png +0 -0
- quickthumb-0.6.0/tests/snapshots/rich_text_alignment.png +0 -0
- quickthumb-0.6.0/tests/snapshots/rich_text_different_colors.png +0 -0
- quickthumb-0.6.0/tests/snapshots/rich_text_mixed_fonts.png +0 -0
- quickthumb-0.6.0/tests/snapshots/rich_text_mixed_styles.png +0 -0
- quickthumb-0.6.0/tests/snapshots/rich_text_with_background.png +0 -0
- quickthumb-0.6.0/tests/snapshots/rich_text_with_effects.png +0 -0
- quickthumb-0.6.0/tests/snapshots/rich_text_with_per_part_fill.png +0 -0
- quickthumb-0.6.0/tests/snapshots/rich_text_wrapping.png +0 -0
- quickthumb-0.6.0/tests/snapshots/roboto_bold.png +0 -0
- quickthumb-0.6.0/tests/snapshots/roboto_bold_italic.png +0 -0
- quickthumb-0.6.0/tests/snapshots/roboto_italic.png +0 -0
- quickthumb-0.6.0/tests/snapshots/shape_primitives.png +0 -0
- quickthumb-0.6.0/tests/snapshots/star_rotated_effects.png +0 -0
- quickthumb-0.6.0/tests/snapshots/svg_layer.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_alignment.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_bold_and_italic.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_fill_with_letter_spacing.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_fill_with_stroke.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_no_wrapping.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_rendering.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_rotation_basic.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_rotation_centered.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_rotation_with_effects.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_weight_black.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_weight_light.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_with_background_and_effects.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_with_background_basic.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_with_basic_shadow.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_with_blurred_shadow.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_with_glow.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_with_image_fill.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_with_line_height.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_with_linear_gradient_fill.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_with_multiple_glows.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_with_radial_gradient_fill.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_with_stroke.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_wrapping_center_aligned.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_wrapping_center_aligned_percentage.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_wrapping_left_aligned.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_wrapping_long_word_overflow.png +0 -0
- quickthumb-0.6.0/tests/snapshots/text_wrapping_right_aligned.png +0 -0
- quickthumb-0.6.0/tests/snapshots/webfont_basic_rendering.png +0 -0
- quickthumb-0.6.0/tests/snapshots/webp_format.webp +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/test_background_layers.py +170 -12
- quickthumb-0.6.0/tests/test_canvas.py +842 -0
- quickthumb-0.6.0/tests/test_cli.py +436 -0
- quickthumb-0.6.0/tests/test_diagnostics.py +393 -0
- quickthumb-0.6.0/tests/test_docs_seo.py +55 -0
- quickthumb-0.6.0/tests/test_export_pptx.py +410 -0
- quickthumb-0.6.0/tests/test_export_snapshots.py +347 -0
- quickthumb-0.6.0/tests/test_export_svg.py +783 -0
- quickthumb-0.6.0/tests/test_group_layers.py +502 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/test_image_layers.py +7 -6
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/test_rendering.py +615 -9
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/test_shape_layers.py +156 -3
- quickthumb-0.6.0/tests/test_svg_layers.py +224 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/test_text_layers.py +233 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/uv.lock +478 -2
- quickthumb-0.4.0/.claude/settings.json +0 -34
- quickthumb-0.4.0/.claude/skills/tdd/SKILL.md +0 -29
- quickthumb-0.4.0/.vscode/extensions.json +0 -3
- quickthumb-0.4.0/.vscode/settings.json +0 -13
- quickthumb-0.4.0/CLAUDE.md +0 -30
- quickthumb-0.4.0/DESIGN.md +0 -606
- quickthumb-0.4.0/PKG-INFO +0 -337
- quickthumb-0.4.0/README.md +0 -306
- quickthumb-0.4.0/docs/installation.md +0 -56
- quickthumb-0.4.0/docs/stylesheets/extra.css +0 -83
- quickthumb-0.4.0/quickthumb/canvas.py +0 -2466
- 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/tests/test_canvas.py +0 -403
- {quickthumb-0.4.0 → quickthumb-0.6.0}/.github/workflows/docs.yml +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/.gitignore +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/LICENSE +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSans-Black.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSans-BlackItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSans-Bold.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSans-BoldItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSans-ExtraBold.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSans-ExtraBoldItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSans-ExtraLight.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSans-ExtraLightItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSans-Italic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSans-Light.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSans-LightItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSans-Medium.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSans-MediumItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSans-Regular.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSans-SemiBold.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSans-SemiBoldItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSans-Thin.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSans-ThinItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSerif-Black.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSerif-BlackItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSerif-Bold.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSerif-BoldItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSerif-ExtraBold.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSerif-ExtraBoldItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSerif-ExtraLight.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSerif-ExtraLightItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSerif-Italic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSerif-Light.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSerif-LightItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSerif-Medium.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSerif-MediumItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSerif-Regular.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSerif-SemiBold.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSerif-SemiBoldItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSerif-Thin.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/NotoSerif-ThinItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/Roboto-Black.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/Roboto-BlackItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/Roboto-Bold.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/Roboto-BoldItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/Roboto-ExtraBold.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/Roboto-ExtraBoldItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/Roboto-ExtraLight.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/Roboto-ExtraLightItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/Roboto-Italic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/Roboto-Light.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/Roboto-LightItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/Roboto-Medium.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/Roboto-MediumItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/Roboto-Regular.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/Roboto-SemiBold.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/Roboto-SemiBoldItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/Roboto-Thin.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/fonts/Roboto-ThinItalic.ttf +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/images/c-g-JgDUVGAXsso-unsplash.jpg +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/images/denise-jans-WIRvXd1PYlg-unsplash.jpg +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/assets/images/tobias-rademacher-wnF27F85ZKw-unsplash.jpg +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/assets/examples/instagram_news_card.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/assets/examples/podcast_interview_promo.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/assets/examples/shorts_cover_agent.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/assets/examples/youtube_reaction.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/assets/examples/youtube_talking_head.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/assets/examples/youtube_thumbnail_01.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/assets/examples/youtube_thumbnail_02.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/docs/assets/examples/youtube_tutorial_explainer.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/examples/instagram_news_card.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/examples/instagram_news_card.py +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/examples/podcast_interview_promo.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/examples/podcast_interview_promo.py +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/examples/shorts_cover_agent.json +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/examples/shorts_cover_agent.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/examples/youtube_reaction.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/examples/youtube_reaction.py +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/examples/youtube_talking_head.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/examples/youtube_talking_head.py +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/examples/youtube_thumbnail_01.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/examples/youtube_thumbnail_01.py +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/examples/youtube_thumbnail_02.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/examples/youtube_thumbnail_02.py +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/examples/youtube_tutorial_explainer.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/examples/youtube_tutorial_explainer.py +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/quickthumb/errors.py +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/quickthumb/font_cache.py +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/__init__.py +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/fixtures/ivana-cajina-_7LbC5J-jw4-unsplash.jpg +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/fixtures/sample_image.jpg +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/fixtures/tobias-rademacher-wnF27F85ZKw-unsplash.jpg +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/arial_bold.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/arial_bold_italic.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/arial_italic.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/background_blur_heavy.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/background_blur_soft.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/background_combined_filters.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/background_contrast_high.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/background_contrast_low.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/background_filter_effect.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/background_saturation_grayscale.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/background_saturation_vivid.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/blend_mode_darken.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/blend_mode_lighten.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/blend_mode_multiply.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/blend_mode_normal.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/blend_mode_overlay.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/blend_mode_screen.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/bold_italic_with_named_font.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/bold_with_named_font.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/custom_layer_with_kwargs.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/font_name_works.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/image_background_basic.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/image_background_with_opacity.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/image_basic.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/image_blend_mode_darken.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/image_blend_mode_lighten.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/image_blend_mode_multiply.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/image_blend_mode_normal.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/image_blend_mode_overlay.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/image_blend_mode_screen.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/image_brightness_decrease.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/image_brightness_increase.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/image_filter_effect.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/image_fit_contain.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/image_fit_cover.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/image_fit_fill.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/image_on_background_image.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/image_percentage_position.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/image_with_border_radius.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/image_with_drop_shadow.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/image_with_glow.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/image_with_opacity.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/image_with_remove_background.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/image_with_rotation.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/image_with_stroke.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/italic_with_named_font.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/linear_gradient_brightness_decrease.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/linear_gradient_brightness_increase.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/linear_gradient_diagonal.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/linear_gradient_horizontal.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/linear_gradient_with_alpha.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/linear_gradient_with_opacity.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/multiline_text_with_glow.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/multiline_text_with_shadow.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/multiple_images.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/outline_basic.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/outline_with_offset.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/radial_gradient_brightness_decrease.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/radial_gradient_brightness_increase.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/radial_gradient_centered.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/radial_gradient_with_opacity.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/shape_ellipse_basic.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/shape_rectangle_basic.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/shape_rectangle_with_border_radius.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/shape_with_effects.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/shape_with_opacity.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/shape_with_rotation.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/solid_background.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/solid_color_brightness_decrease.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/solid_color_brightness_increase.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/text_and_outline_with_opacity.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/text_with_letter_spacing.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/text_with_letter_spacing_glow.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/text_with_letter_spacing_shadow.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/times_new_roman_bold.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/times_new_roman_bold_italic.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/times_new_roman_italic.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/snapshots/url_image_background_basic.png +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/test_font_cache.py +0 -0
- {quickthumb-0.4.0 → quickthumb-0.6.0}/tests/test_outline_layers.py +0 -0
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: quickthumb
|
|
3
|
+
Version: 0.6.0
|
|
4
|
+
Summary: Programmatic thumbnail and social image generation with layered Python and JSON APIs
|
|
5
|
+
Project-URL: Homepage, https://github.com/sjquant/quickthumb
|
|
6
|
+
Project-URL: Repository, https://github.com/sjquant/quickthumb
|
|
7
|
+
Project-URL: Issues, https://github.com/sjquant/quickthumb/issues
|
|
8
|
+
Project-URL: Documentation, https://github.com/sjquant/quickthumb#readme
|
|
9
|
+
Author: Seonu Jang
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: ai,automation,graphics,image-generation,pillow,social-media,thumbnail,thumbnails
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Artistic Software
|
|
21
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Requires-Python: >=3.10
|
|
24
|
+
Requires-Dist: pillow<11.0.0,>=10.4.0
|
|
25
|
+
Requires-Dist: pydantic<3.0.0,>=2.12.5
|
|
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'
|
|
30
|
+
Provides-Extra: pptx
|
|
31
|
+
Requires-Dist: python-pptx<2.0,>=1.0.2; extra == 'pptx'
|
|
32
|
+
Provides-Extra: rembg
|
|
33
|
+
Requires-Dist: onnxruntime<2.0.0,>=1.24.1; extra == 'rembg'
|
|
34
|
+
Requires-Dist: rembg<3.0.0,>=2.0.50; extra == 'rembg'
|
|
35
|
+
Provides-Extra: svg
|
|
36
|
+
Requires-Dist: cairosvg<3.0.0,>=2.7.0; extra == 'svg'
|
|
37
|
+
Description-Content-Type: text/markdown
|
|
38
|
+
|
|
39
|
+
# quickthumb
|
|
40
|
+
|
|
41
|
+
quickthumb is a Python library for programmatic thumbnail, social card, and promo image generation.
|
|
42
|
+
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.
|
|
43
|
+
|
|
44
|
+
## Gallery
|
|
45
|
+
|
|
46
|
+
| YouTube Thumbnail | Burnout Thumbnail | Instagram News Card |
|
|
47
|
+
| --- | --- | --- |
|
|
48
|
+
|  |  |  |
|
|
49
|
+
|
|
50
|
+
| Talking Head | Reaction / Commentary | Tutorial / Explainer |
|
|
51
|
+
| --- | --- | --- |
|
|
52
|
+
|  |  |  |
|
|
53
|
+
|
|
54
|
+

|
|
55
|
+
|
|
56
|
+
*One JSON spec, zero hand-placed text coordinates: [`examples/launch_announcement.json`](examples/launch_announcement.json) combines auto-layout groups, theme tokens, star shapes, SVG layers, grain, and `diagnose()`.*
|
|
57
|
+
|
|
58
|
+
## Why quickthumb
|
|
59
|
+
|
|
60
|
+
- Built for thumbnails and social graphics, not just generic image composition
|
|
61
|
+
- Works with Python method chaining and JSON serialization/deserialization
|
|
62
|
+
- Handles gradients, remote images, rich text, shapes, blend modes, and export helpers
|
|
63
|
+
- Good fit for AI-assisted workflows that need deterministic image specs
|
|
64
|
+
|
|
65
|
+
## Installation
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
uv pip install quickthumb
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Optional background removal support:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
uv pip install "quickthumb[rembg]"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Optional SVG layer support:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
uv pip install "quickthumb[svg]"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Optional PowerPoint (PPTX) export support:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
uv pip install "quickthumb[pptx]"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Quick Start
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from quickthumb import Background, Canvas, Filter, Shadow, Stroke, TextPart
|
|
93
|
+
|
|
94
|
+
canvas = (
|
|
95
|
+
Canvas.from_aspect_ratio("16:9", base_width=1280)
|
|
96
|
+
.background(
|
|
97
|
+
image="https://images.unsplash.com/photo-1516321318423-f06f85e504b3",
|
|
98
|
+
effects=[Filter(brightness=0.65)],
|
|
99
|
+
)
|
|
100
|
+
.background(color="#000000", opacity=0.45)
|
|
101
|
+
.text(
|
|
102
|
+
content=[
|
|
103
|
+
TextPart(
|
|
104
|
+
text="BUILD THUMBNAILS\nFAST\n",
|
|
105
|
+
color="#B8FF00",
|
|
106
|
+
effects=[Stroke(width=8, color="#000000")],
|
|
107
|
+
),
|
|
108
|
+
TextPart(
|
|
109
|
+
text="With Python or JSON specs",
|
|
110
|
+
color="#F5F5F5",
|
|
111
|
+
size=44,
|
|
112
|
+
effects=[Shadow(offset_x=2, offset_y=2, color="#000000", blur_radius=4)],
|
|
113
|
+
),
|
|
114
|
+
],
|
|
115
|
+
size=112,
|
|
116
|
+
position=("8%", "50%"),
|
|
117
|
+
align=("left", "middle"),
|
|
118
|
+
weight=900,
|
|
119
|
+
)
|
|
120
|
+
.outline(width=14, color="#B8FF00")
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
canvas.render("thumbnail.png")
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Core API
|
|
127
|
+
|
|
128
|
+
### Create a canvas
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from quickthumb import Canvas
|
|
132
|
+
|
|
133
|
+
canvas = Canvas(1280, 720)
|
|
134
|
+
square = Canvas.from_aspect_ratio("1:1", base_width=1080)
|
|
135
|
+
vertical = Canvas.from_aspect_ratio("9:16", base_width=1080)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Background layers
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from quickthumb import Canvas, Filter, FitMode, LinearGradient
|
|
142
|
+
|
|
143
|
+
canvas = (
|
|
144
|
+
Canvas(1280, 720)
|
|
145
|
+
.background(color="#101828")
|
|
146
|
+
.background(
|
|
147
|
+
gradient=LinearGradient(
|
|
148
|
+
angle=120,
|
|
149
|
+
stops=[("#0F172A", 0.0), ("#0F172A00", 1.0)],
|
|
150
|
+
),
|
|
151
|
+
)
|
|
152
|
+
.background(
|
|
153
|
+
image="hero.jpg",
|
|
154
|
+
fit=FitMode.COVER,
|
|
155
|
+
blend_mode="multiply",
|
|
156
|
+
effects=[Filter(blur=4, brightness=0.75, contrast=1.1, saturation=0.9)],
|
|
157
|
+
)
|
|
158
|
+
)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Text layers and rich text
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
from quickthumb import Background, Canvas, Glow, Shadow, Stroke, TextPart
|
|
165
|
+
|
|
166
|
+
canvas = Canvas(1280, 720).text(
|
|
167
|
+
content=[
|
|
168
|
+
TextPart(text="5 ", color="#FBBF24", weight=900),
|
|
169
|
+
TextPart(text="WARNING SIGNS", color="#FFFFFF", weight=900),
|
|
170
|
+
],
|
|
171
|
+
size=72,
|
|
172
|
+
position=(80, 540),
|
|
173
|
+
effects=[
|
|
174
|
+
Background(color="#111827CC", padding=(16, 22), border_radius=12),
|
|
175
|
+
Stroke(width=2, color="#000000"),
|
|
176
|
+
Shadow(offset_x=4, offset_y=4, color="#000000", blur_radius=8),
|
|
177
|
+
Glow(color="#F59E0B", radius=14, opacity=0.35),
|
|
178
|
+
],
|
|
179
|
+
)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Gradient and image-filled text
|
|
183
|
+
|
|
184
|
+
Fill text with a gradient or image instead of a flat color. Works on the whole text layer or per `TextPart`.
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
from quickthumb import Canvas, LinearGradient, RadialGradient, TextFillImage, TextPart
|
|
188
|
+
|
|
189
|
+
# Gradient headline
|
|
190
|
+
canvas = Canvas(1280, 720).text(
|
|
191
|
+
content="GRADIENT TITLE",
|
|
192
|
+
size=120,
|
|
193
|
+
fill=LinearGradient(
|
|
194
|
+
angle=90,
|
|
195
|
+
stops=[("#FF6B6B", 0.0), ("#FFE66D", 0.5), ("#4ECDC4", 1.0)],
|
|
196
|
+
),
|
|
197
|
+
position=("50%", "50%"),
|
|
198
|
+
align="center",
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# Image-filled text
|
|
202
|
+
canvas = Canvas(1280, 720).text(
|
|
203
|
+
content="TEXTURE",
|
|
204
|
+
size=140,
|
|
205
|
+
fill=TextFillImage(path="fire_texture.jpg", fit="cover"),
|
|
206
|
+
position=("50%", "50%"),
|
|
207
|
+
align="center",
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# Per-segment fills using TextPart
|
|
211
|
+
canvas = Canvas(1280, 720).text(
|
|
212
|
+
content=[
|
|
213
|
+
TextPart(
|
|
214
|
+
text="HOT ",
|
|
215
|
+
fill=LinearGradient(angle=45, stops=[("#FF4500", 0.0), ("#FFD700", 1.0)]),
|
|
216
|
+
weight=900,
|
|
217
|
+
),
|
|
218
|
+
TextPart(
|
|
219
|
+
text="COLD",
|
|
220
|
+
fill=LinearGradient(angle=45, stops=[("#00BFFF", 0.0), ("#8A2BE2", 1.0)]),
|
|
221
|
+
weight=900,
|
|
222
|
+
),
|
|
223
|
+
],
|
|
224
|
+
size=110,
|
|
225
|
+
position=("50%", "50%"),
|
|
226
|
+
align="center",
|
|
227
|
+
)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
`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.
|
|
231
|
+
|
|
232
|
+
### Image layers
|
|
233
|
+
|
|
234
|
+
```python
|
|
235
|
+
from quickthumb import Canvas, Filter
|
|
236
|
+
|
|
237
|
+
canvas = Canvas(1280, 720).image(
|
|
238
|
+
path="portrait.png",
|
|
239
|
+
position=("73%", "52%"),
|
|
240
|
+
width=420,
|
|
241
|
+
height=520,
|
|
242
|
+
fit="cover",
|
|
243
|
+
align=("center", "middle"),
|
|
244
|
+
border_radius=24,
|
|
245
|
+
remove_background=True,
|
|
246
|
+
blend_mode="normal",
|
|
247
|
+
effects=[Filter(contrast=1.1, saturation=1.05)],
|
|
248
|
+
)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Shape layers
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
from quickthumb import Canvas, Shadow, Stroke
|
|
255
|
+
|
|
256
|
+
canvas = Canvas(1280, 720).shape(
|
|
257
|
+
shape="rectangle",
|
|
258
|
+
position=(64, 60),
|
|
259
|
+
width=320,
|
|
260
|
+
height=88,
|
|
261
|
+
color="#CC0000",
|
|
262
|
+
border_radius=10,
|
|
263
|
+
effects=[
|
|
264
|
+
Stroke(width=2, color="#FFFFFF"),
|
|
265
|
+
Shadow(offset_x=0, offset_y=6, color="#000000", blur_radius=12),
|
|
266
|
+
],
|
|
267
|
+
)
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Beyond `rectangle` and `ellipse`, shape layers support `pill`, `triangle`, `star`, and `polygon`:
|
|
271
|
+
|
|
272
|
+
```python
|
|
273
|
+
canvas = (
|
|
274
|
+
Canvas(1280, 720)
|
|
275
|
+
.shape(shape="pill", position=(64, 60), width=200, height=56, color="#B8FF00")
|
|
276
|
+
.shape(shape="star", position=(400, 60), width=120, height=120, color="#FFD700",
|
|
277
|
+
star_points=6, inner_radius=0.4)
|
|
278
|
+
.shape(
|
|
279
|
+
shape="polygon", # normalized 0..1 points inside the shape box
|
|
280
|
+
position=(600, 60),
|
|
281
|
+
width=160,
|
|
282
|
+
height=100,
|
|
283
|
+
color="#53BF9D",
|
|
284
|
+
points=[(0.0, 0.25), (0.6, 0.25), (0.6, 0.0), (1.0, 0.5), (0.6, 1.0), (0.6, 0.75), (0.0, 0.75)],
|
|
285
|
+
)
|
|
286
|
+
)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### SVG layers
|
|
290
|
+
|
|
291
|
+
Rasterize SVG icons and logos at render time (requires `quickthumb[svg]`):
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
canvas = Canvas(1280, 720).svg(
|
|
295
|
+
path="logo.svg",
|
|
296
|
+
position=("90%", "8%"),
|
|
297
|
+
width=120,
|
|
298
|
+
align=("right", "top"),
|
|
299
|
+
)
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
`width`/`height` control the raster size (aspect ratio is preserved when only one is set). SVG layers support `opacity`, `rotation`, `align`, `blend_mode`, and the same `effects` as image layers.
|
|
303
|
+
|
|
304
|
+
### Group layers (auto layout)
|
|
305
|
+
|
|
306
|
+
Stop hand-placing coordinates: a `group` measures its children and stacks them along a row or column. Specs survive content-length changes, which makes them much more reliable for LLM-generated layouts.
|
|
307
|
+
|
|
308
|
+
```python
|
|
309
|
+
canvas = Canvas(1280, 720).background(color="#16213E").group(
|
|
310
|
+
children=[
|
|
311
|
+
{"type": "shape", "shape": "pill", "width": 120, "height": 36, "color": "#E94560"},
|
|
312
|
+
{"type": "text", "content": "AUTO LAYOUT", "size": 96, "color": "#FFFFFF", "weight": 900},
|
|
313
|
+
{"type": "text", "content": "No coordinates were harmed", "size": 40, "color": "#A2A8D3"},
|
|
314
|
+
],
|
|
315
|
+
direction="column",
|
|
316
|
+
gap=24,
|
|
317
|
+
position=("8%", "50%"),
|
|
318
|
+
align=("left", "middle"),
|
|
319
|
+
)
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
- `direction`: `"column"` (default) or `"row"`
|
|
323
|
+
- `gap`: pixels between children; `padding`: int, `(vertical, horizontal)`, or `(top, right, bottom, left)`
|
|
324
|
+
- `item_align`: cross-axis placement per child — `"start"`, `"center"`, or `"end"`
|
|
325
|
+
- `position` + `align` anchor the whole group box, like image layers
|
|
326
|
+
- Children may be `text`, `image`, `shape`, `svg`, or nested `group` layers; they must not set their own `position`
|
|
327
|
+
|
|
328
|
+
### Theme tokens
|
|
329
|
+
|
|
330
|
+
Define brand tokens once in a JSON spec and reference them anywhere with `$theme.path`:
|
|
331
|
+
|
|
332
|
+
```json
|
|
333
|
+
{
|
|
334
|
+
"width": 1280,
|
|
335
|
+
"height": 720,
|
|
336
|
+
"theme": {
|
|
337
|
+
"colors": {"primary": "#B8FF00", "ink": "#111111"},
|
|
338
|
+
"sizes": {"title": 96}
|
|
339
|
+
},
|
|
340
|
+
"layers": [
|
|
341
|
+
{"type": "background", "color": "$theme.colors.ink"},
|
|
342
|
+
{"type": "text", "content": "Hello", "size": "$theme.sizes.title", "color": "$theme.colors.primary"}
|
|
343
|
+
]
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Whole-string references keep their native JSON type (numbers, lists); scalar tokens can also be embedded inside longer strings. Unknown tokens raise `ValidationError`. Theme tokens work alongside `$var` template substitution.
|
|
348
|
+
|
|
349
|
+
### Diagnostics
|
|
350
|
+
|
|
351
|
+
`canvas.diagnose()` checks a composition for common problems without writing a file — ideal for agent loops (render → diagnose → fix):
|
|
352
|
+
|
|
353
|
+
```python
|
|
354
|
+
for finding in canvas.diagnose():
|
|
355
|
+
print(finding.severity, finding.code, finding.message)
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Findings: `off-canvas` (layer outside the canvas), `tiny-text` (under 2.5% of canvas height), `text-overflow` (a word wider than `max_width`), and `low-contrast` (text vs the composited layers below it). The CLI equivalent is `quickthumb lint spec.json` (exit codes: 0 clean, 1 invalid spec, 2 render failure, 3 findings).
|
|
359
|
+
|
|
360
|
+
### Grain / noise effect
|
|
361
|
+
|
|
362
|
+
Add film-grain noise to background or image layers via `effects=[Grain(...)]`.
|
|
363
|
+
|
|
364
|
+
```python
|
|
365
|
+
from quickthumb import Canvas, Grain
|
|
366
|
+
|
|
367
|
+
canvas = (
|
|
368
|
+
Canvas(1280, 720)
|
|
369
|
+
.background(
|
|
370
|
+
color="#1A1A2E",
|
|
371
|
+
effects=[Grain(intensity=0.12, monochrome=True)],
|
|
372
|
+
)
|
|
373
|
+
.image(
|
|
374
|
+
path="portrait.png",
|
|
375
|
+
position=("70%", "50%"),
|
|
376
|
+
width=400,
|
|
377
|
+
height=500,
|
|
378
|
+
align=("center", "middle"),
|
|
379
|
+
effects=[Grain(intensity=0.08, monochrome=False, blend_mode="overlay", opacity=0.6)],
|
|
380
|
+
)
|
|
381
|
+
)
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
`Grain` parameters:
|
|
385
|
+
|
|
386
|
+
| Parameter | Type | Default | Description |
|
|
387
|
+
| --- | --- | --- | --- |
|
|
388
|
+
| `intensity` | `float` | required | Noise amplitude, `0.0`–`1.0` |
|
|
389
|
+
| `monochrome` | `bool` | `True` | `True` = luminance noise; `False` = per-channel color noise |
|
|
390
|
+
| `blend_mode` | `str` | `"overlay"` | `"overlay"`, `"screen"`, `"multiply"`, or `"normal"` |
|
|
391
|
+
| `opacity` | `float` | `1.0` | Overall grain strength, `0.0`–`1.0` |
|
|
392
|
+
| `seed` | `int \| None` | `None` | Optional RNG seed for deterministic output |
|
|
393
|
+
|
|
394
|
+
`Grain` is valid in `effects` on **background** and **image** layers. It is serialized with `"type": "grain"` in JSON.
|
|
395
|
+
|
|
396
|
+
### Export helpers
|
|
397
|
+
|
|
398
|
+
```python
|
|
399
|
+
png_base64 = canvas.to_base64(format="PNG")
|
|
400
|
+
jpeg_data_url = canvas.to_data_url(format="JPEG", quality=90)
|
|
401
|
+
canvas.render("output.webp", format="WEBP", quality=90)
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
### SVG and PPTX renderers
|
|
405
|
+
|
|
406
|
+
The same canvas renders to vector and document formats, detected from the file extension:
|
|
407
|
+
|
|
408
|
+
```python
|
|
409
|
+
canvas.render("card.svg") # vector SVG with native shapes, gradients, and selectable text
|
|
410
|
+
canvas.render("card.pptx") # PowerPoint slide with editable text boxes and autoshapes
|
|
411
|
+
|
|
412
|
+
svg_markup = canvas.to_svg(embed_fonts=True) # inline @font-face for portable text
|
|
413
|
+
pptx_bytes = canvas.to_pptx() # requires quickthumb[pptx]
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
Layers the target format can express (backgrounds, gradients, outlines, shapes, text — including wrapping, rich parts, letter spacing, and effects) are exported natively and stay editable; everything else (raster images, blend modes, custom layers) is embedded as pixel-exact PNG fragments rendered by the regular pipeline. See the [export docs](https://sjquant.github.io/quickthumb/exports/) for the full mapping.
|
|
417
|
+
|
|
418
|
+
## JSON-First Workflow
|
|
419
|
+
|
|
420
|
+
quickthumb can round-trip most canvases through JSON:
|
|
421
|
+
|
|
422
|
+
```python
|
|
423
|
+
from quickthumb import Canvas
|
|
424
|
+
|
|
425
|
+
config = """
|
|
426
|
+
{
|
|
427
|
+
"width": 1280,
|
|
428
|
+
"height": 720,
|
|
429
|
+
"layers": [
|
|
430
|
+
{
|
|
431
|
+
"type": "background",
|
|
432
|
+
"color": "#111827"
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
"type": "text",
|
|
436
|
+
"content": "Hello quickthumb",
|
|
437
|
+
"size": 72,
|
|
438
|
+
"color": "#FFFFFF",
|
|
439
|
+
"align": "center",
|
|
440
|
+
"position": ["50%", "50%"]
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
"type": "outline",
|
|
444
|
+
"width": 10,
|
|
445
|
+
"color": "#22C55E"
|
|
446
|
+
}
|
|
447
|
+
]
|
|
448
|
+
}
|
|
449
|
+
"""
|
|
450
|
+
|
|
451
|
+
canvas = Canvas.from_json(config)
|
|
452
|
+
canvas.render("hello.png")
|
|
453
|
+
|
|
454
|
+
serialized = canvas.to_json()
|
|
455
|
+
```
|
|
456
|
+
|
|
457
|
+
Notes:
|
|
458
|
+
|
|
459
|
+
- JSON uses top-level `width`, `height`, and `layers`
|
|
460
|
+
- Named custom layers added with `canvas.custom(fn, name="...", kwargs={...})` are JSON-serializable via the registry; unnamed custom layers are not
|
|
461
|
+
- Enum-like values such as `blend_mode`, `fit`, and `align` can be passed as strings
|
|
462
|
+
|
|
463
|
+
## AI-Friendly Workflows
|
|
464
|
+
|
|
465
|
+
quickthumb is a good target when you want an LLM to generate image specs that are deterministic and easy to validate.
|
|
466
|
+
|
|
467
|
+
Prompt pattern for Python generation:
|
|
468
|
+
|
|
469
|
+
```text
|
|
470
|
+
Generate quickthumb Python code for a 1280x720 YouTube thumbnail.
|
|
471
|
+
Use layered composition only.
|
|
472
|
+
Keep text on the left, subject image on the right, and use high-contrast typography.
|
|
473
|
+
Return runnable code that ends with canvas.render("thumbnail.png").
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
Prompt pattern for JSON generation:
|
|
477
|
+
|
|
478
|
+
```text
|
|
479
|
+
Generate a quickthumb JSON config with top-level width, height, and layers.
|
|
480
|
+
Use one background image layer, one dark overlay background layer, two text layers, and one outline layer.
|
|
481
|
+
Only use valid quickthumb layer types and effect names.
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
Recommended workflow:
|
|
485
|
+
|
|
486
|
+
1. Have the model produce quickthumb Python or JSON.
|
|
487
|
+
2. Validate or render it locally.
|
|
488
|
+
3. Adjust only the content, colors, and assets instead of rewriting layout logic from scratch.
|
|
489
|
+
|
|
490
|
+
## Environment Variables
|
|
491
|
+
|
|
492
|
+
quickthumb looks for fonts using these environment variables:
|
|
493
|
+
|
|
494
|
+
- `QUICKTHUMB_FONT_DIR`: directory that contains font files
|
|
495
|
+
- `QUICKTHUMB_DEFAULT_FONT`: default font family/name to use when `font` is omitted
|
|
496
|
+
|
|
497
|
+
Example:
|
|
498
|
+
|
|
499
|
+
```python
|
|
500
|
+
import os
|
|
501
|
+
|
|
502
|
+
os.environ["QUICKTHUMB_FONT_DIR"] = "assets/fonts"
|
|
503
|
+
os.environ["QUICKTHUMB_DEFAULT_FONT"] = "Roboto"
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
## Feature Matrix
|
|
507
|
+
|
|
508
|
+
| Area | Supported |
|
|
509
|
+
| --- | --- |
|
|
510
|
+
| Canvas sizing | Explicit width/height, `from_aspect_ratio()` |
|
|
511
|
+
| Backgrounds | Solid colors, linear gradients, radial gradients, local/remote images |
|
|
512
|
+
| Background controls | Opacity, blend modes, fit modes, blur, brightness, contrast, saturation |
|
|
513
|
+
| Text | Positioning, alignment, wrapping, letter spacing, line height, rotation, auto-scale |
|
|
514
|
+
| Rich text | Per-segment `TextPart` styling |
|
|
515
|
+
| Text fills | Gradient (`LinearGradient`, `RadialGradient`) and image (`TextFillImage`) fills; per-`TextPart` override |
|
|
516
|
+
| Text effects | Stroke, shadow, glow, background fill |
|
|
517
|
+
| Fonts | Local fonts, CSS-style weights, italic/bold flags, webfont URLs, fallback mapping |
|
|
518
|
+
| Images | Local/remote images, sizing, fit modes, alignment, opacity, rotation |
|
|
519
|
+
| Image effects | Stroke, shadow, glow, filter effects, border radius, background removal |
|
|
520
|
+
| Grain / noise | Per-layer `Grain` effect on background and image layers; monochrome or color noise |
|
|
521
|
+
| Shapes | Rectangle, ellipse, pill, triangle, star, and polygon primitives with stroke/shadow/glow support |
|
|
522
|
+
| SVG | `svg` layers rasterized via optional `quickthumb[svg]` extra |
|
|
523
|
+
| Auto layout | `group` layers: row/column stacking, gap, padding, item alignment, nesting |
|
|
524
|
+
| Theme tokens | Top-level `theme` block with `$theme.path` references in JSON specs |
|
|
525
|
+
| Diagnostics | `canvas.diagnose()` and `quickthumb lint`: off-canvas, tiny text, overflow, low contrast |
|
|
526
|
+
| Export | PNG, JPEG, WebP, file output, base64, data URLs |
|
|
527
|
+
| Document renderers | SVG (`to_svg()`), editable PPTX via optional `quickthumb[pptx]` extra |
|
|
528
|
+
| Serialization | `to_json()` / `from_json()` for built-in layer types and named custom layers |
|
|
529
|
+
|
|
530
|
+
## Real Example Scripts
|
|
531
|
+
|
|
532
|
+
See the shipped examples in [`examples/README.md`](examples/README.md):
|
|
533
|
+
|
|
534
|
+
- `examples/youtube_thumbnail_01.py`
|
|
535
|
+
- `examples/youtube_thumbnail_02.py`
|
|
536
|
+
- `examples/instagram_news_card.py`
|
|
537
|
+
- `examples/launch_announcement.py` — the 0.5 feature set (groups, theme tokens, shapes, SVG, diagnostics) in one JSON spec
|
|
538
|
+
|
|
539
|
+
## Gotchas
|
|
540
|
+
|
|
541
|
+
- `weight` and `bold=True` are mutually exclusive on text layers and `TextPart`
|
|
542
|
+
- `auto_scale=True` requires `max_width`
|
|
543
|
+
- `position` percentage values must be strings like `"50%"`
|
|
544
|
+
- `fill` and `color` are independent fields; when `fill` is set it takes visual precedence over `color`
|
|
545
|
+
- `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
|
|
546
|
+
- `Grain` is valid only on background and image layer `effects`; it is not a valid text or shape effect
|
|
547
|
+
- `Grain(intensity=0.0)` is a no-op (no noise is generated or composited)
|
|
548
|
+
- Group children must not set `position`; the group assigns positions (their `align` is also ignored — use `item_align`)
|
|
549
|
+
- `svg` layers raise `RenderingError` unless `quickthumb[svg]` (cairosvg) is installed
|
|
550
|
+
- `theme` blocks are resolved at parse time; `to_json()` emits resolved values without the `theme` block
|
|
551
|
+
|
|
552
|
+
## Development
|
|
553
|
+
|
|
554
|
+
```bash
|
|
555
|
+
uv sync
|
|
556
|
+
uv run pytest
|
|
557
|
+
uv run ruff check .
|
|
558
|
+
uv run ty quickthumb/
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
## Reference
|
|
562
|
+
|
|
563
|
+
- License: [LICENSE](LICENSE)
|