Java AI智能P图工具技术笔记

Java AI智能P图工具技术笔记

一、项目概述

本项目是基于Java Swing开发的AI智能P图桌面工具,集成摄像头拍照、本地图片加载、字节跳动火山方舟AI图像生成接口调用功能,实现通过文本提示词对图片进行AI滤镜处理,核心技术栈:Java Swing(GUI)、Webcam Capture(摄像头)、Java HttpClient(网络请求)、Base64(图片编码)、火山方舟AI开放接口。

二、核心模块拆分与代码解析

1. AI服务调用模块(AIService.java)

负责本地图片Base64编码、构建AI接口请求、发送HTTP请求、解析响应结果、下载生成后的图片,是核心业务模块。

java 复制代码
package aiimgpro;

import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.InputStream;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.time.Duration;
import java.util.Base64;

// 智能P图服务调用接口
public class AIService {
    // 火山方舟AI接口地址
    private static final String API_URL = "https://ark.cn-beijing.volces.com/api/v3/images/generations";
    // AI模型名称
    private static final String MODEL = "doubao-seedream-5-0-260128";
    // 接口认证密钥
    private static final String apiKey = "ark-03de5193-3179-4250-b026-3dca2e9224d0-4c349";

    /**
     * 调用AI生成图片(阻塞方法,需在后台执行)
     * @param imageFile 本地图片文件
     * @param prompt 提示词
     * @return 处理后的BufferedImage对象
     * @throws Exception 网络/解析异常
     */
    public BufferedImage generateImage(File imageFile, String prompt) throws Exception
    {
        // 1. 图片转Base64编码,适配AI接口要求
        byte[] imageBytes = Files.readAllBytes(imageFile.toPath());
        String base64Image = Base64.getEncoder().encodeToString(imageBytes);
        String mimeType = detectMimeType(imageFile.getName());
        // 拼接DataURI格式,AI接口标准入参
        String dataUri = "data:" + mimeType + ";base64," + base64Image;

        // 2. 构建JSON请求体
        String jsonBody = buildRequestJson(prompt, dataUri);

        // 3. 创建HTTP客户端,设置30秒连接超时
        HttpClient client = HttpClient.newBuilder()
                .connectTimeout(Duration.ofSeconds(30))
                .build();

        // 4. 构建POST请求,携带请求头和请求体
        HttpRequest request = HttpRequest.newBuilder()
                .uri(URI.create(API_URL))
                .header("Content-Type", "application/json")
                // Bearer认证,接口鉴权核心
                .header("Authorization", "Bearer " + apiKey)
                .POST(HttpRequest.BodyPublishers.ofString(jsonBody))
                // 120秒请求超时,适配AI生成耗时
                .timeout(Duration.ofSeconds(120))
                .build();

        // 5. 发送请求,获取字符串格式响应
        HttpResponse<String> response = client.send(request,
                HttpResponse.BodyHandlers.ofString());
        int statusCode = response.statusCode();
        String body = response.body();

        // 6. 异常处理:非200状态码抛出错误
        if (statusCode != 200) {
            String errMsg = extractJsonValue(body, "message");
            if (errMsg == null) errMsg = body;
            throw new RuntimeException("API返回错误 (HTTP " + statusCode + "): " +
                    errMsg);
        }

        // 7. 解析响应,提取生成后的图片URL
        String imageUrl = extractImageUrl(body);
        if (imageUrl == null || imageUrl.isBlank()) {
            throw new RuntimeException("未从API响应中获取到图片URL。\n响应内容: " +
                    body);
        }

        // 8. 下载生成的图片,返回流格式
        HttpRequest imgRequest = HttpRequest.newBuilder()
                .uri(URI.create(imageUrl))
                .GET()
                .timeout(Duration.ofSeconds(60))
                .build();
        HttpResponse<InputStream> imgResponse = client.send(imgRequest,
                HttpResponse.BodyHandlers.ofInputStream());

        // 9. 流转换为BufferedImage,返回给UI渲染
        BufferedImage resultImage = ImageIO.read(imgResponse.body());
        if (resultImage == null) {
            throw new RuntimeException("无法解码API返回的图片。");
        }
        return resultImage;
    }

    // ======================== JSON工具方法 ========================
    /**
     * 手动构建请求JSON(无第三方依赖,轻量化)
     */
    private String buildRequestJson(String prompt, String imageDataUri) {
        StringBuilder sb = new StringBuilder();
        sb.append("{");
        sb.append("\"model\":\"").append(jsonEscape(MODEL)).append("\",");
        sb.append("\"prompt\":\"").append(jsonEscape(prompt)).append("\",");
        sb.append("\"image\":\"").append(jsonEscape(imageDataUri)).append("\",");
        sb.append("\"size\":\"2K\","); // 图片分辨率
        sb.append("\"sequential_image_generation\":\"disabled\",");
        sb.append("\"response_format\":\"url\","); // 响应格式:URL
        sb.append("\"stream\":false,"); // 关闭流式响应
        sb.append("\"watermark\":true"); // 开启水印
        sb.append("}");
        return sb.toString();
    }

    /**
     * JSON转义:处理特殊字符,避免请求格式错误
     */
    private static String jsonEscape(String s) {
        if (s == null) return "";
        StringBuilder sb = new StringBuilder(s.length() + 16);
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            switch (c) {
                case '"': sb.append("\\\""); break; // 转义双引号
                case '\\': sb.append("\\\\"); break; // 转义反斜杠
                case '\b': sb.append("\\b"); break;
                case '\f': sb.append("\\f"); break;
                case '\n': sb.append("\\n"); break; // 转义换行
                case '\r': sb.append("\\r"); break;
                case '\t': sb.append("\\t"); break;
                default:
                    if (c < 0x20) {
                        sb.append(String.format("\\u%04x", (int) c));
                    } else {
                        sb.append(c);
                    }
            }
        }
        return sb.toString();
    }

    /**
     * 提取响应中的图片URL(手动解析,无第三方依赖)
     */
    private static String extractImageUrl(String json) {
        int dataIdx = json.indexOf("\"data\"");
        if (dataIdx < 0) return null;
        String sub = json.substring(dataIdx);
        int urlIdx = sub.indexOf("\"url\"");
        if (urlIdx < 0) return null;
        sub = sub.substring(urlIdx + 5);
        int colon = sub.indexOf(':');
        if (colon < 0) return null;
        sub = sub.substring(colon + 1).stripLeading();
        if (sub.isEmpty() || sub.charAt(0) != '"') return null;
        sub = sub.substring(1);
        int endQuote = sub.indexOf('"');
        if (endQuote < 0) return null;
        return sub.substring(0, endQuote).replace("\\/", "/");
    }

    /**
     * 通用JSON值提取方法
     */
    private static String extractJsonValue(String json, String key) {
        String search = "\"" + key + "\"";
        int idx = json.indexOf(search);
        if (idx < 0) return null;
        String sub = json.substring(idx + search.length());
        int colon = sub.indexOf(':');
        if (colon < 0) return null;
        sub = sub.substring(colon + 1).stripLeading();
        if (sub.isEmpty()) return null;
        if (sub.charAt(0) == '"') {
            sub = sub.substring(1);
            int end = sub.indexOf('"');
            if (end < 0) return null;
            return sub.substring(0, end);
        }
        int end = 0;
        while (end < sub.length() && sub.charAt(end) != ',' && sub.charAt(end) != '}')
            end++;
        return sub.substring(0, end).strip();
    }

    /**
     * 根据文件名后缀识别图片MIME类型
     */
    private static String detectMimeType(String filename) {
        String lower = filename.toLowerCase();
        if (lower.endsWith(".png")) return "image/png";
        if (lower.endsWith(".gif")) return "image/gif";
        if (lower.endsWith(".bmp")) return "image/bmp";
        if (lower.endsWith(".webp")) return "image/webp";
        return "image/jpeg"; // 默认JPEG
    }
}

2. 事件监听模块(AgentListener.java)

实现Swing按钮的事件监听,处理打开相机、拍照、打开图片、应用滤镜四大核心操作,衔接UI与AI服务。

java 复制代码
package aiimgpro;

import com.github.sarxos.webcam.Webcam;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Date;

public class AgentListener implements ActionListener {
    Graphics g; // 绘图对象,用于UI渲染图片
    JTextField promptField; // 提示词输入框
    File selectedFile; // 选中/拍摄的图片文件
    AIService aiService = new AIService(); // AI服务实例
    Webcam webcam; // 摄像头对象
    String path="D:\\相册"; // 拍照保存路径

    @Override
    public void actionPerformed(ActionEvent e) {
        String ac = e.getActionCommand();
        // 分支判断按钮类型,执行对应逻辑
        if (ac.equals("打开相机")) {
            openCam();
        } else if (ac.equals("拍照")) {
            takePhoto();
        } else if (ac.equals("打开图片")) {
            openImage();
        } else if (ac.equals("应用滤镜")) {
            applyFilter();
        }
    }

    /**
     * 打开本地图片并渲染到UI
     */
    public void openImage() {
        JFileChooser chooser = new JFileChooser();
        chooser.setDialogTitle("选择图片");
        // 文件选择器确认选择
        if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) {
            selectedFile = chooser.getSelectedFile();
            try {
                // 读取图片并绘制到左侧区域
                BufferedImage img = ImageIO.read(selectedFile);
                g.drawImage(img, 30, 100, 500, 500, null);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 调用AI服务应用滤镜,渲染结果图片
     */
    public void applyFilter() {
        String prompt = promptField.getText();
        // 非空校验
        if (prompt.isEmpty() || selectedFile == null) {
            JOptionPane.showMessageDialog(null, "请选择图片文件并输入提示词~");
        } else {
            try {
                // 核心:调用AI接口获取处理后图片
                BufferedImage img = aiService.generateImage(selectedFile, prompt);
                // 绘制到右侧区域,对比展示原图与AI图
                g.drawImage(img, 600, 100, 500, 500, null);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 打开摄像头,启动预览线程
     */
    public void openCam() {
        if (webcam != null&&webcam.isOpen()) {
            return; // 避免重复打开
        }
        if(webcam==null) {
            webcam = Webcam.getDefault(); // 获取默认摄像头
        }
        webcam.open();
        // 启动独立线程实时渲染摄像头画面
        CamThread camThread = new CamThread();
        camThread.g = g;
        camThread.webcam = webcam;
        camThread.start();
    }

    /**
     * 关闭摄像头
     */
    public void closeCam() {
        if (webcam.isOpen()) {
            webcam.close();
        }
    }

    /**
     * 拍照并保存到本地,赋值给选中文件
     */
    private void takePhoto() {
        BufferedImage image = webcam.getImage();
        g.drawImage(image, 30, 100, 300, 200, null);
        // 时间戳命名,避免文件名重复
        Date date = new Date();
        String timeStr = date.getYear()+1900+"-"+(date.getMonth()+1)+"-"+date.getDate()
                +"-"+date.getHours()+"-"+date.getMinutes()+"-"+date.getSeconds();
        File imgeFile = new File(path+"\\"+timeStr+".png");
        try {
            ImageIO.write(image, "png", imgeFile); // 保存图片
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
        selectedFile=imgeFile; // 保存文件用于AI处理
        closeCam(); // 拍照后关闭相机
    }
}

3. 摄像头预览线程模块(CamThread.java)

独立线程实时获取摄像头画面并渲染,避免阻塞UI主线程,保证界面流畅性。

java 复制代码
package aiimgpro;

import com.github.sarxos.webcam.Webcam;
import java.awt.*;
import java.awt.image.BufferedImage;

// 摄像头预览线程
public class CamThread extends Thread{
    Graphics g; // 绘图对象
    Webcam webcam; // 摄像头对象

    @Override
    public void run() {
        // 无限循环,实时渲染画面
        while (true){
            try {
                Thread.sleep(100); // 100ms刷新一次,降低性能消耗
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            // 相机开启时渲染画面,关闭则终止线程
            if(webcam.isOpen()){
                BufferedImage image = webcam.getImage();
                g.drawImage(image,30,100,300,200,null);
            }else {
                System.out.println("相机线程结束~");
                break; // 退出循环,终止线程
            }
        }
    }
}

4. UI界面模块(ImageAgentUI.java)

基于Swing搭建桌面图形界面,布局组件、注册事件监听,实现可视化操作。

java 复制代码
package aiimgpro;

import javax.swing.*;
import java.awt.*;

// UI主界面
public class ImageAgentUI extends JFrame{
    public ImageAgentUI() {
        setTitle("Image Agent"); // 窗口标题
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 关闭窗口退出程序
        setSize(1100, 600); // 窗口尺寸
        setLayout(new FlowLayout()); // 流式布局

        // 初始化功能按钮与输入框
        JButton openCamBtn = new JButton("打开相机");
        JButton takePhotoBtn = new JButton("拍照");
        JButton openBtn = new JButton("打开图片");
        JLabel promptLbl = new JLabel("滤镜提示词:");
        JTextField promptTxt = new JTextField(20);
        JButton applyBtn = new JButton("应用滤镜");

        // 添加组件到窗口
        add(openCamBtn);
        add(takePhotoBtn);
        add(openBtn);
        add(promptLbl);
        add(promptTxt);
        add(applyBtn);

        setVisible(true); // 显示窗口
        Graphics g = getGraphics(); // 获取绘图对象

        // 注册事件监听器,绑定组件
        AgentListener listener = new AgentListener();
        takePhotoBtn.addActionListener(listener);
        openCamBtn.addActionListener(listener);
        openBtn.addActionListener(listener);
        applyBtn.addActionListener(listener);

        // 传递参数给监听器
        listener.promptField = promptTxt;
        listener.g = g;
    }

    // 程序入口
    public static void main(String[] args) {
        new ImageAgentUI(); // 启动UI
    }
}

三、关键技术点总结

  1. 图片Base64编码:将本地图片转为字符串格式,满足AI接口入参要求,是图片上传的核心步骤。
  2. 原生HttpClient请求:无第三方依赖,轻量级实现HTTP请求,适配AI接口的POST调用、超时控制、鉴权头配置。
  3. 手动JSON解析/构建:不依赖FastJSON/Gson,降低项目体积,通过字符串操作处理请求体和响应数据。
  4. 多线程优化:摄像头预览使用独立线程,避免UI卡顿;AI接口调用为阻塞方法,实际使用可优化为后台线程。
  5. Swing绘图机制 :通过Graphics对象直接在窗口渲染原图、摄像头画面、AI生成图,实现实时预览。
  6. 接口鉴权 :使用Bearer + API Key完成火山方舟接口认证,保证请求合法性。

四、注意事项

  1. 拍照保存路径D:\\相册需提前创建,否则会报文件不存在异常;
  2. AI接口调用为阻塞操作,建议添加加载提示,避免界面无响应;
  3. 摄像头依赖webcam-capture第三方库,需正确导入依赖;
  4. API Key为敏感信息,正式项目需加密存储,不可硬编码;
  5. 图片尺寸、生成参数可根据AI接口文档灵活调整。
相关推荐
AI袋鼠帝1 小时前
本地4B开源模型,把任何App当Skil用!告 别token焦虑,私密性强~
人工智能
谷雨不太卷1 小时前
进程的状态码
java·前端·算法
ComputerInBook1 小时前
数字图像处理(4版)——第 11 章——特征提取(下)(Rafael C.Gonzalez&Richard E. Woods)
图像处理·人工智能·特征提取
顾温1 小时前
default——C#/C++
java·c++·c#
在线打码1 小时前
ToutiaoAI:AI 驱动的智能新闻杂志平台
人工智能·ai·aigc·ai写作·新闻资讯
ar01231 小时前
AR电路巡检:让电力运维进入智能可视化时代
运维·人工智能·ar
空中海1 小时前
02 ArkTS 语言与工程规范
java·前端·spring
低调小一1 小时前
Midscene.js 原理拆解:它不是“自然语言点按钮”,而是一套会看屏幕的 UI 自动化运行时
人工智能·rnn·架构·大模型·transformer·tdd·midscene
FakeOccupational1 小时前
【电路笔记 PCB】Altium Designer : AD20信号完整性(Signal Integrity)分析+单线路传输分析+串扰分析(暂记)
笔记