【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(空指针异常)。
相关推荐
宇木灵6 小时前
C语言基础学习-二、运算符
c语言·开发语言·学习
无心水6 小时前
【任务调度:数据库锁 + 线程池实战】3、 从 SELECT 到 UPDATE:深入理解 SKIP LOCKED 的锁机制与隔离级别
java·分布式·科技·spring·架构
xcLeigh6 小时前
IoTDB 数据导入全攻略:工具、自动加载与 Load SQL 详解
数据库·sql·工具·iotdb·数据导入·loadsql
yangSimaticTech6 小时前
沿触发的4个问题
开发语言·制造
编程小白gogogo7 小时前
苍穹外卖图片不显示解决教程
java·spring boot
舟舟亢亢7 小时前
算法总结——二叉树【hot100】(上)
java·开发语言·算法
百锦再7 小时前
Java中的char、String、StringBuilder与StringBuffer 深度详解
java·开发语言·python·struts·kafka·tomcat·maven
清漠2338 小时前
win11“网络和Internet“中无“以太网“这个选项解决记录
服务器·网络·数据库
普通网友8 小时前
多协议网络库设计
开发语言·c++·算法
努力努力再努力wz8 小时前
【Linux网络系列】:TCP 的秩序与策略:揭秘传输层如何从不可靠的网络中构建绝对可靠的通信信道
java·linux·开发语言·数据结构·c++·python·算法