| | |
| | | package com.ruoyi.service.impl; |
| | | |
| | | import com.drew.imaging.ImageMetadataReader; |
| | | import com.drew.metadata.Metadata; |
| | | import com.drew.metadata.exif.ExifIFD0Directory; |
| | | import com.ruoyi.common.config.RuoYiConfig; |
| | | import com.ruoyi.common.utils.StringUtils; |
| | | import lombok.extern.slf4j.Slf4j; |
| | |
| | | import javax.imageio.ImageWriter; |
| | | import javax.imageio.stream.MemoryCacheImageOutputStream; |
| | | import java.awt.*; |
| | | import java.awt.geom.AffineTransform; |
| | | import java.awt.image.BufferedImage; |
| | | import java.io.*; |
| | | import java.nio.file.Files; |
| | |
| | | |
| | | |
| | | |
| | | /** |
| | | * 读取图片并处理EXIF方向 |
| | | */ |
| | | private BufferedImage readImageWithExifOrientation(File file) throws Exception { |
| | | // 读取原始图片 |
| | | BufferedImage originalImage = ImageIO.read(file); |
| | | if (originalImage == null) { |
| | | return null; |
| | | } |
| | | |
| | | // 获取EXIF方向 |
| | | int orientation = 1; |
| | | try { |
| | | orientation = getExifOrientation(file); |
| | | } catch (Exception e) { |
| | | // 如果不能读取EXIF,使用默认方向 |
| | | log.warn("无法读取EXIF方向信息: {}", e.getMessage()); |
| | | } |
| | | |
| | | // 根据方向旋转图片 |
| | | return rotateImageByExifOrientation(originalImage, orientation); |
| | | } |
| | | |
| | | /** |
| | | * 获取EXIF方向信息 |
| | | */ |
| | | private int getExifOrientation(File file) throws Exception { |
| | | Metadata metadata = ImageMetadataReader.readMetadata(file); |
| | | ExifIFD0Directory exifIFD0Directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class); |
| | | |
| | | if (exifIFD0Directory != null && exifIFD0Directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) { |
| | | return exifIFD0Directory.getInt(ExifIFD0Directory.TAG_ORIENTATION); |
| | | } |
| | | |
| | | return 1; // 默认方向 |
| | | } |
| | | |
| | | /** |
| | | * 根据EXIF方向旋转图片 |
| | | */ |
| | | private BufferedImage rotateImageByExifOrientation(BufferedImage image, int orientation) { |
| | | int width = image.getWidth(); |
| | | int height = image.getHeight(); |
| | | |
| | | AffineTransform transform = new AffineTransform(); |
| | | |
| | | switch (orientation) { |
| | | case 1: // 正常 |
| | | return image; |
| | | case 2: // 水平翻转 |
| | | transform.scale(-1, 1); |
| | | transform.translate(-width, 0); |
| | | break; |
| | | case 3: // 旋转180度 |
| | | transform.translate(width, height); |
| | | transform.rotate(Math.PI); |
| | | break; |
| | | case 4: // 垂直翻转 |
| | | transform.scale(1, -1); |
| | | transform.translate(0, -height); |
| | | break; |
| | | case 5: // 顺时针旋转90度 + 水平翻转 |
| | | transform.rotate(-Math.PI / 2); |
| | | transform.scale(-1, 1); |
| | | break; |
| | | case 6: // 顺时针旋转90度 |
| | | transform.rotate(Math.PI / 2); |
| | | transform.translate(0, -height); |
| | | break; |
| | | case 7: // 顺时针旋转270度 + 水平翻转 |
| | | transform.rotate(Math.PI / 2); |
| | | transform.scale(-1, 1); |
| | | transform.translate(-width, 0); |
| | | break; |
| | | case 8: // 顺时针旋转270度 |
| | | transform.rotate(-Math.PI / 2); |
| | | transform.translate(-width, 0); |
| | | break; |
| | | default: |
| | | return image; |
| | | } |
| | | |
| | | // 计算新图片尺寸 |
| | | double newWidth, newHeight; |
| | | if (orientation >= 5 && orientation <= 8) { |
| | | // 旋转了90或270度,宽高互换 |
| | | newWidth = height; |
| | | newHeight = width; |
| | | } else { |
| | | newWidth = width; |
| | | newHeight = height; |
| | | } |
| | | |
| | | BufferedImage rotatedImage = new BufferedImage( |
| | | (int) Math.ceil(newWidth), |
| | | (int) Math.ceil(newHeight), |
| | | image.getType() |
| | | ); |
| | | |
| | | Graphics2D g2d = rotatedImage.createGraphics(); |
| | | g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, |
| | | RenderingHints.VALUE_INTERPOLATION_BILINEAR); |
| | | |
| | | g2d.drawImage(image, transform, null); |
| | | g2d.dispose(); |
| | | |
| | | return rotatedImage; |
| | | } |
| | | |
| | | |
| | | /** |
| | | * 处理图片文件 |
| | | */ |
| | | public Map<String, Object> processImage(File file, int width, int height, |
| | | float quality, String format,boolean deleteY) throws IOException { |
| | | float quality, String format) throws Exception { |
| | | Map<String, Object> result = new HashMap<>(); |
| | | |
| | | // 读取图片 |
| | | BufferedImage image = ImageIO.read(file); |
| | | BufferedImage image = readImageWithExifOrientation(file); |
| | | if (image == null) { |
| | | result.put("success", false); |
| | | result.put("message", "无法读取图片文件"); |
| | |
| | | result.put("compressedHeight", height > 0 ? height : originalHeight); |
| | | result.put("base64", dataUrl); |
| | | result.put("message", "图片压缩成功"); |
| | | if (deleteY) { |
| | | file.delete(); |
| | | result.put("originalDeleted", false); |
| | | result.put("message", "图片压缩成功"); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | /** |
| | | * 压缩为PNG格式 |
| | | * 压缩为PNG格式(兼容性修复) |
| | | */ |
| | | private void compressAsPng(BufferedImage image, ByteArrayOutputStream output, int compressionLevel) |
| | | throws IOException { |
| | | Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("png"); |
| | | if (!writers.hasNext()) { |
| | | ImageIO.write(image, "png", output); |
| | | // 如果没有PNG编码器,使用默认方式 |
| | | boolean written = ImageIO.write(image, "png", output); |
| | | if (!written) { |
| | | throw new IOException("无法写入PNG格式"); |
| | | } |
| | | return; |
| | | } |
| | | |
| | | ImageWriter writer = writers.next(); |
| | | ImageWriteParam param = writer.getDefaultWriteParam(); |
| | | try { |
| | | ImageWriteParam param = writer.getDefaultWriteParam(); |
| | | |
| | | // PNG的压缩级别(0-9,0最快但压缩率低,9最慢但压缩率高) |
| | | if (compressionLevel >= 0) { |
| | | param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); |
| | | param.setCompressionType("Deflate"); |
| | | param.setCompressionQuality(Math.max(0.0f, Math.min(1.0f, compressionLevel / 9.0f))); |
| | | // PNG的压缩级别(0-9,0最快但压缩率低,9最慢但压缩率高) |
| | | if (compressionLevel >= 0) { |
| | | try { |
| | | // 先检查是否支持压缩模式 |
| | | if (param.canWriteCompressed()) { |
| | | param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); |
| | | // 检查是否支持Deflate压缩类型 |
| | | String[] compressionTypes = param.getCompressionTypes(); |
| | | if (compressionTypes != null) { |
| | | for (String type : compressionTypes) { |
| | | if ("Deflate".equalsIgnoreCase(type)) { |
| | | param.setCompressionType("Deflate"); |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | param.setCompressionQuality(Math.max(0.0f, Math.min(1.0f, compressionLevel / 9.0f))); |
| | | } else { |
| | | // 如果不支持压缩,使用默认设置 |
| | | log.debug("PNG编码器不支持压缩设置,使用默认压缩"); |
| | | } |
| | | } catch (UnsupportedOperationException e) { |
| | | // 捕获不支持的操作异常,继续使用默认设置 |
| | | log.warn("PNG压缩设置不被支持: {}", e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | writer.setOutput(new MemoryCacheImageOutputStream(output)); |
| | | writer.write(null, new IIOImage(image, null, null), param); |
| | | |
| | | } finally { |
| | | writer.dispose(); |
| | | } |
| | | |
| | | writer.setOutput(new MemoryCacheImageOutputStream(output)); |
| | | writer.write(null, new IIOImage(image, null, null), param); |
| | | writer.dispose(); |
| | | } |
| | | |
| | | |
| | |
| | | * 处理视频文件:生成Base64编码的封面 (使用JavaCV) |
| | | */ |
| | | public Map<String, Object> processVideo(File file, int width, int height, |
| | | float quality, String format, boolean deleteY) { |
| | | float quality, String format) { |
| | | Map<String, Object> result = new HashMap<>(); |
| | | |
| | | try { |
| | |
| | | log.error("视频封面生成失败", e); |
| | | result.put("success", false); |
| | | result.put("message", "封面生成失败: " + e.getMessage()); |
| | | } finally { |
| | | // 清理临时文件 |
| | | if (deleteY && file != null && file.exists()) { |
| | | try { |
| | | file.delete(); |
| | | } catch (Exception e) { |
| | | log.warn("删除临时视频文件时出错", e); |
| | | } |
| | | } |
| | | } |
| | | return result; |
| | | } |