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}