chuckchen 1 mese fa
commit
22296a1dd5
16 ha cambiato i file con 1232 aggiunte e 0 eliminazioni
  1. 184 0
      .difyignore
  2. 3 0
      .env.example
  3. 109 0
      .github/workflows/plugin-publish.yml
  4. 176 0
      .gitignore
  5. 137 0
      GUIDE.md
  6. 3 0
      PRIVACY.md
  7. 130 0
      README.md
  8. 55 0
      _assets/icon-dark.svg
  9. 55 0
      _assets/icon.svg
  10. 6 0
      main.py
  11. 37 0
      manifest.yaml
  12. 53 0
      provider/image_fitz.py
  13. 62 0
      provider/image_fitz.yaml
  14. 4 0
      requirements.txt
  15. 168 0
      tools/image_fitz.py
  16. 50 0
      tools/image_fitz.yaml

+ 184 - 0
.difyignore

@@ -0,0 +1,184 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+#   For a library or package, you might want to ignore these files since the code is
+#   intended to run in multiple environments; otherwise, check them in:
+.python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+Pipfile.lock
+
+# UV
+#   Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
+#   This is especially recommended for binary packages to ensure reproducibility, and is more
+#   commonly ignored for libraries.
+uv.lock
+
+# poetry
+#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+#   This is especially recommended for binary packages to ensure reproducibility, and is more
+#   commonly ignored for libraries.
+#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+poetry.lock
+
+# pdm
+#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+#   in version control.
+#   https://pdm.fming.dev/latest/usage/project/#working-with-version-control
+.pdm.toml
+.pdm-python
+.pdm-build/
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+#  and can be added to the global gitignore or merged into this file.  For a more nuclear
+#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
+.idea/
+
+# Vscode
+.vscode/
+
+# Git
+.git/
+.gitignore
+.github/
+
+# Mac
+.DS_Store
+
+# Windows
+Thumbs.db
+
+# Dify plugin packages
+#  To prevent packaging repetitively
+*.difypkg
+

+ 3 - 0
.env.example

@@ -0,0 +1,3 @@
+INSTALL_METHOD=remote
+REMOTE_INSTALL_URL=debug.dify.ai:5003
+REMOTE_INSTALL_KEY=********-****-****-****-************

+ 109 - 0
.github/workflows/plugin-publish.yml

@@ -0,0 +1,109 @@
+name: Plugin Publish Workflow
+
+on:
+  release:
+    types: [published]
+
+jobs:
+  publish:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v3
+
+      - name: Download CLI tool
+        run: |
+          mkdir -p $RUNNER_TEMP/bin
+          cd $RUNNER_TEMP/bin
+
+          wget https://github.com/langgenius/dify-plugin-daemon/releases/download/0.0.6/dify-plugin-linux-amd64
+          chmod +x dify-plugin-linux-amd64
+
+          echo "CLI tool location:"
+          pwd
+          ls -la dify-plugin-linux-amd64
+
+      - name: Get basic info from manifest
+        id: get_basic_info
+        run: |
+          PLUGIN_NAME=$(grep "^name:" manifest.yaml | cut -d' ' -f2)
+          echo "Plugin name: $PLUGIN_NAME"
+          echo "plugin_name=$PLUGIN_NAME" >> $GITHUB_OUTPUT
+
+          VERSION=$(grep "^version:" manifest.yaml | cut -d' ' -f2)
+          echo "Plugin version: $VERSION"
+          echo "version=$VERSION" >> $GITHUB_OUTPUT
+
+          # If the author's name is not your github username, you can change the author here
+          AUTHOR=$(grep "^author:" manifest.yaml | cut -d' ' -f2)
+          echo "Plugin author: $AUTHOR"
+          echo "author=$AUTHOR" >> $GITHUB_OUTPUT
+
+      - name: Package Plugin
+        id: package
+        run: |
+          cd $GITHUB_WORKSPACE
+          PACKAGE_NAME="${{ steps.get_basic_info.outputs.plugin_name }}-${{ steps.get_basic_info.outputs.version }}.difypkg"
+          $RUNNER_TEMP/bin/dify-plugin-linux-amd64 plugin package . -o "$PACKAGE_NAME"
+
+          echo "Package result:"
+          ls -la "$PACKAGE_NAME"
+          echo "package_name=$PACKAGE_NAME" >> $GITHUB_OUTPUT
+
+          echo "\nFull file path:"
+          pwd
+          echo "\nDirectory structure:"
+          tree || ls -R
+
+      - name: Checkout target repo
+        uses: actions/checkout@v3
+        with:
+          repository: ${{steps.get_basic_info.outputs.author}}/dify-plugins
+          path: dify-plugins
+          token: ${{ secrets.PLUGIN_ACTION }}
+          fetch-depth: 1
+          persist-credentials: true
+
+      - name: Prepare and create PR
+        run: |
+          PACKAGE_NAME="${{ steps.get_basic_info.outputs.plugin_name }}-${{ steps.get_basic_info.outputs.version }}.difypkg"
+          mkdir -p dify-plugins/${{ steps.get_basic_info.outputs.author }}/${{ steps.get_basic_info.outputs.plugin_name }}
+          mv "$PACKAGE_NAME" dify-plugins/${{ steps.get_basic_info.outputs.author }}/${{ steps.get_basic_info.outputs.plugin_name }}/
+
+          cd dify-plugins
+
+          git config user.name "GitHub Actions"
+          git config user.email "actions@github.com"
+
+          git fetch origin main
+          git checkout main
+          git pull origin main
+
+          BRANCH_NAME="bump-${{ steps.get_basic_info.outputs.plugin_name }}-plugin-${{ steps.get_basic_info.outputs.version }}"
+          git checkout -b "$BRANCH_NAME"
+
+          git add .
+          git commit -m "bump ${{ steps.get_basic_info.outputs.plugin_name }} plugin to version ${{ steps.get_basic_info.outputs.version }}"
+
+          git push -u origin "$BRANCH_NAME" --force
+
+          git branch -a
+          echo "Waiting for branch to sync..."
+          sleep 10  # Wait 10 seconds for branch sync
+
+      - name: Create PR via GitHub API
+        env:
+          # How to config the token:
+          # 1. Profile -> Settings -> Developer settings -> Personal access tokens -> Generate new token (with repo scope) -> Copy the token
+          # 2. Go to the target repository -> Settings -> Secrets and variables -> Actions -> New repository secret -> Add the token as PLUGIN_ACTION
+          GH_TOKEN: ${{ secrets.PLUGIN_ACTION }}
+        run: |
+          gh pr create \
+            --repo langgenius/dify-plugins \
+            --head "${{ steps.get_basic_info.outputs.author }}:${{ steps.get_basic_info.outputs.plugin_name }}-${{ steps.get_basic_info.outputs.version }}" \
+            --base main \
+            --title "bump ${{ steps.get_basic_info.outputs.plugin_name }} plugin to version ${{ steps.get_basic_info.outputs.version }}" \
+            --body "bump ${{ steps.get_basic_info.outputs.plugin_name }} plugin package to version ${{ steps.get_basic_info.outputs.version }}
+
+            Changes:
+            - Updated plugin package file" || echo "PR already exists or creation skipped." # Handle cases where PR already exists

+ 176 - 0
.gitignore

@@ -0,0 +1,176 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+#   For a library or package, you might want to ignore these files since the code is
+#   intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+#   However, in case of collaboration, if having platform-specific dependencies or dependencies
+#   having no cross-platform support, pipenv may install dependencies that don't work, or not
+#   install all needed dependencies.
+#Pipfile.lock
+
+# UV
+#   Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
+#   This is especially recommended for binary packages to ensure reproducibility, and is more
+#   commonly ignored for libraries.
+#uv.lock
+
+# poetry
+#   Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+#   This is especially recommended for binary packages to ensure reproducibility, and is more
+#   commonly ignored for libraries.
+#   https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+#   Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+#   pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+#   in version control.
+#   https://pdm.fming.dev/latest/usage/project/#working-with-version-control
+.pdm.toml
+.pdm-python
+.pdm-build/
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+#  JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+#  be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+#  and can be added to the global gitignore or merged into this file.  For a more nuclear
+#  option (not recommended) you can uncomment the following to ignore the entire idea folder.
+.idea/
+
+# Vscode
+.vscode/
+
+# macOS
+.DS_Store
+.AppleDouble
+.LSOverride

+ 137 - 0
GUIDE.md

@@ -0,0 +1,137 @@
+# Dify Plugin Development Guide
+
+Welcome to Dify plugin development! This guide will help you get started quickly.
+
+## Plugin Types
+
+Dify plugins extend three main capabilities:
+
+| Type | Description | Example |
+|------|-------------|---------|
+| **Tool** | Perform specific tasks | Google Search, Stable Diffusion |
+| **Model** | AI model integrations | OpenAI, Anthropic |
+| **Endpoint** | HTTP services | Custom APIs, integrations |
+
+You can create:
+- **Tool**: Tool provider with optional endpoints (e.g., Discord bot)
+- **Model**: Model provider only
+- **Extension**: Simple HTTP service
+
+## Setup
+
+### Requirements
+- Python 3.11+
+- Dependencies: `pip install -r requirements.txt`
+
+## Development Process
+
+<details>
+<summary><b>1. Manifest Structure</b></summary>
+
+Edit `manifest.yaml` to describe your plugin:
+
+```yaml
+version: 0.1.0                  # Required: Plugin version
+type: plugin                    # Required: plugin or bundle
+author: YourOrganization        # Required: Organization name
+label:                          # Required: Multi-language names
+  en_US: Plugin Name
+  zh_Hans: 插件名称
+created_at: 2023-01-01T00:00:00Z # Required: Creation time (RFC3339)
+icon: assets/icon.png           # Required: Icon path
+
+# Resources and permissions
+resource:
+  memory: 268435456            # Max memory (bytes)
+  permission:
+    tool:
+      enabled: true            # Tool permission
+    model:
+      enabled: true            # Model permission
+      llm: true
+      text_embedding: false
+      # Other model types...
+    # Other permissions...
+
+# Extensions definition
+plugins:
+  tools:
+    - tools/my_tool.yaml       # Tool definition files
+  models:
+    - models/my_model.yaml     # Model definition files
+  endpoints:
+    - endpoints/my_api.yaml    # Endpoint definition files
+
+# Runtime metadata
+meta:
+  version: 0.0.1               # Manifest format version
+  arch:
+    - amd64
+    - arm64
+  runner:
+    language: python
+    version: "3.12"
+    entrypoint: main
+```
+
+**Restrictions:**
+- Cannot extend both tools and models
+- Must have at least one extension
+- Cannot extend both models and endpoints
+- Limited to one supplier per extension type
+</details>
+
+<details>
+<summary><b>2. Implementation Examples</b></summary>
+
+Study these examples to understand plugin implementation:
+
+- [OpenAI](https://github.com/langgenius/dify-plugin-sdks/tree/main/python/examples/openai) - Model provider
+- [Google Search](https://github.com/langgenius/dify-plugin-sdks/tree/main/python/examples/google) - Tool provider
+- [Neko](https://github.com/langgenius/dify-plugin-sdks/tree/main/python/examples/neko) - Endpoint group
+</details>
+
+<details>
+<summary><b>3. Testing & Debugging</b></summary>
+
+1. Copy `.env.example` to `.env` and configure:
+   ```
+   INSTALL_METHOD=remote
+   REMOTE_INSTALL_URL=debug.dify.ai:5003
+   REMOTE_INSTALL_KEY=your-debug-key
+   ```
+
+2. Run your plugin: 
+   ```bash
+   python -m main
+   ```
+
+3. Refresh your Dify instance to see the plugin (marked as "debugging")
+</details>
+
+<details>
+<summary><b>4. Publishing</b></summary>
+
+#### Manual Packaging
+```bash
+dify-plugin plugin package ./YOUR_PLUGIN_DIR
+```
+
+#### Automated GitHub Workflow
+
+Configure GitHub Actions to automate PR creation:
+
+1. Create a Personal Access Token for your forked repository
+2. Add it as `PLUGIN_ACTION` secret in your source repo
+3. Create `.github/workflows/plugin-publish.yml`
+
+When you create a release, the action will:
+- Package your plugin
+- Create a PR to your fork
+
+[Detailed workflow documentation](https://docs.dify.ai/plugins/publish-plugins/plugin-auto-publish-pr)
+</details>
+
+## Privacy Policy
+
+If publishing to the Marketplace, provide a privacy policy in [PRIVACY.md](PRIVACY.md).

+ 3 - 0
PRIVACY.md

@@ -0,0 +1,3 @@
+## Privacy
+
+!!! Please fill in the privacy policy of the plugin.

+ 130 - 0
README.md

@@ -0,0 +1,130 @@
+# Image Fitz - 高质量图像预处理插件
+
+**Author:** chenlong028  
+**Version:** 0.0.1  
+**Type:** Dify Plugin Tool  
+
+## 📖 项目描述
+
+Image Fitz 是一个基于 PyMuPDF(fitz) 的 Dify 插件,专门用于图像预处理和高质量渲染。该插件能够将输入的图片按指定 DPI 重新渲染,生成高质量的图像输出,特别适用于需要提升图像质量或统一图像规格的场景。
+
+## ✨ 功能特性
+
+- 🖼️ **多种输入格式支持**:支持本地文件路径、HTTP(S) URL、Base64 数据 URI 和 PIL.Image 对象
+- 🎯 **自定义 DPI 渲染**:可指定目标 DPI 进行高质量图像渲染(默认 200 DPI)
+- 🚀 **智能优化**:自动处理超大图像,避免内存溢出问题
+- 🔧 **易于集成**:作为 Dify 插件,可直接在工作流中使用
+- 📦 **轻量级**:基于成熟的 PyMuPDF 库,性能稳定可靠
+
+## 🛠️ 安装说明
+
+### 依赖要求
+
+- Python 3.11+
+- PyMuPDF >= 1.23.0
+- Pillow >= 10.0.0
+- requests >= 2.31.0
+- dify_plugin >= 0.4.0
+
+### 安装步骤
+
+1. 克隆项目到本地:
+```bash
+git clone <repository-url>
+cd image_fitz
+```
+
+2. 安装依赖:
+```bash
+pip install -r requirements.txt
+```
+
+3. 在 Dify 中安装插件:
+   - 将插件目录复制到 Dify 插件目录
+   - 或通过 Dify 插件市场安装
+
+## 🚀 使用示例
+
+### 在 Dify 工作流中使用
+
+1. 在工作流中添加 "Image Fitz Processor" 工具
+2. 配置参数:
+   - **image_input**: 图像输入(URL、文件路径或 Base64)
+   - **target_dpi**: 目标 DPI(可选,默认 200)
+
+### API 调用示例
+
+```python
+from tools.image_fitz import render_image_at_dpi
+
+# 处理本地图片
+processed_image = render_image_at_dpi("path/to/image.jpg", target_dpi=300)
+
+# 处理网络图片
+processed_image = render_image_at_dpi("https://example.com/image.png", target_dpi=200)
+
+# 处理 PIL Image 对象
+from PIL import Image
+img = Image.open("image.jpg")
+processed_image = render_image_at_dpi(img, target_dpi=150)
+```
+
+## 📋 参数说明
+
+| 参数名 | 类型 | 必填 | 默认值 | 说明 |
+|--------|------|------|--------|------|
+| image_input | string | 是 | - | 图像输入,支持 URL、文件路径、Base64 数据 URI |
+| target_dpi | number | 否 | 200 | 目标渲染 DPI,影响输出图像质量 |
+
+## 🔧 核心功能
+
+### 主要函数
+
+- `render_image_at_dpi()`: 主要 API,按指定 DPI 渲染图像
+- `get_image_by_fitz_doc()`: 兼容函数,功能同上
+- `fitz_doc_to_image()`: 将 fitz.Page 渲染为 PIL.Image
+
+### 支持的输入格式
+
+- **本地文件路径**: `/path/to/image.jpg`
+- **HTTP(S) URL**: `https://example.com/image.png`
+- **Base64 数据 URI**: `data:image/png;base64,iVBORw0KGgo...`
+- **PIL.Image 对象**: 直接传入 PIL Image 实例
+
+## 🎯 使用场景
+
+- 📸 **图像质量提升**: 将低分辨率图像渲染为高质量版本
+- 📏 **规格统一**: 将不同来源的图像统一为相同 DPI
+- 🔄 **格式转换**: 在保持质量的同时进行格式转换
+- 🖨️ **打印准备**: 为打印输出准备高 DPI 图像
+- 🤖 **AI 预处理**: 为 AI 模型提供标准化的图像输入
+
+## ⚠️ 注意事项
+
+- 超大图像(宽度或高度 > 4500px)会自动回退到默认 DPI 以避免内存问题
+- 网络图片下载可能受网络环境影响,建议添加适当的错误处理
+- 高 DPI 设置会增加处理时间和内存使用
+
+## 📄 许可证
+
+本项目采用 MIT 许可证,详见 LICENSE 文件。
+
+## 🤝 贡献指南
+
+欢迎提交 Issue 和 Pull Request 来改进这个项目!
+
+1. Fork 本项目
+2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
+3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
+4. 推送到分支 (`git push origin feature/AmazingFeature`)
+5. 开启 Pull Request
+
+## 📞 联系方式
+
+如有问题或建议,请通过以下方式联系:
+
+- 作者: chenlong028
+- 项目地址: [GitHub Repository]
+
+
+

+ 55 - 0
_assets/icon-dark.svg

@@ -0,0 +1,55 @@
+<!--
+  ~ Dify Marketplace Template Icon
+  ~ Dify 市场模板图标
+  ~ Dify マーケットプレイステンプレートアイコン
+  ~
+  ~ WARNING / 警告 / 警告:
+  ~ 
+  ~ English: This is a TEMPLATE icon from Dify Marketplace only. You MUST NOT use this default icon in any way.
+  ~ Please replace it with your own custom icon before submit this plugin.
+  ~ 
+  ~ 中文: 这只是来自 Dify 市场的模板图标。您绝对不能以任何方式使用此默认图标。
+  ~ 请在提交此插件之前将其替换为您自己的自定义图标。
+  ~ 
+  ~ 日本語: これは Dify マーケットプレイスのテンプレートアイコンです。このデフォルトアイコンをいかなる方法でも使用してはいけません。
+  ~ このプラグインを提出する前に、独自のカスタムアイコンに置き換えてください。
+  ~ 
+  ~ DIFY_MARKETPLACE_TEMPLATE_ICON_DO_NOT_USE
+  -->
+<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_15253_95095)">
+<rect width="40" height="40" fill="#0033FF"/>
+<g filter="url(#filter0_n_15253_95095)">
+<rect width="40" height="40" fill="url(#paint0_linear_15253_95095)"/>
+</g>
+<path d="M28 10C28.5523 10 29 10.4477 29 11V16C29 16.5523 28.5523 17 28 17H23V30C23 30.5523 22.5523 31 22 31H18C17.4477 31 17 30.5523 17 30V17H11.5C10.9477 17 10.5 16.5523 10.5 16V13.618C10.5 13.2393 10.714 12.893 11.0528 12.7236L16.5 10H28ZM23 12H16.9721L12.5 14.2361V15H19V29H21V15H23V12ZM27 12H25V15H27V12Z" fill="white"/>
+</g>
+<defs>
+<filter id="filter0_n_15253_95095" x="0" y="0" width="40" height="40" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feTurbulence type="fractalNoise" baseFrequency="2 2" stitchTiles="stitch" numOctaves="3" result="noise" seed="8033" />
+<feComponentTransfer in="noise" result="coloredNoise1">
+<feFuncR type="linear" slope="2" intercept="-0.5" />
+<feFuncG type="linear" slope="2" intercept="-0.5" />
+<feFuncB type="linear" slope="2" intercept="-0.5" />
+<feFuncA type="discrete" tableValues="1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "/>
+</feComponentTransfer>
+<feComposite operator="in" in2="shape" in="coloredNoise1" result="noise1Clipped" />
+<feComponentTransfer in="noise1Clipped" result="color1">
+<feFuncA type="table" tableValues="0 0.06" />
+</feComponentTransfer>
+<feMerge result="effect1_noise_15253_95095">
+<feMergeNode in="shape" />
+<feMergeNode in="color1" />
+</feMerge>
+</filter>
+<linearGradient id="paint0_linear_15253_95095" x1="0" y1="0" x2="40" y2="40" gradientUnits="userSpaceOnUse">
+<stop stop-color="#1443FF"/>
+<stop offset="1" stop-color="#0031F5"/>
+</linearGradient>
+<clipPath id="clip0_15253_95095">
+<rect width="40" height="40" fill="white"/>
+</clipPath>
+</defs>
+</svg>

+ 55 - 0
_assets/icon.svg

@@ -0,0 +1,55 @@
+<!--
+  ~ Dify Marketplace Template Icon
+  ~ Dify 市场模板图标
+  ~ Dify マーケットプレイステンプレートアイコン
+  ~
+  ~ WARNING / 警告 / 警告:
+  ~ 
+  ~ English: This is a TEMPLATE icon from Dify Marketplace only. You MUST NOT use this default icon in any way.
+  ~ Please replace it with your own custom icon before submit this plugin.
+  ~ 
+  ~ 中文: 这只是来自 Dify 市场的模板图标。您绝对不能以任何方式使用此默认图标。
+  ~ 请在提交此插件之前将其替换为您自己的自定义图标。
+  ~ 
+  ~ 日本語: これは Dify マーケットプレイスのテンプレートアイコンです。このデフォルトアイコンをいかなる方法でも使用してはいけません。
+  ~ このプラグインを提出する前に、独自のカスタムアイコンに置き換えてください。
+  ~ 
+  ~ DIFY_MARKETPLACE_TEMPLATE_ICON_DO_NOT_USE
+  -->
+<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g clip-path="url(#clip0_15255_46435)">
+<rect width="40" height="40" fill="#0033FF"/>
+<g filter="url(#filter0_n_15255_46435)">
+<rect width="40" height="40" fill="url(#paint0_linear_15255_46435)"/>
+</g>
+<path d="M28 10C28.5523 10 29 10.4477 29 11V16C29 16.5523 28.5523 17 28 17H23V30C23 30.5523 22.5523 31 22 31H18C17.4477 31 17 30.5523 17 30V17H11.5C10.9477 17 10.5 16.5523 10.5 16V13.618C10.5 13.2393 10.714 12.893 11.0528 12.7236L16.5 10H28ZM23 12H16.9721L12.5 14.2361V15H19V29H21V15H23V12ZM27 12H25V15H27V12Z" fill="white"/>
+</g>
+<defs>
+<filter id="filter0_n_15255_46435" x="0" y="0" width="40" height="40" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feTurbulence type="fractalNoise" baseFrequency="2 2" stitchTiles="stitch" numOctaves="3" result="noise" seed="8033" />
+<feComponentTransfer in="noise" result="coloredNoise1">
+<feFuncR type="linear" slope="2" intercept="-0.5" />
+<feFuncG type="linear" slope="2" intercept="-0.5" />
+<feFuncB type="linear" slope="2" intercept="-0.5" />
+<feFuncA type="discrete" tableValues="1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "/>
+</feComponentTransfer>
+<feComposite operator="in" in2="shape" in="coloredNoise1" result="noise1Clipped" />
+<feComponentTransfer in="noise1Clipped" result="color1">
+<feFuncA type="table" tableValues="0 0.06" />
+</feComponentTransfer>
+<feMerge result="effect1_noise_15255_46435">
+<feMergeNode in="shape" />
+<feMergeNode in="color1" />
+</feMerge>
+</filter>
+<linearGradient id="paint0_linear_15255_46435" x1="0" y1="0" x2="40" y2="40" gradientUnits="userSpaceOnUse">
+<stop stop-color="#1F4CFF"/>
+<stop offset="1" stop-color="#0033FF"/>
+</linearGradient>
+<clipPath id="clip0_15255_46435">
+<rect width="40" height="40" fill="white"/>
+</clipPath>
+</defs>
+</svg>

+ 6 - 0
main.py

@@ -0,0 +1,6 @@
+from dify_plugin import Plugin, DifyPluginEnv
+
+plugin = Plugin(DifyPluginEnv(MAX_REQUEST_TIMEOUT=120))
+
+if __name__ == '__main__':
+    plugin.run()

+ 37 - 0
manifest.yaml

@@ -0,0 +1,37 @@
+version: 0.0.1
+type: plugin
+author: chenlong028
+name: image_fitz
+label:
+  en_US: image_fitz
+  ja_JP: image_fitz
+  zh_Hans: image_fitz
+  pt_BR: image_fitz
+description:
+  en_US: 提供将输入图片(本地路径、HTTP(S) URL 或 PIL.Image)通过 PyMuPDF(fitz) 按指定 DPI 渲染为新的高质量图像的能力。
+  ja_JP: 提供将输入图片(本地路径、HTTP(S) URL 或 PIL.Image)通过 PyMuPDF(fitz) 按指定 DPI 渲染为新的高质量图像的能力。
+  zh_Hans: 提供将输入图片(本地路径、HTTP(S) URL 或 PIL.Image)通过 PyMuPDF(fitz) 按指定 DPI 渲染为新的高质量图像的能力。
+  pt_BR: 提供将输入图片(本地路径、HTTP(S) URL 或 PIL.Image)通过 PyMuPDF(fitz) 按指定 DPI 渲染为新的高质量图像的能力。
+icon: icon.svg
+icon_dark: icon-dark.svg
+resource:
+  memory: 268435456
+  permission:
+    tool:
+      enabled: true
+plugins:
+  tools:
+    - provider/image_fitz.yaml
+meta:
+  version: 0.0.1
+  arch:
+    - amd64
+    - arm64
+  runner:
+    language: python
+    version: "3.12"
+    entrypoint: main
+  minimum_dify_version: null
+created_at: 2025-10-15T15:45:56.519674+08:00
+privacy: PRIVACY.md
+verified: false

+ 53 - 0
provider/image_fitz.py

@@ -0,0 +1,53 @@
+from typing import Any
+
+from dify_plugin import ToolProvider
+from dify_plugin.errors.tool import ToolProviderCredentialValidationError
+
+
+class ImageFitzProvider(ToolProvider):
+    
+    def _validate_credentials(self, credentials: dict[str, Any]) -> None:
+        try:
+            """
+            IMPLEMENT YOUR VALIDATION HERE
+            """
+        except Exception as e:
+            raise ToolProviderCredentialValidationError(str(e))
+
+    #########################################################################################
+    # If OAuth is supported, uncomment the following functions.
+    # Warning: please make sure that the sdk version is 0.4.2 or higher.
+    #########################################################################################
+    # def _oauth_get_authorization_url(self, redirect_uri: str, system_credentials: Mapping[str, Any]) -> str:
+    #     """
+    #     Generate the authorization URL for image_fitz OAuth.
+    #     """
+    #     try:
+    #         """
+    #         IMPLEMENT YOUR AUTHORIZATION URL GENERATION HERE
+    #         """
+    #     except Exception as e:
+    #         raise ToolProviderOAuthError(str(e))
+    #     return ""
+        
+    # def _oauth_get_credentials(
+    #     self, redirect_uri: str, system_credentials: Mapping[str, Any], request: Request
+    # ) -> Mapping[str, Any]:
+    #     """
+    #     Exchange code for access_token.
+    #     """
+    #     try:
+    #         """
+    #         IMPLEMENT YOUR CREDENTIALS EXCHANGE HERE
+    #         """
+    #     except Exception as e:
+    #         raise ToolProviderOAuthError(str(e))
+    #     return dict()
+
+    # def _oauth_refresh_credentials(
+    #     self, redirect_uri: str, system_credentials: Mapping[str, Any], credentials: Mapping[str, Any]
+    # ) -> OAuthCredentials:
+    #     """
+    #     Refresh the credentials
+    #     """
+    #     return OAuthCredentials(credentials=credentials, expires_at=-1)

+ 62 - 0
provider/image_fitz.yaml

@@ -0,0 +1,62 @@
+identity:
+  author: "chenlong028"
+  name: "image_fitz"
+  label:
+    en_US: "image_fitz"
+    zh_Hans: "image_fitz"
+    pt_BR: "image_fitz"
+    ja_JP: "image_fitz"
+  description:
+    en_US: "提供将输入图片(本地路径、HTTP(S) URL 或 PIL.Image)通过 PyMuPDF(fitz) 按指定 DPI 渲染为新的高质量图像的能力。"
+    zh_Hans: "提供将输入图片(本地路径、HTTP(S) URL 或 PIL.Image)通过 PyMuPDF(fitz) 按指定 DPI 渲染为新的高质量图像的能力。"
+    pt_BR: "提供将输入图片(本地路径、HTTP(S) URL 或 PIL.Image)通过 PyMuPDF(fitz) 按指定 DPI 渲染为新的高质量图像的能力。"
+    ja_JP: "提供将输入图片(本地路径、HTTP(S) URL 或 PIL.Image)通过 PyMuPDF(fitz) 按指定 DPI 渲染为新的高质量图像的能力。"
+  icon: "icon.svg"
+
+#########################################################################################
+# If you want to support OAuth, you can uncomment the following code.
+#########################################################################################
+# oauth_schema:
+#   client_schema:
+#     - name: "client_id"
+#       type: "secret-input"
+#       required: true
+#       url: https://example.com/oauth/authorize
+#       placeholder:
+#         en_US: "Please input your Client ID"
+#         zh_Hans: "请输入你的 Client ID"
+#         pt_BR: "Insira seu Client ID"
+#       help:
+#         en_US: "Client ID is used to authenticate requests to the example.com API."
+#         zh_Hans: "Client ID 用于认证请求到 example.com API。"
+#         pt_BR: "Client ID é usado para autenticar solicitações à API do example.com."
+#       label:
+#         zh_Hans: "Client ID"
+#         en_US: "Client ID"
+#     - name: "client_secret"
+#       type: "secret-input"
+#       required: true
+#       url: https://example.com/oauth/authorize
+#       placeholder:
+#         en_US: "Please input your Client Secret"
+#         zh_Hans: "请输入你的 Client Secret"
+#         pt_BR: "Insira seu Client Secret"
+#       help:
+#         en_US: "Client Secret is used to authenticate requests to the example.com API."
+#         zh_Hans: "Client Secret 用于认证请求到 example.com API。"
+#         pt_BR: "Client Secret é usado para autenticar solicitações à API do example.com."
+#       label:
+#         zh_Hans: "Client Secret"
+#         en_US: "Client Secret"
+#   credentials_schema:
+#     - name: "access_token"
+#       type: "secret-input"
+#       label:
+#         zh_Hans: "Access Token"
+#         en_US: "Access Token"
+
+tools:
+  - tools/image_fitz.yaml
+extra:
+  python:
+    source: provider/image_fitz.py

+ 4 - 0
requirements.txt

@@ -0,0 +1,4 @@
+dify_plugin>=0.4.0,<0.7.0
+PyMuPDF>=1.23.0
+Pillow>=10.0.0
+requests>=2.31.0

+ 168 - 0
tools/image_fitz.py

@@ -0,0 +1,168 @@
+"""
+Fitz 图像预处理插件
+
+提供将输入图片(本地路径、HTTP(S) URL 或 PIL.Image)通过 PyMuPDF(fitz)
+按指定 DPI 渲染为新的高质量图像的能力。
+
+导出的主要函数:
+- render_image_at_dpi: 首选、语义清晰的 API。
+- get_image_by_fitz_doc: 与现有代码保持一致的兼容函数名。
+
+相关辅助函数也一并提炼,确保模块自洽可用:
+- fitz_doc_to_image: 将 fitz.Page 渲染为 PIL.Image。
+"""
+
+from collections.abc import Generator
+from typing import Any, Union, Optional
+from io import BytesIO
+import os
+import base64
+import requests
+import fitz  # PyMuPDF
+from PIL import Image
+
+from dify_plugin import Tool
+from dify_plugin.entities.tool import ToolInvokeMessage
+
+
+def _load_image_to_bytes(image: Union[str, Image.Image]) -> tuple[Image.Image, bytes]:
+    """
+    将输入图片统一读取为 PIL.Image 和原始字节数据。
+
+    支持:
+    - PIL.Image.Image 直接传入
+    - 本地路径(以普通字符串表示)
+    - HTTP(S) URL
+    - Base64 数据 URI (data:image/xxx;base64,...)
+
+    返回:
+    - (pil_image, image_bytes)
+    """
+    if isinstance(image, Image.Image):
+        bio = BytesIO()
+        image.save(bio, format='PNG')
+        return image, bio.getvalue()
+
+    assert isinstance(image, str), f"Unsupported image type: {type(image)}"
+
+    # 处理 base64 数据 URI
+    if image.startswith("data:image/"):
+        try:
+            # 解析 data URI: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...
+            header, data = image.split(',', 1)
+            if 'base64' in header:
+                data_bytes = base64.b64decode(data)
+                pil_img = Image.open(BytesIO(data_bytes))
+                return pil_img, data_bytes
+            else:
+                raise ValueError("Only base64 encoded data URIs are supported")
+        except Exception as e:
+            raise ValueError(f"Failed to parse base64 data URI: {e}")
+
+    # 处理 HTTP(S) URL
+    if image.startswith("http://") or image.startswith("https://"):
+        try:
+            with requests.get(image, stream=True) as response:
+                response.raise_for_status()
+                data_bytes = response.content
+                pil_img = Image.open(BytesIO(data_bytes))
+                return pil_img, data_bytes
+        except Exception as e:
+            raise ValueError(f"Failed to download image from URL: {e}")
+
+    # 默认当作本地路径处理
+    try:
+        with open(image, 'rb') as f:
+            data_bytes = f.read()
+        pil_img = Image.open(BytesIO(data_bytes))
+        return pil_img, data_bytes
+    except Exception as e:
+        raise ValueError(f"Failed to load image from file path: {e}")
+
+
+def fitz_doc_to_image(doc: fitz.Page, target_dpi: int = 200, origin_dpi: Optional[tuple] = None) -> Image.Image:
+    """
+    将 fitz.Page 按指定 DPI 渲染为 PIL.Image。
+
+    参数:
+    - doc: fitz.Page
+    - target_dpi: 目标渲染 DPI,默认 200
+    - origin_dpi: 原始 DPI 信息(当前未使用,保留参数以兼容原始签名)
+    """
+    mat = fitz.Matrix(target_dpi / 72, target_dpi / 72)
+    pm = doc.get_pixmap(matrix=mat, alpha=False)
+
+    if pm.width > 4500 or pm.height > 4500:
+        # 超大图回退到 fitz 默认 DPI,以避免内存和性能问题
+        mat = fitz.Matrix(72 / 72, 72 / 72)
+        pm = doc.get_pixmap(matrix=mat, alpha=False)
+
+    image = Image.frombytes('RGB', (pm.width, pm.height), pm.samples)
+    return image
+
+
+def render_image_at_dpi(image: Union[str, Image.Image], target_dpi: int = 200) -> Image.Image:
+    """
+    使用 fitz 将任意输入图片按指定 DPI 渲染为新的 PIL.Image。
+
+    支持输入:
+    - PIL.Image.Image
+    - 本地路径
+    - HTTP(S) URL
+
+    返回:
+    - 渲染后的 PIL.Image
+    """
+    pil_img, data_bytes = _load_image_to_bytes(image)
+    origin_dpi = pil_img.info.get('dpi', None)
+
+    # 先将图片封装为 PDF,再用 fitz 渲染
+    pdf_bytes = fitz.open(stream=data_bytes).convert_to_pdf()
+    doc = fitz.open('pdf', pdf_bytes)
+    page = doc[0]
+    image_fitz = fitz_doc_to_image(page, target_dpi=target_dpi, origin_dpi=origin_dpi)
+    return image_fitz
+
+
+def get_image_by_fitz_doc(image: Union[str, Image.Image], target_dpi: int = 200) -> Image.Image:
+    """
+    兼容函数名:行为等同于 render_image_at_dpi。
+    """
+    return render_image_at_dpi(image, target_dpi=target_dpi)
+
+
+class ImageFitzTool(Tool):
+    def _invoke(self, tool_parameters: dict[str, Any]) -> Generator[ToolInvokeMessage]:
+        """
+        调用图像预处理工具
+        """
+        try:
+            # 获取参数
+            image_input = tool_parameters.get("image_input")
+            target_dpi = tool_parameters.get("target_dpi", 200)
+            
+            if not image_input:
+                raise ValueError("image_input parameter is required")
+            
+            # 处理图像
+            processed_image = render_image_at_dpi(image_input, target_dpi)
+            
+            # 将处理后的图像转换为字节数据
+            bio = BytesIO()
+            processed_image.save(bio, format='PNG')
+            image_bytes = bio.getvalue()
+            
+            # 创建文件元数据
+            meta = {
+                'filename': 'processed_image.png',
+                'mime_type': 'image/png',
+                'width': processed_image.size[0],
+                'height': processed_image.size[1],
+                'dpi': target_dpi
+            }
+            
+            # 返回 blob 消息(文件形式)
+            yield self.create_blob_message(blob=image_bytes, meta=meta)
+            
+        except Exception as e:
+            yield self.create_text_message(f"Error processing image: {str(e)}")

+ 50 - 0
tools/image_fitz.yaml

@@ -0,0 +1,50 @@
+identity:
+  name: "image_fitz"
+  author: "chenlong028"
+  label:
+    en_US: "Image Fitz Processor"
+    zh_Hans: "Fitz 图像预处理器"
+    pt_BR: "Processador de Imagem Fitz"
+    ja_JP: "Fitz画像プロセッサー"
+description:
+  human:
+    en_US: "Process images using PyMuPDF(fitz) to render high-quality images at specified DPI. Supports local paths, HTTP(S) URLs, and PIL.Image inputs."
+    zh_Hans: "使用 PyMuPDF(fitz) 按指定 DPI 渲染高质量图像。支持本地路径、HTTP(S) URL 和 PIL.Image 输入。"
+    pt_BR: "Processa imagens usando PyMuPDF(fitz) para renderizar imagens de alta qualidade em DPI especificado. Suporta caminhos locais, URLs HTTP(S) e entradas PIL.Image."
+    ja_JP: "PyMuPDF(fitz)を使用して指定されたDPIで高品質な画像をレンダリングします。ローカルパス、HTTP(S) URL、PIL.Image入力をサポートします。"
+  llm: "Process images using PyMuPDF(fitz) to render high-quality images at specified DPI. Supports local paths, HTTP(S) URLs, and PIL.Image inputs."
+parameters:
+  - name: image_input
+    type: string
+    required: true
+    label:
+      en_US: Image Input
+      zh_Hans: 图像输入
+      pt_BR: Entrada de Imagem
+      ja_JP: 画像入力
+    human_description:
+      en_US: "Image input (URL, base64 data URI, or file path)"
+      zh_Hans: "图像输入(URL、base64 数据 URI 或文件路径)"
+      pt_BR: "Entrada de imagem (URL, URI de dados base64 ou caminho do arquivo)"
+      ja_JP: "画像入力(URL、base64データURI、またはファイルパス)"
+    llm_description: "Image input as URL, base64 data URI, or file path"
+    form: llm
+  - name: target_dpi
+    type: number
+    required: false
+    default: 200
+    label:
+      en_US: Target DPI
+      zh_Hans: 目标 DPI
+      pt_BR: DPI Alvo
+      ja_JP: ターゲットDPI
+    human_description:
+      en_US: "Target DPI for image rendering (default: 200)"
+      zh_Hans: "图像渲染的目标 DPI(默认:200)"
+      pt_BR: "DPI alvo para renderização de imagem (padrão: 200)"
+      ja_JP: "画像レンダリングのターゲットDPI(デフォルト:200)"
+    llm_description: "Target DPI for image rendering, default is 200"
+    form: llm
+extra:
+  python:
+    source: tools/image_fitz.py