做完你会得到什么
一个「照片变漫画」App:拍一张或选一张照片,点「日系动漫」「水彩插画」「像素游戏」「3D 黏土」任意一个,十几秒后得到同一个画面、不同画风的新图。

图为执行最终 .aia 里的真实积木生成的布局预览,不是真机截图;AI 的回答与照片、生成的图片都是示意。
| 难度 | 用时 | 前置 | 用到的模型 |
|---|---|---|---|
| ★★★ | 30 分钟 | 第 4 课 AI 画图 | 豆包 Seedream 生图模型 |
关键问题:本地照片没有网址
第 4 课的图生图,参考图是上一版的网址。可手机里拍的照片只是一个本地文件,GenerateImageFromUrl 收不了文件路径。
办法是把图片本身写进地址里:图片的字节转成 Base64 文本,前面加上 data:image/jpeg;base64,,就成了一个「自带内容」的图片地址,生图接口能直接认。
data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgG……(很长)
图片处理组件正好有现成的方法:
| 方法 | 作用 |
|---|---|
| 压缩(图片, 最长边, 质量) | 缩小尺寸、存成 JPEG,返回新文件路径 |
| 转Base64(图片) | 把文件内容转成 Base64 文本(不带 data: 前缀) |
一定要先压缩再转。Base64 比原文件还大三分之一,一张 4MB 的原图转出来 5MB 多的文本,上传又慢,还可能超过接口的大小限制。压到 1024 再转,只有一两百 KB。
界面组件
| 组件 | 命名 | 关键属性 / 作用 |
|---|---|---|
| 按钮 / 图像选择器 | 按钮_拍照、图像选择器_相册 | |
| 图像 | 图像_原图、图像_成品 | 原图高 200,成品高 300 |
| 按钮 | 按钮_日漫、按钮_水彩、按钮_像素、按钮_黏土 | 两行两列 |
| Doubao(拓展) | Doubao1 | 默认属性即可 |
| 照相机、图片处理 | 照相机1、图片处理1 |
API Key 卡片与第 4 课相同(标签名 doubaoKey,两课共用一个火山方舟 Key)。
分步搭
第 1 步:照片 → 压缩 → Base64 地址
global 原图地址 = ""
global 正在画 = false
global 当前风格 = ""
when 按钮_拍照.Click() {
照相机1.TakePicture()
}
when 照相机1.AfterPicture(image) {
准备原图(image)
}
when 图像选择器_相册.AfterPicking() {
准备原图(图像选择器_相册.Selection)
}
procedure 准备原图(图片) {
if 正在画 {
底部提示条1.Show("还在画,稍等一下再换照片")
} else {
// 先压缩再转 Base64:原图几 MB,转出来的文本更大,会超过接口上限
var 小图 = 图片处理1.Compress(图片, 1024, 85)
var 编码 = choose(小图 == "", "", 图片处理1.ToBase64(小图))
if 编码 == "" {
底部提示条1.Show("这张图片处理不了,换一张试试")
} else {
原图地址 = join("data:image/jpeg;base64,", 编码)
图像_原图.Picture = 小图
图像_成品.Picture = ""
标签_状态.Text = "照片选好了,点上面任意一种风格。"
}
}
}
压缩总是存成 JPEG,所以前缀固定写 image/jpeg。choose(小图 == "", "", …) 的意思是:压缩失败就不再转 Base64,直接当失败处理。
中途验证:选一张照片,原图位置显示压缩后的照片,状态行提示选风格。
第 2 步:四种风格,只换一个词
四个按钮调用同一个过程,传不同的风格名。提示词的其余部分完全一样,强调要保留什么:人物、姿势、构图、背景布局、五官特征。不说的话,模型常常只借用一下颜色,画出一张完全不同的图。
when 按钮_日漫.Click() {
变身("日系动漫")
}
when 按钮_水彩.Click() {
变身("水彩插画")
}
when 按钮_像素.Click() {
变身("像素游戏")
}
when 按钮_黏土.Click() {
变身("3D 黏土动画")
}
procedure 变身(风格) {
if 正在画 {
底部提示条1.Show("还在画上一张,稍等一下")
} else if Doubao1.APIKey == "" {
底部提示条1.Show("先在上面填好 API Key 并点保存")
} else if 原图地址 == "" {
底部提示条1.Show("先拍一张或选一张照片")
} else {
当前风格 = 风格
正在画 = true
设置按钮(false)
标签_状态.Text = join("正在画成", 风格, "风格……")
Doubao1.GenerateImageFromUrl(join("把这张照片转换成", 风格, "风格。保留原图的人物、姿势、构图和背景布局,人物五官特征保持可辨认,画面干净,不要添加任何文字。"), 原图地址)
}
}
procedure 设置按钮(可用) {
按钮_日漫.Enabled = 可用
按钮_水彩.Enabled = 可用
按钮_像素.Enabled = 可用
按钮_黏土.Enabled = 可用
按钮_拍照.Enabled = 可用
图像选择器_相册.Enabled = 可用
}
想加更多风格,复制一个按钮、改一个词:「宫崎骏动画」「赛博朋克」「中国水墨」「乐高积木」「美式漫画」……
第 3 步:显示结果
when Doubao1.GotImage(imageUrl) {
正在画 = false
设置按钮(true)
图像_成品.Picture = imageUrl
标签_状态.Text = join("变好了:", 当前风格, "风格。换一种风格再点一次。图片链接有时效,想留着可以照第 4 课的办法下载保存。")
}
when Screen1.ErrorOccurred(component, functionName, errorNumber, message) {
正在画 = false
设置按钮(true)
标签_状态.Text = 错误说明(message)
}
function 错误说明(message) {
return choose(containstext(message, "401"), "API Key 不对:检查是否抄全,或到火山方舟重新创建。", choose(containstext(message, "ModelNotOpen"), "生图模型还没开通:到火山方舟「开通管理」里开通 Seedream 后再试。", choose(containstext(message, "Sensitive"), "照片或描述被判定为不适合生成的内容,换一张试试。", choose(containstext(message, "429"), "请求太频繁了,等几秒再试。", join("出错了:", message)))))
}
中途验证:点「日系动漫」,所有按钮变灰;十几秒后成品出现,按钮恢复;再点「像素游戏」,同一张原图换了画风。
这一课的要点
- 「本地图片 → 地址」万能公式:
join("data:image/jpeg;base64,", 图片处理.转Base64(图片处理.压缩(图片, 1024, 85)))。凡是只收图片网址的接口都能这样喂本地照片——包括识图的ChatWithImageUrl。 - 先压缩再编码,Base64 只会让体积变大。
- 图生图的提示词要说「保留什么」,不只是「变成什么」。
- 和第 4 课比,这里每次都以原图为参考(不是上一张结果),所以换风格不会越变越走样。
常见问题
| 现象 | 原因与处理 |
|---|---|
| 提示「这张图片处理不了」 | 选到的不是图片,或文件已被删除。换一张 |
| 报错大意是「图片太大」「请求体过大」 | 没压缩就转了 Base64。照第 1 步先压缩到 1024 |
| 结果和原图毫无关系 | 提示词里没写「保留原图的人物、姿势、构图」;或者照片主体太小,换一张主体清楚的 |
| 人脸不像本人了 | 风格化必然会改变五官。「五官特征保持可辨认」能改善,但做不到照片级还原 |
| 状态行「被判定为不适合生成的内容」 | 照片里有审核敏感的内容(比如某些公众人物、证件),换一张 |
| 手机里看原图是横的,传过去变成竖的 | 不会:压缩会按照片的拍摄方向自动转正 |
平台说明
- 安卓、鸿蒙都能运行,鸿蒙的图片处理组件同样提供压缩和转 Base64。
- 业务检查见验证记录:Base64 地址解出来就是压缩后的文件、编码后体积、四种风格提示词只差风格名、生成期间不能换照片等都逐条执行过。
下一课
第 6 课 视频摘要:从图片升级到视频——给通义千问一个视频链接,让它写摘要、列时间线、出理解题。
扫码添加客服咨询