修改描述
parent
36f8af6a76
commit
473998f9ce
|
|
@ -932,7 +932,7 @@ public class ArchivesQueryExe {
|
||||||
}
|
}
|
||||||
if(!ObjectUtils.isEmpty(item.getUserAvatarUrl())){
|
if(!ObjectUtils.isEmpty(item.getUserAvatarUrl())){
|
||||||
// PictureRenderData facePicture = Pictures.ofUrl(fileUrlConfig.getPrefixUrl() + item.getUserAvatarUrl(), PictureType.JPEG).size(50, 50).style("rotation:90;").create();//网络图片地址
|
// PictureRenderData facePicture = Pictures.ofUrl(fileUrlConfig.getPrefixUrl() + item.getUserAvatarUrl(), PictureType.JPEG).size(50, 50).style("rotation:90;").create();//网络图片地址
|
||||||
PictureRenderData facePicture = ImageUtil.createWithThumbnailator(fileUrlConfig.getPrefixUrl() + item.getUserAvatarUrl(), 50, 50);
|
PictureRenderData facePicture = ImageUtil.createWithThumbnailator(fileUrlConfig.getPrefixUrl() + item.getUserAvatarUrl(), 100, 100);
|
||||||
|
|
||||||
itemMap.put("facePicture", facePicture);
|
itemMap.put("facePicture", facePicture);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import javax.imageio.metadata.IIOMetadata;
|
||||||
import javax.imageio.metadata.IIOMetadataNode;
|
import javax.imageio.metadata.IIOMetadataNode;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
|
@ -27,24 +28,21 @@ public class FixedPictureUtils {
|
||||||
* @throws IOException IO异常
|
* @throws IOException IO异常
|
||||||
*/
|
*/
|
||||||
public static PictureRenderData ofNormalUrl(String imgUrl, int width, int height) throws IOException {
|
public static PictureRenderData ofNormalUrl(String imgUrl, int width, int height) throws IOException {
|
||||||
// 1. 从URL读取图片字节(纯JDK8原生)
|
|
||||||
byte[] imgBytes = readBytesFromUrl(imgUrl);
|
byte[] imgBytes = readBytesFromUrl(imgUrl);
|
||||||
// 2. 修复图片旋转(核心:移除containsTree()依赖)
|
byte[] fixedImgBytes = fixImageRotation(imgBytes);
|
||||||
byte[] fixedImgBytes = fixImageRotationForJdk8(imgBytes);
|
|
||||||
// 3. 构建PictureRenderData(使用ofBytes方法)
|
|
||||||
return Pictures.ofBytes(fixedImgBytes)
|
return Pictures.ofBytes(fixedImgBytes)
|
||||||
.size(width, height)
|
.size(width, height)
|
||||||
.create();
|
.create();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JDK8原生:从URL读取字节数组(无第三方依赖)
|
* JDK 8原生:从URL读取字节数组(无第三方依赖)
|
||||||
*/
|
*/
|
||||||
private static byte[] readBytesFromUrl(String url) throws IOException {
|
private static byte[] readBytesFromUrl(String url) throws IOException {
|
||||||
URL picUrl = new URL(url);
|
URL picUrl = new URL(url);
|
||||||
try (InputStream is = picUrl.openStream();
|
try (InputStream is = picUrl.openStream();
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||||
byte[] buffer = new byte[1024];
|
byte[] buffer = new byte[4096];
|
||||||
int len;
|
int len;
|
||||||
while ((len = is.read(buffer)) != -1) {
|
while ((len = is.read(buffer)) != -1) {
|
||||||
baos.write(buffer, 0, len);
|
baos.write(buffer, 0, len);
|
||||||
|
|
@ -54,101 +52,141 @@ public class FixedPictureUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 核心:修复图片EXIF旋转(移除containsTree()依赖)
|
* 核心:修复图片旋转(JDK 8兼容)
|
||||||
*/
|
*/
|
||||||
private static byte[] fixImageRotationForJdk8(byte[] imageBytes) throws IOException {
|
private static byte[] fixImageRotation(byte[] imageBytes) throws IOException {
|
||||||
|
// 1. 解析EXIF旋转角度
|
||||||
|
int rotation = getExifRotation(imageBytes);
|
||||||
|
|
||||||
|
// 2. 兜底检测:无EXIF时自动判断
|
||||||
|
if (rotation == 0) {
|
||||||
|
rotation = detectImageOrientation(imageBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 无需旋转则返回原字节
|
||||||
|
if (rotation == 0) {
|
||||||
|
return imageBytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 读取并旋转图片
|
||||||
|
BufferedImage original = readImage(imageBytes);
|
||||||
|
BufferedImage rotated = rotateImage(original, rotation);
|
||||||
|
|
||||||
|
// 5. 转为字节返回
|
||||||
|
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||||
|
String format = getImageFormat(imageBytes);
|
||||||
|
ImageIO.write(rotated, format, baos);
|
||||||
|
return baos.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JDK 8兼容:解析EXIF旋转角度(替换switch表达式)
|
||||||
|
*/
|
||||||
|
private static int getExifRotation(byte[] imageBytes) {
|
||||||
try (ByteArrayInputStream bais = new ByteArrayInputStream(imageBytes);
|
try (ByteArrayInputStream bais = new ByteArrayInputStream(imageBytes);
|
||||||
ImageInputStream iis = ImageIO.createImageInputStream(bais)) {
|
ImageInputStream iis = ImageIO.createImageInputStream(bais)) {
|
||||||
|
|
||||||
// 获取图片读取器
|
|
||||||
Iterator<ImageReader> readers = ImageIO.getImageReaders(iis);
|
Iterator<ImageReader> readers = ImageIO.getImageReaders(iis);
|
||||||
if (!readers.hasNext()) {
|
if (!readers.hasNext()) {
|
||||||
return imageBytes; // 无读取器,返回原字节
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageReader reader = readers.next();
|
ImageReader reader = readers.next();
|
||||||
reader.setInput(iis, true);
|
reader.setInput(iis, true);
|
||||||
|
|
||||||
// 1. 获取旋转角度(绕开containsTree())
|
|
||||||
int rotation = getExifRotationWithoutContainsTree(reader);
|
|
||||||
if (rotation == 0) {
|
|
||||||
reader.dispose();
|
|
||||||
return imageBytes; // 无需旋转,直接返回
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 读取原始图片
|
|
||||||
BufferedImage original = reader.read(0);
|
|
||||||
reader.dispose();
|
|
||||||
|
|
||||||
// 3. 旋转图片
|
|
||||||
BufferedImage rotated = rotateImageForJdk8(original, rotation);
|
|
||||||
|
|
||||||
// 4. 转为字节返回
|
|
||||||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
|
||||||
String format = getImageFormatForJdk8(imageBytes);
|
|
||||||
ImageIO.write(rotated, format, baos);
|
|
||||||
return baos.toByteArray();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 关键修正:绕开containsTree(),解析EXIF旋转角度
|
|
||||||
*/
|
|
||||||
private static int getExifRotationWithoutContainsTree(ImageReader reader) {
|
|
||||||
try {
|
|
||||||
IIOMetadata metadata = reader.getImageMetadata(0);
|
IIOMetadata metadata = reader.getImageMetadata(0);
|
||||||
String exifFormat = "javax_imageio_jpeg_image_1.0";
|
int rotation = 0;
|
||||||
|
|
||||||
// 直接尝试获取元数据树,不先判断containsTree()
|
// 尝试所有支持的EXIF格式
|
||||||
// 捕获异常即可,无需提前检查
|
String[] formats = {"javax_imageio_jpeg_image_1.0", "javax_imageio_tiff_image_1.0"};
|
||||||
Object metadataTree;
|
for (String format : formats) {
|
||||||
try {
|
try {
|
||||||
metadataTree = metadata.getAsTree(exifFormat);
|
IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree(format);
|
||||||
} catch (IllegalArgumentException e) {
|
rotation = parseOrientationFromMetadata(root);
|
||||||
// 不支持该格式,返回0度
|
if (rotation != 0) {
|
||||||
return 0;
|
break;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(metadataTree instanceof IIOMetadataNode)) {
|
reader.dispose();
|
||||||
return 0;
|
return rotation;
|
||||||
}
|
|
||||||
|
|
||||||
IIOMetadataNode root = (IIOMetadataNode) metadataTree;
|
|
||||||
IIOMetadataNode exifNode = getChildNodeForJdk8(root, "app0JFIF");
|
|
||||||
if (exifNode == null) {
|
|
||||||
exifNode = getChildNodeForJdk8(root, "exif");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exifNode == null) return 0;
|
|
||||||
|
|
||||||
// 解析Orientation参数
|
|
||||||
String orientation = exifNode.getAttribute("Orientation");
|
|
||||||
if (orientation == null || orientation.trim().isEmpty()) return 0;
|
|
||||||
|
|
||||||
int ori = Integer.parseInt(orientation);
|
|
||||||
switch (ori) {
|
|
||||||
case 3: return 180;
|
|
||||||
case 6: return 90;
|
|
||||||
case 8: return 270;
|
|
||||||
default: return 0;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// 任何异常都返回0度(比如元数据解析失败)
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JDK8适配:获取XML子节点
|
* JDK 8兼容:解析Orientation(使用switch语句)
|
||||||
*/
|
*/
|
||||||
private static IIOMetadataNode getChildNodeForJdk8(IIOMetadataNode parent, String name) {
|
private static int parseOrientationFromMetadata(IIOMetadataNode root) {
|
||||||
|
IIOMetadataNode node = findNodeRecursive(root, "Orientation");
|
||||||
|
if (node == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
String orientation = node.getAttribute("value");
|
||||||
|
if (orientation == null || orientation.isEmpty()) {
|
||||||
|
orientation = node.getTextContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orientation == null || orientation.isEmpty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ori = Integer.parseInt(orientation);
|
||||||
|
// JDK 8支持的switch语句(替换表达式)
|
||||||
|
int rotation = 0;
|
||||||
|
switch (ori) {
|
||||||
|
case 1:
|
||||||
|
rotation = 0;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
rotation = 0;
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
rotation = 180;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
rotation = 180;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
rotation = 90;
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
rotation = 90;
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
rotation = 270;
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
rotation = 270;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
rotation = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return rotation;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JDK 8兼容:递归查找节点
|
||||||
|
*/
|
||||||
|
private static IIOMetadataNode findNodeRecursive(IIOMetadataNode parent, String nodeName) {
|
||||||
|
if (parent.getNodeName().equals(nodeName)) {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < parent.getLength(); i++) {
|
for (int i = 0; i < parent.getLength(); i++) {
|
||||||
Object item = parent.item(i);
|
Object item = parent.item(i);
|
||||||
if (item instanceof IIOMetadataNode) {
|
if (item instanceof IIOMetadataNode) {
|
||||||
IIOMetadataNode node = (IIOMetadataNode) item;
|
IIOMetadataNode child = (IIOMetadataNode) item;
|
||||||
if (name.equals(node.getNodeName())) {
|
IIOMetadataNode found = findNodeRecursive(child, nodeName);
|
||||||
return node;
|
if (found != null) {
|
||||||
|
return found;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -156,43 +194,109 @@ public class FixedPictureUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JDK8适配:旋转图片(解决JDK8 Graphics2D兼容问题)
|
* JDK 8兼容:自动检测图片方向(兜底方案)
|
||||||
*/
|
*/
|
||||||
private static BufferedImage rotateImageForJdk8(BufferedImage img, int angle) {
|
private static int detectImageOrientation(byte[] imageBytes) {
|
||||||
|
try {
|
||||||
|
BufferedImage img = readImage(imageBytes);
|
||||||
|
if (img.getHeight() > img.getWidth() && (double) img.getHeight() / img.getWidth() > 1.2) {
|
||||||
|
return 90;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JDK 8兼容:读取图片
|
||||||
|
*/
|
||||||
|
private static BufferedImage readImage(byte[] imageBytes) throws IOException {
|
||||||
|
try (ByteArrayInputStream bais = new ByteArrayInputStream(imageBytes)) {
|
||||||
|
return ImageIO.read(bais);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JDK 8兼容:旋转图片(简化AffineTransform)
|
||||||
|
*/
|
||||||
|
private static BufferedImage rotateImage(BufferedImage img, int angle) {
|
||||||
int width = img.getWidth();
|
int width = img.getWidth();
|
||||||
int height = img.getHeight();
|
int height = img.getHeight();
|
||||||
|
|
||||||
// 计算旋转后的画布尺寸
|
// 计算旋转后的尺寸
|
||||||
int newWidth = (angle % 180 == 0) ? width : height;
|
int newWidth = width;
|
||||||
int newHeight = (angle % 180 == 0) ? height : width;
|
int newHeight = height;
|
||||||
|
if (angle == 90 || angle == 270) {
|
||||||
|
newWidth = height;
|
||||||
|
newHeight = width;
|
||||||
|
}
|
||||||
|
|
||||||
// JDK8兼容:创建BufferedImage(避免透明通道问题)
|
// JDK 8兼容的AffineTransform使用方式
|
||||||
|
AffineTransform transform = new AffineTransform();
|
||||||
|
transform.translate(newWidth / 2.0, newHeight / 2.0);
|
||||||
|
transform.rotate(Math.toRadians(angle));
|
||||||
|
transform.translate(-width / 2.0, -height / 2.0);
|
||||||
|
|
||||||
|
// 创建旋转后的图片(JDK 8兼容的类型)
|
||||||
BufferedImage rotated = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
|
BufferedImage rotated = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB);
|
||||||
Graphics2D g2d = rotated.createGraphics();
|
Graphics2D g2d = rotated.createGraphics();
|
||||||
|
|
||||||
// 抗锯齿,避免旋转后模糊(JDK8必须)
|
// JDK 8支持的渲染参数
|
||||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||||
|
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||||
|
|
||||||
// 旋转图片
|
// 绘制旋转后的图片
|
||||||
g2d.rotate(Math.toRadians(angle), newWidth / 2.0, newHeight / 2.0);
|
g2d.drawImage(img, transform, null);
|
||||||
g2d.drawImage(img, (newWidth - width) / 2, (newHeight - height) / 2, null);
|
|
||||||
g2d.dispose();
|
g2d.dispose();
|
||||||
|
|
||||||
return rotated;
|
return rotated;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* JDK8适配:识别图片格式(jpg/png)
|
* JDK 8兼容:识别图片格式
|
||||||
*/
|
*/
|
||||||
private static String getImageFormatForJdk8(byte[] imageBytes) {
|
private static String getImageFormat(byte[] imageBytes) {
|
||||||
if (imageBytes.length < 4) return "jpg";
|
if (imageBytes.length < 8) {
|
||||||
// JDK8字节判断,兼容所有系统
|
|
||||||
if (imageBytes[0] == (byte)0xFF && imageBytes[1] == (byte)0xD8) {
|
|
||||||
return "jpg";
|
return "jpg";
|
||||||
} else if (imageBytes[0] == (byte)0x89 && imageBytes[1] == (byte)0x50) {
|
}
|
||||||
|
|
||||||
|
// JPG
|
||||||
|
if (imageBytes[0] == (byte) 0xFF && imageBytes[1] == (byte) 0xD8) {
|
||||||
|
return "jpg";
|
||||||
|
}
|
||||||
|
// PNG
|
||||||
|
else if (imageBytes[0] == (byte) 0x89 && imageBytes[1] == (byte) 0x50
|
||||||
|
&& imageBytes[2] == (byte) 0x4E && imageBytes[3] == (byte) 0x47) {
|
||||||
return "png";
|
return "png";
|
||||||
}
|
}
|
||||||
|
// WEBP
|
||||||
|
else if (imageBytes[0] == (byte) 0x52 && imageBytes[1] == (byte) 0x49
|
||||||
|
&& imageBytes[2] == (byte) 0x46 && imageBytes[3] == (byte) 0x46) {
|
||||||
|
return "webp";
|
||||||
|
}
|
||||||
|
// BMP
|
||||||
|
else if (imageBytes[0] == (byte) 0x42 && imageBytes[1] == (byte) 0x4D) {
|
||||||
|
return "bmp";
|
||||||
|
}
|
||||||
return "jpg";
|
return "jpg";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------- 应急方法:手动指定旋转角度 --------------------
|
||||||
|
/**
|
||||||
|
* 手动指定旋转角度(JDK 8兼容)
|
||||||
|
*/
|
||||||
|
public static PictureRenderData ofNormalUrlWithRotate(String imgUrl, int width, int height, int rotateAngle) throws IOException {
|
||||||
|
byte[] imgBytes = readBytesFromUrl(imgUrl);
|
||||||
|
BufferedImage original = readImage(imgBytes);
|
||||||
|
BufferedImage rotated = rotateImage(original, rotateAngle);
|
||||||
|
|
||||||
|
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
|
||||||
|
ImageIO.write(rotated, "jpg", baos);
|
||||||
|
return Pictures.ofBytes(baos.toByteArray())
|
||||||
|
.size(width, height)
|
||||||
|
.create();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue