001/*
002 *  Copyright (c) 2023-2026, Agents-Flex (fuhai999@gmail.com).
003 *  <p>
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *  <p>
008 *  http://www.apache.org/licenses/LICENSE-2.0
009 *  <p>
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package com.agentsflex.core.util;
017
018import com.agentsflex.core.model.client.HttpClient;
019
020import java.io.File;
021import java.net.URLConnection;
022import java.util.Base64;
023import java.util.Locale;
024import java.util.Map;
025import java.util.Objects;
026import java.util.concurrent.ConcurrentHashMap;
027
028public class ImageUtil {
029
030    private static final HttpClient imageHttpClient = new HttpClient();
031
032    // 手动维护的扩展名 -> MIME 类型映射(覆盖 JDK 未识别的格式)
033    private static final Map<String, String> EXTENSION_TO_MIME = new ConcurrentHashMap<>();
034
035    static {
036        // 常见图片格式(包括现代格式)
037        EXTENSION_TO_MIME.put("jpg", "image/jpeg");
038        EXTENSION_TO_MIME.put("jpeg", "image/jpeg");
039        EXTENSION_TO_MIME.put("png", "image/png");
040        EXTENSION_TO_MIME.put("gif", "image/gif");
041        EXTENSION_TO_MIME.put("bmp", "image/bmp");
042        EXTENSION_TO_MIME.put("svg", "image/svg+xml");
043        EXTENSION_TO_MIME.put("webp", "image/webp");
044        EXTENSION_TO_MIME.put("avif", "image/avif");
045        EXTENSION_TO_MIME.put("jxl", "image/jxl");      // JPEG XL
046        EXTENSION_TO_MIME.put("tiff", "image/tiff");
047        EXTENSION_TO_MIME.put("tif", "image/tiff");
048        EXTENSION_TO_MIME.put("ico", "image/x-icon");
049        // 可根据项目需要继续扩展
050    }
051
052    /**
053     * 将图片 URL 转换为 Data URI 格式的字符串(例如:image/jpeg;base64,...)
054     *
055     * @throws IllegalArgumentException 如果 URL 无效或无法获取内容
056     */
057    public static String imageUrlToDataUri(String imageUrl) {
058        Objects.requireNonNull(imageUrl, "Image URL must not be null");
059        try {
060            byte[] bytes = imageHttpClient.getBytes(imageUrl);
061            String mimeType = guessMimeTypeFromName(imageUrl);
062            return imageBytesToDataUri(bytes, mimeType);
063        } catch (Exception e) {
064            throw new IllegalArgumentException("Failed to convert image URL to Data URI: " + imageUrl, e);
065        }
066    }
067
068    /**
069     * 将图片文件转换为 Data URI 格式的字符串
070     *
071     * @throws IllegalArgumentException 如果文件不存在或读取失败
072     */
073    public static String imageFileToDataUri(File imageFile) {
074        Objects.requireNonNull(imageFile, "Image file must not be null");
075        byte[] bytes = IOUtil.readBytes(imageFile);
076        String mimeType = guessMimeTypeFromName(imageFile.getName());
077        return imageBytesToDataUri(bytes, mimeType);
078    }
079
080
081    /**
082     * 根据文件名(或 URL)提取扩展名,并返回对应的 MIME 类型。
083     * 优先使用内置映射,其次尝试 JDK 的 guessContentTypeFromName,最后 fallback 到 image/jpeg。
084     */
085    private static String guessMimeTypeFromName(String name) {
086        if (name == null || name.isEmpty()) {
087            return "image/jpeg";
088        }
089
090        // 提取扩展名(最后一个 '.' 之后的部分)
091        int lastDotIndex = name.lastIndexOf('.');
092        if (lastDotIndex == -1 || lastDotIndex == name.length() - 1) {
093            // 无扩展名,尝试让 JDK 猜测(虽然大概率失败)
094            String mime = URLConnection.guessContentTypeFromName(name);
095            return mime != null ? mime : "image/jpeg";
096        }
097
098        String ext = name.substring(lastDotIndex + 1).toLowerCase(Locale.ROOT);
099        String mime = EXTENSION_TO_MIME.get(ext);
100        if (mime != null) {
101            return mime;
102        }
103
104        // JDK 可能认识一些格式(如 .png, .jpg),作为后备
105        mime = URLConnection.guessContentTypeFromName(name);
106        return mime != null ? mime : "image/jpeg";
107    }
108
109    public static String imageBytesToDataUri(byte[] data, String mimeType) {
110        String base64 = Base64.getEncoder().encodeToString(data);
111        return mimeType + ";base64," + base64;
112    }
113}