diff --git a/web-app/src/main/java/com/zcloud/edu/command/query/archives/ArchivesQueryExe.java b/web-app/src/main/java/com/zcloud/edu/command/query/archives/ArchivesQueryExe.java index 1899af6..65affdc 100644 --- a/web-app/src/main/java/com/zcloud/edu/command/query/archives/ArchivesQueryExe.java +++ b/web-app/src/main/java/com/zcloud/edu/command/query/archives/ArchivesQueryExe.java @@ -932,7 +932,7 @@ public class ArchivesQueryExe { } if(!ObjectUtils.isEmpty(item.getUserAvatarUrl())){ // 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); } diff --git a/web-domain/src/main/java/com/zcloud/edu/domain/utils/FixedPictureUtils.java b/web-domain/src/main/java/com/zcloud/edu/domain/utils/FixedPictureUtils.java index 7ee128a..d265f9f 100644 --- a/web-domain/src/main/java/com/zcloud/edu/domain/utils/FixedPictureUtils.java +++ b/web-domain/src/main/java/com/zcloud/edu/domain/utils/FixedPictureUtils.java @@ -9,6 +9,7 @@ import javax.imageio.metadata.IIOMetadata; import javax.imageio.metadata.IIOMetadataNode; import javax.imageio.stream.ImageInputStream; import java.awt.*; +import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -27,24 +28,21 @@ public class FixedPictureUtils { * @throws IOException IO异常 */ public static PictureRenderData ofNormalUrl(String imgUrl, int width, int height) throws IOException { - // 1. 从URL读取图片字节(纯JDK8原生) byte[] imgBytes = readBytesFromUrl(imgUrl); - // 2. 修复图片旋转(核心:移除containsTree()依赖) - byte[] fixedImgBytes = fixImageRotationForJdk8(imgBytes); - // 3. 构建PictureRenderData(使用ofBytes方法) + byte[] fixedImgBytes = fixImageRotation(imgBytes); return Pictures.ofBytes(fixedImgBytes) .size(width, height) .create(); } /** - * JDK8原生:从URL读取字节数组(无第三方依赖) + * JDK 8原生:从URL读取字节数组(无第三方依赖) */ private static byte[] readBytesFromUrl(String url) throws IOException { URL picUrl = new URL(url); try (InputStream is = picUrl.openStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream()) { - byte[] buffer = new byte[1024]; + byte[] buffer = new byte[4096]; int len; while ((len = is.read(buffer)) != -1) { 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); ImageInputStream iis = ImageIO.createImageInputStream(bais)) { - // 获取图片读取器 Iterator readers = ImageIO.getImageReaders(iis); if (!readers.hasNext()) { - return imageBytes; // 无读取器,返回原字节 + return 0; } ImageReader reader = readers.next(); 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); - String exifFormat = "javax_imageio_jpeg_image_1.0"; + int rotation = 0; - // 直接尝试获取元数据树,不先判断containsTree() - // 捕获异常即可,无需提前检查 - Object metadataTree; - try { - metadataTree = metadata.getAsTree(exifFormat); - } catch (IllegalArgumentException e) { - // 不支持该格式,返回0度 - return 0; + // 尝试所有支持的EXIF格式 + String[] formats = {"javax_imageio_jpeg_image_1.0", "javax_imageio_tiff_image_1.0"}; + for (String format : formats) { + try { + IIOMetadataNode root = (IIOMetadataNode) metadata.getAsTree(format); + rotation = parseOrientationFromMetadata(root); + if (rotation != 0) { + break; + } + } catch (Exception e) { + continue; + } } - if (!(metadataTree instanceof IIOMetadataNode)) { - return 0; - } - - 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; - } + reader.dispose(); + return rotation; } catch (Exception e) { - // 任何异常都返回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++) { Object item = parent.item(i); if (item instanceof IIOMetadataNode) { - IIOMetadataNode node = (IIOMetadataNode) item; - if (name.equals(node.getNodeName())) { - return node; + IIOMetadataNode child = (IIOMetadataNode) item; + IIOMetadataNode found = findNodeRecursive(child, nodeName); + 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 height = img.getHeight(); - // 计算旋转后的画布尺寸 - int newWidth = (angle % 180 == 0) ? width : height; - int newHeight = (angle % 180 == 0) ? height : width; + // 计算旋转后的尺寸 + int newWidth = 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); Graphics2D g2d = rotated.createGraphics(); - // 抗锯齿,避免旋转后模糊(JDK8必须) + // JDK 8支持的渲染参数 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); 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, (newWidth - width) / 2, (newHeight - height) / 2, null); + // 绘制旋转后的图片 + g2d.drawImage(img, transform, null); g2d.dispose(); return rotated; } /** - * JDK8适配:识别图片格式(jpg/png) + * JDK 8兼容:识别图片格式 */ - private static String getImageFormatForJdk8(byte[] imageBytes) { - if (imageBytes.length < 4) return "jpg"; - // JDK8字节判断,兼容所有系统 - if (imageBytes[0] == (byte)0xFF && imageBytes[1] == (byte)0xD8) { + private static String getImageFormat(byte[] imageBytes) { + if (imageBytes.length < 8) { 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"; } + // 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"; } -} + + // -------------------- 应急方法:手动指定旋转角度 -------------------- + /** + * 手动指定旋转角度(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(); + } + } +} \ No newline at end of file