package org.sxkj.common.utils; import javax.imageio.IIOImage; import javax.imageio.ImageIO; import javax.imageio.ImageWriteParam; import javax.imageio.ImageWriter; import javax.imageio.stream.ImageOutputStream; import javax.imageio.stream.MemoryCacheImageOutputStream; import java.awt.*; import java.awt.image.BufferedImage; import java.io.*; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.Iterator; public class ImgZipUtil { /** * tif 压缩转换成 jpeg * @param inputFile * @param targetSizeKB 压缩目标大小 * @param targetName 压缩目标名称,例如 _small,_show * @return */ public static File compressTifToJpeg(File inputFile,int targetSizeKB,String targetName) throws IOException { File jpeg = TifCompressorUtils.convertTifToJpeg(inputFile, ".jpeg"); File file = compressImage(jpeg, targetSizeKB, targetName); return file; } /** * 图片压缩 * @param inputFile * @param targetSizeKB 压缩目标大小 * @param targetName 压缩目标名称,例如 _small,_show * @return * @throws IOException */ public static File compressImage(File inputFile, int targetSizeKB,String targetName) throws IOException { BufferedImage image = ImageIO.read(inputFile); // 计算目标图片的尺寸 long targetSizeBytes = targetSizeKB * 1024L; long originalSizeBytes = getImageSize(image); double compressionRatio = (double) targetSizeBytes / originalSizeBytes; int targetWidth = (int) (image.getWidth() * Math.sqrt(compressionRatio)); int targetHeight = (int) (image.getHeight() * Math.sqrt(compressionRatio)); // 使用ImageIO进行压缩 BufferedImage compressedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB); Graphics2D graphics = compressedImage.createGraphics(); graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); graphics.drawImage(image, 0, 0, targetWidth, targetHeight, null); graphics.dispose(); String name = inputFile.getName(); String fileName = name.substring(0,name.lastIndexOf(".")); // 获取文件名称 String extension = name.substring(name.lastIndexOf(".")); // 获取后缀名 // 将压缩后的图片写入输出文件 File outputFile = new File(inputFile.getParent(), fileName + targetName + extension); ImageIO.write(compressedImage, "jpeg", outputFile); return outputFile; } /** * 图片压缩 * * @return * @throws IOException */ public static File compressImage(File inputFile, int targetSizeKB) throws IOException { BufferedImage image = ImageIO.read(inputFile); // 计算目标图片的尺寸 long targetSizeBytes = targetSizeKB * 1024L; long originalSizeBytes = getImageSize(image); double compressionRatio = (double) targetSizeBytes / originalSizeBytes; int targetWidth = (int) (image.getWidth() * Math.sqrt(compressionRatio)); int targetHeight = (int) (image.getHeight() * Math.sqrt(compressionRatio)); // 使用ImageIO进行压缩 BufferedImage compressedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB); Graphics2D graphics = compressedImage.createGraphics(); graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); graphics.drawImage(image, 0, 0, targetWidth, targetHeight, null); graphics.dispose(); // 将压缩后的图片写入输出文件 File outputFile = new File(inputFile.getParent(), "compressed_" + inputFile.getName()); ImageIO.write(compressedImage, "jpeg", outputFile); return outputFile; } public static ByteArrayInputStream compressImage(InputStream inputStream, int targetSizeKB) throws IOException { if (!inputStream.markSupported()) { inputStream = new BufferedInputStream(inputStream); } String format = getOriginalFormat(inputStream); // format will be "jpg" or "png" BufferedImage image = ImageIO.read(inputStream); if (image == null) { throw new IOException("图片读取异常"); } // 将 "jpeg" 统一为 "jpg" 以便内部处理,虽然 ImageIO 通常两者都认 String internalFormat = "jpeg".equalsIgnoreCase(format) ? "jpg" : format.toLowerCase(); long originalSizeBytes = getImageSize(image, internalFormat); // 如果小于2MB,直接返回原图 (重新编码成原始格式) // if (originalSizeBytes < 2 * 1024 * 1024) { // ByteArrayOutputStream baos = new ByteArrayOutputStream(); // if (!ImageIO.write(image, internalFormat, baos)) { // throw new IOException("Failed to write original image to byte array for format: " + internalFormat); // } // return new ByteArrayInputStream(baos.toByteArray()); // } long targetSizeBytes = targetSizeKB * 1024L; if (targetSizeBytes <= 0) { targetSizeBytes = 1; } if (originalSizeBytes <= 0) { originalSizeBytes = 1; } double compressionRatio = (double) targetSizeBytes / originalSizeBytes; // 限制压缩比,避免图片缩得太小或反而变大 compressionRatio = Math.min(1.0, Math.max(0.01, compressionRatio)); // 最小缩小到1% int targetWidth = (int) (image.getWidth() * Math.sqrt(compressionRatio)); int targetHeight = (int) (image.getHeight() * Math.sqrt(compressionRatio)); targetWidth = Math.max(1, targetWidth); targetHeight = Math.max(1, targetHeight); BufferedImage compressedImage; if ("png".equalsIgnoreCase(internalFormat)) { // 对于PNG,保留Alpha通道 compressedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_ARGB); } else { // "jpg" // 对于JPG,使用RGB (JPG不支持Alpha) // 如果原始图像有Alpha,转换到RGB时Alpha会丢失 // 如果原始PNG image.getType() 是 TYPE_CUSTOM 或其他复杂类型,直接绘制到TYPE_INT_RGB可能需要注意 // 但通常 ImageIO.read() 会返回一个标准类型 if (image.getType() == BufferedImage.TYPE_INT_ARGB || image.getType() == BufferedImage.TYPE_INT_ARGB_PRE || image.getTransparency() != BufferedImage.OPAQUE) { // 如果原图是PNG且有透明度,但目标是JPG,创建一个RGB图像,绘制时透明部分会变黑(或Graphics2D的默认背景色) // 更好的做法是先在白色背景上绘制原图,再缩放 BufferedImage tempImageWithWhiteBg = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_RGB); Graphics2D g2dTemp = tempImageWithWhiteBg.createGraphics(); g2dTemp.setColor(java.awt.Color.WHITE); // 设置背景为白色 g2dTemp.fillRect(0, 0, image.getWidth(), image.getHeight()); g2dTemp.drawImage(image, 0, 0, null); g2dTemp.dispose(); image = tempImageWithWhiteBg; // 后续缩放基于这个去除了透明度的图 } compressedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB); } Graphics2D graphics = compressedImage.createGraphics(); graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); // 如果需要更高质量的缩放(但更慢) // graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); // graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); graphics.drawImage(image, 0, 0, targetWidth, targetHeight, null); graphics.dispose(); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // 对于JPG,可以尝试设置压缩质量,但这会使大小更难预测 // if ("jpg".equalsIgnoreCase(internalFormat)) { // Iterator writers = ImageIO.getImageWritersByFormatName("jpeg"); // "jpeg" or "jpg" // if (writers.hasNext()) { // ImageWriter writer = writers.next(); // ImageWriteParam param = writer.getDefaultWriteParam(); // if (param.canWriteCompressed()) { // param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); // param.setCompressionQuality(0.75f); // 0.0 (高压缩) to 1.0 (高质量) // } // ImageOutputStream ios = ImageIO.createImageOutputStream(byteArrayOutputStream); // writer.setOutput(ios); // writer.write(null, new IIOImage(compressedImage, null, null), param); // ios.close(); // writer.dispose(); // } else { // throw new IOException("No JPEG writer found for setting quality."); // } // } else { if (!ImageIO.write(compressedImage, internalFormat, byteArrayOutputStream)) { throw new IOException("处理缩略图片失败: " + internalFormat); } // } return new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); } // 获取图像的字节大小 /** * 图片压缩 * * @return * @throws IOException */ public static ByteArrayInputStream compressImageByInputString(InputStream inputStream, int targetSizeKB) throws IOException { BufferedImage image = ImageIO.read(inputStream); // 计算目标图片的尺寸 long targetSizeBytes = targetSizeKB * 1024L; long originalSizeBytes = getImageSize(image); double compressionRatio = (double) targetSizeBytes / originalSizeBytes; int targetWidth = (int) (image.getWidth() * Math.sqrt(compressionRatio)); int targetHeight = (int) (image.getHeight() * Math.sqrt(compressionRatio)); // 使用ImageIO进行压缩 BufferedImage compressedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_RGB); Graphics2D graphics = compressedImage.createGraphics(); graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); graphics.drawImage(image, 0, 0, targetWidth, targetHeight, null); // 释放图形上下文 graphics.dispose(); // 将处理后的图片转换为输入流 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // 将压缩后的图片写入输出文件 ImageIO.write(compressedImage, "jpeg",byteArrayOutputStream); // 返回 return new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); } public static long getImageSize(BufferedImage image) { File tempFile; try { tempFile = File.createTempFile("temp", ".tmp"); ImageIO.write(image, "jpg", tempFile); long size = tempFile.length(); tempFile.delete(); return size; } catch (IOException ex) { ex.printStackTrace(); return 0; } } public static long getImageSize(BufferedImage image, String format) throws IOException { // 规范化格式名称 (例如,将 "jpeg" 转为 "jpg",全部转为小写) String actualFormat = "jpeg".equalsIgnoreCase(format) ? "jpg" : format.toLowerCase(); File tempFile = null; try { // 1. 创建一个临时文件来写入图像数据 tempFile = File.createTempFile("temp_img_size_", "." + actualFormat); // System.out.println("DEBUG (getImageSize): Temp file will be created at: " + tempFile.getAbsolutePath()); // 2. 获取指定格式的 ImageWriter Iterator writers = ImageIO.getImageWritersByFormatName(actualFormat); if (!writers.hasNext()) { // 如果没有找到 ImageWriter,则无法进行写入操作 throw new IOException("无法找到合适的 ImageWriter 来写入 " + actualFormat + " 格式"); } ImageWriter writer = writers.next(); // 通常使用第一个找到的 writer // 3. 创建一个 ImageOutputStream 连接到临时文件 ImageOutputStream ios = null; try { ios = ImageIO.createImageOutputStream(tempFile); if (ios == null) { // 这种情况不常见,但以防万一 throw new IOException("无法为临时文件创建 ImageOutputStream: " + tempFile.getAbsolutePath()); } // 4. 将 ImageWriter 的输出目标设置为 ImageOutputStream writer.setOutput(ios); // 5. 写入图像数据 // 使用 IIOImage 包装 BufferedImage,可以传递缩略图和元数据(这里都设为 null) // 第三个参数是 ImageWriteParam,设为 null 表示使用默认写入参数 writer.write(null, new IIOImage(image, null, null), null); // 6. 确保所有缓冲数据都已写入流 (可选,通常 close() 会做这个) // ios.flush(); // System.out.println("DEBUG (getImageSize): Image written to temp file."); } finally { // 7. 关闭 ImageOutputStream (非常重要,确保数据落盘并释放资源) if (ios != null) { try { ios.close(); // System.out.println("DEBUG (getImageSize): ImageOutputStream closed."); } catch (IOException e) { // 记录关闭流时的错误,但可能仍需尝试删除文件和释放writer System.err.println("Error closing ImageOutputStream: " + e.getMessage()); } } // 8. 释放 ImageWriter 资源 if (writer != null) { writer.dispose(); } } // 9. 获取临时文件的大小 long length = tempFile.length(); // System.out.println("DEBUG (getImageSize): Temp file length: " + length + " bytes for format " + actualFormat); return length; } finally { // 10. 无论成功与否,都尝试删除临时文件 if (tempFile != null && tempFile.exists()) { // System.out.println("DEBUG (getImageSize): Deleting temp file: " + tempFile.getAbsolutePath()); if (!tempFile.delete()) { System.err.println("警告: 无法删除临时文件: " + tempFile.getAbsolutePath()); // 可以选择让它在JVM退出时删除 // tempFile.deleteOnExit(); } } } } public static String getOriginalFormat(InputStream inputStream) throws IOException { if (!inputStream.markSupported()) { inputStream = new BufferedInputStream(inputStream); } inputStream.mark(8); // 8 bytes should be enough for JPG and PNG byte[] header = new byte[8]; int bytesRead = inputStream.read(header); inputStream.reset(); if (bytesRead < 4) { System.err.println("Warning: Could not read enough bytes to determine format. Defaulting to jpg."); return "jpg"; } // JPEG: FF D8 FF if (header[0] == (byte) 0xFF && header[1] == (byte) 0xD8 && header[2] == (byte) 0xFF) { return "jpg"; // 标准的 ImageIO format name 是 "jpeg" 或 "jpg" } // PNG: 89 50 4E 47 (‰PNG) else if (header[0] == (byte) 0x89 && header[1] == 'P' && header[2] == 'N' && header[3] == 'G') { return "png"; } System.err.println("Warning: Unknown image format based on header (only JPG/PNG supported). Defaulting to jpg."); return "jpg"; // 默认 fallback } /** * 图片压缩 * * @param originalImageFile * @param compressionQuality 图片压缩质量 0-1 * @return * @throws IOException */ public static File compressImageAndGetFile(File originalImageFile, float compressionQuality) throws IOException { BufferedImage originalImage = ImageIO.read(originalImageFile); try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next(); try (ImageOutputStream ios = new MemoryCacheImageOutputStream(baos)) { writer.setOutput(ios); ImageWriteParam param = writer.getDefaultWriteParam(); param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); param.setCompressionQuality(compressionQuality); writer.write(null, new javax.imageio.IIOImage(originalImage, null, null), param); } finally { writer.dispose(); } InputStream in = new ByteArrayInputStream(baos.toByteArray()); File tempFile = File.createTempFile("compressed-", ".jpg"); tempFile.deleteOnExit(); Files.copy(in, tempFile.toPath(), StandardCopyOption.REPLACE_EXISTING); return tempFile; } } /** * 图片压缩 * * @param inputStream * @param compressionQuality 图片压缩质量 0-1 * @return * @throws IOException */ public static ByteArrayInputStream compressImageAndGetByteInputStream(InputStream inputStream, float compressionQuality) throws IOException { BufferedImage originalImage = ImageIO.read(inputStream); ByteArrayInputStream byteArrayInputStream = null; ImageWriter writer = null; ImageOutputStream ios = null; ByteArrayOutputStream baos = new ByteArrayOutputStream(); try{ writer = ImageIO.getImageWritersByFormatName("jpg").next(); ios = new MemoryCacheImageOutputStream(baos); writer.setOutput(ios); ImageWriteParam param = writer.getDefaultWriteParam(); param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); param.setCompressionQuality(compressionQuality); writer.write(null, new javax.imageio.IIOImage(originalImage, null, null), param); byteArrayInputStream = new ByteArrayInputStream(baos.toByteArray()); }catch (Exception e){ e.printStackTrace(); }finally { byteArrayInputStream.close(); baos.close(); ios.close(); writer.dispose(); } return byteArrayInputStream; } /** * inputStream 转 byte * @param inputStream * @return * @throws IOException */ public static byte[] readInputStreamToByteArray(InputStream inputStream) throws IOException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { byteArrayOutputStream.write(buffer, 0, bytesRead); } return byteArrayOutputStream.toByteArray(); } }