【AI 图片编辑|第九天】调用AI大模型实现扩图

本部分内容主要来源于鱼皮智能协图云图库部分,并在笔者个人项目学习的基础上进行扩展衍生。由于项目开发文档已经足够详细,因此这里只记录要点。

这部分内容后端较为简单。

一、基础图片编辑

需求分析

在日常的图片管理中,用户经常需要对图片进行简单处理,比如裁剪多余部分、旋转图片、放大缩小尺寸等。

因此,我们首先要引入基础图片编辑功能,帮助用户快速完成以下操作:

  • 裁剪:支持按固定比例或自由裁剪
  • 旋转:提供顺时针、逆时针旋转功能

这个功能非常适合上传证件照之类的场景。

注意,该功能不需要限制仅在空间内才能使用,公共图库也可以支持。

方案设计

图片编辑功能的实现以前端为主,编辑完成后通过调用现有的图片上传接口,将编辑后的图片保存至平台。

具体业务流程:

  1. 在图片上传页面,如果用户已上传图片,页面会展示 "编辑图片" 按钮。
  2. 用户点击 "编辑图片" 后,将打开图片编辑的弹窗组件,支持裁剪、旋转等操作。
  3. 用户确认编辑后,会调用图片上传接口,将编辑后的新图片保存至平台,同时更新图片信息。

其实还有另一种设计,在用户每次选择本地或 URL 图片时,先不调用后端的图片上传接口,而是自动弹出图片编辑弹窗组件,编辑完后再保存。但这样做就不是 "扩展功能" 而是 "修改已有功能",涉及到的代码改动会更多,感兴趣的同学可以尝试实现。

AI 图片编辑

方案设计

1.由于 AI 绘画任务计算量大且耗时长,所以选择异步调用

同步调用流程如下,好处是客户端可以直接获取到结果,调用更方便:

异步调用流程如下,客户端需要在提交任务后,不断轮询请求,来检查任务是否执行完成:

2.选择前端轮询

1)前端轮询

前端调用后端提交任务后得到任务 ID,然后通过定时器轮询请求查询任务状态接口,直到任务完成或失败。

2)后端轮询

后端通过循环或定时任务检测任务状态,接口保持阻塞,直到任务完成或失败,直接返回结果给前端。

后端开发

新建数据模型类

java 复制代码
@Data
public class CreateOutPaintingTaskRequest implements Serializable {

    
    private String model = "image-out-painting";

    
    private Input input;

    
    private Parameters parameters;

    @Data
    public static class Input {
        
        @Alias("image_url")
        private String imageUrl;
    }

    @Data
    public static class Parameters implements Serializable {
        
        private Integer angle;

        
        @Alias("output_ratio")
        private String outputRatio;

        
        @Alias("x_scale")
        @JsonProperty("xScale")
        private Float xScale;

        
        @Alias("y_scale")
        @JsonProperty("yScale")
        private Float yScale;

        
        @Alias("top_offset")
        private Integer topOffset;

        
        @Alias("bottom_offset")
        private Integer bottomOffset;

        
        @Alias("left_offset")
        private Integer leftOffset;

        
        @Alias("right_offset")
        private Integer rightOffset;

        
        @Alias("best_quality")
        private Boolean bestQuality;

        
        @Alias("limit_image_size")
        private Boolean limitImageSize;

        
        @Alias("add_watermark")
        private Boolean addWatermark = false;
    }
}

前端如果传递参数名 xScale,是无法赋值给 xScale 字段的;但是传递参数名 xscale,就可以赋值。这是因为 SpringMVC 对于第二个字母是大写的参数无法映射(和参数类别无关)

xScale字段 → Lombok 生成getXScale() → Jackson 推断属性名为XScale → 前端传xScale无法匹配,传xscale模糊匹配成功 → 出现你遇到的诡异现象。

原因与JavaBean有关,相关知识详解见这里

API 调用类,通过 Hutool 的 HTTP 请求工具类来调用阿里云百炼的 API

java 复制代码
@Slf4j
@Component
public class AliYunAiApi {
    
    @Value("${aliYunAi.apiKey}")
    private String apiKey;

    
    public static final String CREATE_OUT_PAINTING_TASK_URL = "https://dashscope.aliyuncs.com/api/v1/services/aigc/image2image/out-painting";

    
    public static final String GET_OUT_PAINTING_TASK_URL = "https://dashscope.aliyuncs.com/api/v1/tasks/%s";

    
    public CreateOutPaintingTaskResponse createOutPaintingTask(CreateOutPaintingTaskRequest createOutPaintingTaskRequest) {
        if (createOutPaintingTaskRequest == null) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "扩图参数为空");
        }
        
        HttpRequest httpRequest = HttpRequest.post(CREATE_OUT_PAINTING_TASK_URL)
                .header(Header.AUTHORIZATION, "Bearer " + apiKey)
                
                .header("X-DashScope-Async", "enable")
                .header(Header.CONTENT_TYPE, ContentType.JSON.getValue())
                .body(JSONUtil.toJsonStr(createOutPaintingTaskRequest));
        try (HttpResponse httpResponse = httpRequest.execute()) {
            if (!httpResponse.isOk()) {
                log.error("请求异常:{}", httpResponse.body());
                throw new BusinessException(ErrorCode.OPERATION_ERROR, "AI 扩图失败");
            }
            CreateOutPaintingTaskResponse response = JSONUtil.toBean(httpResponse.body(), CreateOutPaintingTaskResponse.class);
            String errorCode = response.getCode();
            if (StrUtil.isNotBlank(errorCode)) {
                String errorMessage = response.getMessage();
                log.error("AI 扩图失败,errorCode:{}, errorMessage:{}", errorCode, errorMessage);
                throw new BusinessException(ErrorCode.OPERATION_ERROR, "AI 扩图接口响应异常");
            }
            return response;
        }
    }

    
    public GetOutPaintingTaskResponse getOutPaintingTask(String taskId) {
        if (StrUtil.isBlank(taskId)) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "任务 id 不能为空");
        }
        try (HttpResponse httpResponse = HttpRequest.get(String.format(GET_OUT_PAINTING_TASK_URL, taskId))
                .header(Header.AUTHORIZATION, "Bearer " + apiKey)
                .execute()) {
            if (!httpResponse.isOk()) {
                throw new BusinessException(ErrorCode.OPERATION_ERROR, "获取任务失败");
            }
            return JSONUtil.toBean(httpResponse.body(), GetOutPaintingTaskResponse.class);
        }
    }
}

要点:

try-with-resources的核心标识是try后的**圆括号(),**括号里面声明的就是需要自动释放的资源。

java 复制代码
@Override
public CreateOutPaintingTaskResponse createPictureOutPaintingTask(CreatePictureOutPaintingTaskRequest createPictureOutPaintingTaskRequest, User loginUser) {
    
    Long pictureId = createPictureOutPaintingTaskRequest.getPictureId();
    Picture picture = Optional.ofNullable(this.getById(pictureId))
            .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND_ERROR));
    
    checkPictureAuth(loginUser, picture);
    
    CreateOutPaintingTaskRequest taskRequest = new CreateOutPaintingTaskRequest();
    CreateOutPaintingTaskRequest.Input input = new CreateOutPaintingTaskRequest.Input();
    input.setImageUrl(picture.getUrl());
    taskRequest.setInput(input);
    BeanUtil.copyProperties(createPictureOutPaintingTaskRequest, taskRequest);
    
    return aliYunAiApi.createOutPaintingTask(taskRequest);
}

要点:

Picture picture = Optional.ofNullable(this.getById(pictureId)) .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND_ERROR));

  • Optional.ofNullable(...):把查询结果(可能为null,比如图片 ID 不存在)包装成Optional对象,允许后续优雅处理null值。
  • orElseThrow(...):如果查询结果为null(图片不存在),直接抛出自定义业务异常BusinessException),错误码为NOT_FOUND_ERROR(未找到),终止后续流程,避免后续对picture对象操作出现NullPointerException(空指针异常)。
相关推荐
久违8164 小时前
SQL注入攻击核心技术深度总结
数据库·sql·oracle
2401_891450464 小时前
Python上下文管理器(with语句)的原理与实践
jvm·数据库·python
helloworldandy4 小时前
使用Python处理计算机图形学(PIL/Pillow)
jvm·数据库·python
「光与松果」4 小时前
Oracle中v$session视图用法
数据库·oracle
木辰風5 小时前
PLSQL自定义自动替换(AutoReplace)
java·数据库·sql
无限码力5 小时前
华为OD技术面真题 - 数据库MySQL - 3
数据库·mysql·华为od·八股文·华为od技术面八股文
2501_944525545 小时前
Flutter for OpenHarmony 个人理财管理App实战 - 预算详情页面
android·开发语言·前端·javascript·flutter·ecmascript
heartbeat..5 小时前
Redis 中的锁:核心实现、类型与最佳实践
java·数据库·redis·缓存·并发
Prince-Peng5 小时前
技术架构系列 - 详解Redis
数据结构·数据库·redis·分布式·缓存·中间件·架构
虾说羊5 小时前
redis中的哨兵机制
数据库·redis·缓存