Skill: zweien/image-gen-skill (version 0.1.0)

--- name: image-gen description: | Nano-banana 图像生成与编辑 Skill。支持文生图和图生图，根据文字描述或参考图使用 AI 生成/编辑图像，自动压缩大图，保存到 workspace/img 目录，并直接在飞书中展示。 触发场景：用户要求生成图片、画图、AI 绘图、创建图像、修改图片、基于图片生成、图生图。 --- # Nano-banana 图像生成与编辑 Skill 使用 Nano-banana-3.1-Flash API 进行图像生成与编辑，支持： - **文生图 (text-to-image)**: 根据文字描述生成图像 - **图生图 (image-to-image)**: 基于参考图生成新图像 自动压缩超过 1MB 的图片，保存到 workspace，并直接在飞书中展示。 ## 配置 在workspace目录下创建 `.env` 文件： ```bash # .env IMAGE_API_BASE_URL=https://api.imyaigc.top IMAGE_API_KEY=your-api-key-here IMAGE_MODEL=gemini-3.1-flash-image-preview IMAGE_SIZE=2K IMAGE_ASPECT_RATIO=1:1 ``` ## 图片保存位置 所有生成的图片保存在： ``` ~/.openclaw/workspace/img/ ``` 文件名格式：`image_YYYYMMDD_HHMMSS.png` **压缩规则：** - 原始图片保存为：`image_YYYYMMDD_HHMMSS_original.png` - 如果原图 > 1MB，自动压缩并保存为：`image_YYYYMMDD_HHMMSS.png` - 发送到时使用压缩后的版本 ## 使用方法 ### 1. 文生图 (Text-to-Image) 直接告诉我要生成什么图片： 示例： - "生成一张夕阳下的海滩图片" - "画一只可爱的猫咪" - "创建一个赛博朋克风格的大龙虾，16:9 比例" ### 2. 图生图 (Image-to-Image) 提供参考图和修改描述： 示例： - "基于这张图，改成水彩风格" - "把这张图改成夜景" - "参考这张图，生成一个类似风格的建筑" - "把这张照片变成油画风格" **工作流程：** 1. 用户提供参考图（发送图片） 2. 下载图片并转为 base64 3. 调用 API 时传入 `image` 参数 4. 返回基于参考图生成的新图像 ## 工作流程 ### 文生图流程 1. **解析参数** - 提取提示词 - 识别宽高比参数（如 "16:9"、"1:1"） 2. **调用 API** ```bash curl -X POST "${IMAGE_API_BASE_URL}/v1/images/generations" \ -H "Authorization: Bearer ${IMAGE_API_KEY}" \ -H "Content-Type: application/json" \ -d '{ "model": "'"${IMAGE_MODEL}"'", "prompt": "描述内容", "response_format": "url", "aspect_ratio": "16:9" }' ``` ### 图生图流程 1. **获取参考图** - 用户发送图片 - 下载图片并转为 base64 2. **调用 API** ```bash curl -X POST "${IMAGE_API_BASE_URL}/v1/images/generations" \ -H "Authorization: Bearer ${IMAGE_API_KEY}" \ -H "Content-Type: application/json" \ -d '{ "model": "'"${IMAGE_MODEL}"'", "prompt": "修改描述（如：改为水彩风格）", "response_format": "url", "aspect_ratio": "16:9", "image": ["data:image/png;base64,iVBORw0KGgo..."] }' ``` 3. **获取图片 URL** - 从响应中提取 `data[0].url` 4. **下载并保存原始图片** ```bash OUTPUT_DIR=~/.openclaw/workspace/img TIMESTAMP=$(date +%Y%m%d_%H%M%S) ORIGINAL_FILE="${OUTPUT_DIR}/image_${TIMESTAMP}_original.png" FINAL_FILE="${OUTPUT_DIR}/image_${TIMESTAMP}.png" curl -o "$ORIGINAL_FILE" "$IMAGE_URL" ``` 5. **检查大小并压缩（如需要）** ```bash FILE_SIZE=$(stat -f%z "$ORIGINAL_FILE" 2>/dev/null || stat -c%s "$ORIGINAL_FILE") MAX_SIZE=$((1024 * 1024)) # 1MB if [ "$FILE_SIZE" -gt "$MAX_SIZE" ]; then echo "图片超过 1MB，正在压缩..." python3 compress_image.py "$ORIGINAL_FILE" "$FINAL_FILE" 1 else cp "$ORIGINAL_FILE" "$FINAL_FILE" fi ``` 6. **发送到飞书** 如果在飞书对话中，注意以下发送方式 ``` message( action="send", channel="feishu", accountId="second", target="用户或群组ID", media="~/.openclaw/workspace/img/image_xxx.png" ) ``` ## 压缩工具 使用 `compress_image.py` 进行图片压缩： ```bash # 用法 python3 compress_image.py <input> <output> [max_size_mb] # 示例 python3 compress_image.py original.png compressed.png 1 ``` **压缩策略：** 1. 如果图片 ≤ 1MB，不压缩 2. 如果图片 > 1MB： - 逐步降低 JPEG 质量 (85 → 10) - 如果仍超过限制，缩小尺寸 (90% → 30%) 3. 保留原始文件，压缩结果另存 ## 支持的参数 ### 宽高比 (aspect_ratio) - `1:1` - 正方形（默认） - `4:3`, `3:4` - 标准比例 - `16:9`, `9:16` - 宽屏/竖屏 - `2:3`, `3:2` - 照片比例 - `4:5`, `5:4` - 接近正方形 - `21:9` - 超宽屏 - `1:4`, `4:1` - 长条形 - `8:1`, `1:8` - 超长条形 ### 图像尺寸 (image_size) - `1K` - 适合快速预览 - `2K` - 推荐默认（默认） - `4K` - 高清大图 - `512px` - 小图 ### 参考图 (image) - 类型：字符串数组 - 格式：URL 或 base64 编码（推荐 `data:image/png;base64,...` 格式） - 支持：单张或多张参考图 ## API 信息 - **Endpoint:** `POST /v1/images/generations` - **认证:** `Authorization: Bearer {{YOUR_API_KEY}}` - **模型:** `${IMAGE_MODEL}` (默认: `gemini-3.1-flash-image-preview`) - **Base URL:** `https://api.imyaigc.top` ### 完整请求参数 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | model | string | ✅ | 模型名称 | | prompt | string | ✅ | 生成描述 | | response_format | string | ❌ | `url` 或 `b64_json` | | aspect_ratio | string | ❌ | 宽高比 | | image | array | ❌ | 参考图数组（URL 或 base64） | | image_size | string | ❌ | 图像尺寸（仅 nano-banana-2 支持） | ## 目录结构 ``` ~/.openclaw/workspace/ ├── img/ # 生成的图片保存目录 │ ├── image_20260302_221900_original.png # 原始图片 │ ├── image_20260302_221900.png # 压缩后（用于发送） │ └── ... └── skills/ └── image-gen/ ├── SKILL.md # 本说明文件 ├── image-gen.sh # 生成脚本 ├── compress_image.py # 压缩工具 ├── .env # API 配置 └── .env.example # 配置示例 ``` ## 注意事项 1. 图片**必须**保存在 `~/.openclaw/workspace/img/` 目录 2. 超过 1MB 的图片会自动压缩后再发送到飞书 3. 原始图片保留，方便后续使用 4. 文件名使用时间戳避免冲突 5. 提示词会自动翻译为英文以获得更好的生成效果 6. API 调用可能需要 30-60 秒，请告知用户等待 7. **图生图时**：参考图会转为 base64 传入 API 的 `image` 参数

path: README.md
content: # Nano-banana 图像生成与编辑 Skill

OpenClaw Agent Skill，使用 Nano-banana API 进行 AI 图像生成与编辑。

## ✨ 功能特性

- **文生图 (Text-to-Image)**: 根据文字描述生成图像
- **图生图 (Image-to-Image)**: 基于参考图生成新图像
- **自动压缩**: 超过 1MB 的图片自动压缩
- **飞书集成**: 生成的图片可直接在飞书中展示

## 📦 安装

### 方法一：使用 ClawHub（推荐）

```bash
clawhub install image-gen
```

### 方法二：手动安装

```bash
cd ~/.openclaw/workspace/skills
git clone https://github.com/zweien/image-gen-skill.git image-gen
```

## ⚙️ 配置

在 OpenClaw workspace 目录下创建 `.env` 文件：

```bash
# ~/.openclaw/workspace/.env
IMAGE_API_BASE_URL=https://api.imyaigc.top
IMAGE_API_KEY=your-api-key-here
IMAGE_MODEL=gemini-3.1-flash-image-preview
IMAGE_SIZE=2K
IMAGE_ASPECT_RATIO=1:1
```

## 🚀 使用方法

### 文生图示例

```
生成一张夕阳下的海滩图片
画一只可爱的猫咪
创建一个赛博朋克风格的大龙虾，16:9 比例
```

### 图生图示例

```
基于这张图，改成水彩风格
把这张图改成夜景
参考这张图，生成一个类似风格的建筑
```

## 📐 支持的参数

### 宽高比 (aspect_ratio)
- `1:1` - 正方形（默认）
- `4:3`, `3:4` - 标准比例
- `16:9`, `9:16` - 宽屏/竖屏
- `2:3`, `3:2` - 照片比例
- `4:5`, `5:4` - 接近正方形
- `21:9` - 超宽屏

### 图像尺寸 (image_size)
- `1K` - 快速预览
- `2K` - 推荐默认
- `4K` - 高清大图
- `512px` - 小图

## 📁 目录结构

```
~/.openclaw/workspace/
├── img/                          # 生成的图片
│   ├── image_YYYYMMDD_HHMMSS_original.png  # 原始图片
│   └── image_YYYYMMDD_HHMMSS.png           # 压缩后
└── skills/
    └── image-gen/
        ├── SKILL.md              # Skill 说明
        ├── image-gen.sh          # 生成脚本
        ├── compress_image.py     # 压缩工具
        └── .env.example          # 配置示例
```

## 🔧 工作流程

### 文生图
1. 解析提示词和宽高比参数
2. 调用 Nano-banana API
3. 下载图片到 `workspace/img/`
4. 自动压缩（如需要）
5. 返回图片路径

### 图生图
1. 下载用户提供的参考图
2. 转换为 base64
3. 连同修改描述调用 API
4. 下载并保存生成结果

## 📝 注意事项

- 图片保存在 `~/.openclaw/workspace/img/` 目录
- 超过 1MB 自动压缩（原始文件保留）
- API 调用可能需要 30-60 秒
- 提示词会自动翻译为英文以获得更好效果

## 📄 License

MIT License

## 🤝 贡献

欢迎提交 Issue 和 Pull Request！

## 🔗 相关链接

- [OpenClaw](https://github.com/openclaw/openclaw)
- [ClawHub](https://clawhub.com)
- [Nano-banana API](https://api.imyaigc.top)


path: compress_image.py
content: #!/usr/bin/env python3
"""
图片压缩工具
当图片大小超过指定阈值时，自动压缩图片
"""

import os
import sys
from PIL import Image

def compress_image(input_path, output_path, max_size_mb=1, quality=85):
    """
    压缩图片到指定大小以下

    Args:
        input_path: 输入图片路径
        output_path: 输出图片路径
        max_size_mb: 最大文件大小（MB）
        quality: 初始压缩质量 (1-100)

    Returns:
        tuple: (输出路径, 原始大小KB, 压缩后大小KB, 是否压缩)
    """
    max_size_bytes = max_size_mb * 1024 * 1024

    # 获取原始文件大小
    original_size = os.path.getsize(input_path)
    original_size_kb = original_size / 1024

    # 如果文件已经小于阈值，直接复制
    if original_size <= max_size_bytes:
        if input_path != output_path:
            import shutil
            shutil.copy(input_path, output_path)
        return (output_path, original_size_kb, original_size_kb, False)

    # 打开图片
    img = Image.open(input_path)

    # 转换为 RGB（如果需要）
    if img.mode in ('RGBA', 'P'):
        # 保留透明度，使用 PNG
        img = img.convert('RGBA')
        format = 'PNG'
    else:
        img = img.convert('RGB')
        format = 'JPEG'

    # 逐步降低质量直到满足大小要求
    current_quality = quality
    while current_quality >= 10:
        # 尝试保存
        img.save(output_path, format=format, quality=current_quality, optimize=True)

        # 检查大小
        compressed_size = os.path.getsize(output_path)
        compressed_size_kb = compressed_size / 1024

        if compressed_size <= max_size_bytes:
            return (output_path, original_size_kb, compressed_size_kb, True)

        # 降低质量继续尝试
        current_quality -= 5

    # 如果质量降到最低仍然太大，尝试缩小尺寸
    scale = 0.9
    while scale >= 0.3:
        new_width = int(img.width * scale)
        new_height = int(img.height * scale)
        resized = img.resize((new_width, new_height), Image.LANCZOS)

        resized.save(output_path, format=format, quality=75, optimize=True)
        compressed_size = os.path.getsize(output_path)
        compressed_size_kb = compressed_size / 1024

        if compressed_size <= max_size_bytes:
            return (output_path, original_size_kb, compressed_size_kb, True)

        scale -= 0.1

    # 最后一次保存（即使超过限制）
    img.save(output_path, format=format, quality=10, optimize=True)
    final_size = os.path.getsize(output_path)
    return (output_path, original_size_kb, final_size / 1024, True)


def main():
    if len(sys.argv) < 2:
        print("用法: python3 compress_image.py <input_image> [output_image] [max_size_mb]")
        sys.exit(1)

    input_path = sys.argv[1]
    output_path = sys.argv[2] if len(sys.argv) > 2 else input_path
    max_size_mb = float(sys.argv[3]) if len(sys.argv) > 3 else 1.0

    if not os.path.exists(input_path):
        print(f"错误: 文件不存在 - {input_path}")
        sys.exit(1)

    result = compress_image(input_path, output_path, max_size_mb)
    output, original_kb, compressed_kb, was_compressed = result

    if was_compressed:
        print(f"✅ 已压缩: {original_kb:.1f}KB → {compressed_kb:.1f}KB")
    else:
        print(f"ℹ️ 无需压缩: {original_kb:.1f}KB")

    print(f"输出: {output}")


if __name__ == "__main__":
    main()


path: image-gen.sh
content: #!/bin/bash
# Nano-banana 图像生成与编辑脚本
# 用法:
#   文生图: ./image-gen.sh "图像描述" [output.png] [aspect_ratio]
#   图生图: ./image-gen.sh "图像描述" [output.png] [aspect_ratio] [参考图路径]

set -e

# 获取脚本所在目录
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ENV_FILE="$SCRIPT_DIR/.env"

# 颜色输出
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'

log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }

# 检查 .env 文件
if [[ ! -f "$ENV_FILE" ]]; then
    log_error "未找到 .env 文件"
    echo "请在 $SCRIPT_DIR 目录下创建 .env 文件"
    echo "参考 .env.example 文件格式"
    exit 1
fi

# 加载环境变量
source "$ENV_FILE"

# 检查必要的环境变量
if [[ -z "$IMAGE_API_BASE_URL" || -z "$IMAGE_API_KEY" ]]; then
    log_error ".env 文件中缺少 IMAGE_API_BASE_URL 或 IMAGE_API_KEY"
    exit 1
fi

# 设置默认值
MODEL="${IMAGE_MODEL:-gemini-3.1-flash-image-preview}"
SIZE="${IMAGE_SIZE:-2K}"
ASPECT_RATIO="${3:-${IMAGE_ASPECT_RATIO:-1:1}}"

# 获取参数
PROMPT="$1"
OUTPUT="${2:-generated_image_$(date +%Y%m%d_%H%M%S).png}"
REFERENCE_IMAGE="$4"

if [[ -z "$PROMPT" ]]; then
    echo "用法:"
    echo "  文生图: $0 \"图像描述\" [output.png] [aspect_ratio]"
    echo "  图生图: $0 \"图像描述\" [output.png] [aspect_ratio] [参考图路径]"
    echo ""
    echo "支持的宽高比: 1:1, 4:3, 3:4, 16:9, 9:16, 2:3, 3:2, 4:5, 5:4, 21:9, 1:4, 4:1, 8:1, 1:8"
    echo "支持的尺寸: 1K, 2K, 4K, 512px"
    exit 1
fi

log_info "正在生成图像..."
echo "  模型: $MODEL"
echo "  提示词: $PROMPT"
echo "  宽高比: $ASPECT_RATIO"
echo "  尺寸: $SIZE"

# 构建 JSON 请求体
if [[ -n "$REFERENCE_IMAGE" && -f "$REFERENCE_IMAGE" ]]; then
    log_info "使用参考图: $REFERENCE_IMAGE"

    # 检测图片类型
    MIME_TYPE=$(file -b --mime-type "$REFERENCE_IMAGE")

    # 转为 base64
    IMAGE_BASE64=$(base64 -w 0 "$REFERENCE_IMAGE")
    IMAGE_DATA_URI="data:${MIME_TYPE};base64,${IMAGE_BASE64}"

    log_info "参考图已转为 base64 (大小: $(echo "$IMAGE_BASE64" | wc -c) 字符)"

    # 构建 JSON，包含 image 参数
    # 使用 jq 来安全构建 JSON
    JSON_PAYLOAD=$(jq -n \
        --arg model "$MODEL" \
        --arg prompt "$PROMPT" \
        --arg aspect_ratio "$ASPECT_RATIO" \
        --arg image "$IMAGE_DATA_URI" \
        '{
            model: $model,
            prompt: $prompt,
            response_format: "url",
            aspect_ratio: $aspect_ratio,
            image: [$image]
        }')

    echo "  模式: 图生图 (Image-to-Image)"
else
    # 文生图模式
    JSON_PAYLOAD=$(cat <<EOF
{
    "model": "$MODEL",
    "prompt": "$PROMPT",
    "response_format": "url",
    "aspect_ratio": "$ASPECT_RATIO"
}
EOF
)
    echo "  模式: 文生图 (Text-to-Image)"
fi

# 调用 API
RESPONSE=$(curl -s -X POST "${IMAGE_API_BASE_URL}/v1/images/generations" \
    -H "Authorization: Bearer ${IMAGE_API_KEY}" \
    -H "Content-Type: application/json" \
    -d "$JSON_PAYLOAD")

# 尝试解析图像 URL
# 支持多种响应格式
IMAGE_URL=$(echo "$RESPONSE" | jq -r '.data[0].url // .url // .image_url // empty' 2>/dev/null)

if [[ -z "$IMAGE_URL" || "$IMAGE_URL" == "null" ]]; then
    log_error "未能获取图像 URL"
    echo "响应: $RESPONSE"
    exit 1
fi

# 下载图像
log_info "下载图像到 $OUTPUT..."
curl -s -o "$OUTPUT" "$IMAGE_URL"

if [[ -f "$OUTPUT" ]]; then
    log_info "✅ 图像已保存到: $OUTPUT"
    log_info "文件大小: $(du -h "$OUTPUT" | cut -f1)"
else
    log_error "图像下载失败"
    exit 1
fi


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)