springboot使用GDAL获取tif文件的缩略图并转为base64
首先需要安装gdal:https://blog.csdn.net/qq_61950936/article/details/142880279?spm=1001.2014.3001.5501
然后是配置pom.xml文件:
<!--处理缩略图的-->
<dependency>
<groupId>org.gdal</groupId>
<artifactId>gdal</artifactId>
<version>3.9.0</version>
</dependency>
更新maven。
效果:
TiffThumbnailGenerator类:
package com.geofly.dataservicecenter.api.common.util;
import java.awt.*;
import org.gdal.gdal.Dataset;
import org.gdal.gdal.gdal;
import org.gdal.gdal.Band;
import org.gdal.gdalconst.gdalconstConstants;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import java.io.*;
import java.util.Base64;
/**
* @Description: 读取 tif文件的InputStream 并生成缩略图
*
* @Auther: yanghaoxing
* @Date: 2024/10/12
*/
public class TiffThumbnailGenerator {
static {
System.loadLibrary("gdal");
gdal.AllRegister();
}
// 将 InputStream 写入临时文件
private static File inputStreamToFile(InputStream inputStream) throws IOException {
File tempFile = File.createTempFile("tempTiff", ".tif");
try (FileOutputStream out = new FileOutputStream(tempFile)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
return tempFile;
}
/**
* @Description: 传入InputStream,读取缩略图的BufferedImage
*
* @Param: [inputStream, width, height]
* @Return: java.awt.image.BufferedImage
* @Author yanghaoxing
* @Date 2024/10/12 16:25
*/
public static BufferedImage generateThumbnail(InputStream inputStream, int width, int height) throws IOException {
File tiffFile = inputStreamToFile(inputStream);
Dataset dataset = gdal.Open(tiffFile.getAbsolutePath(), gdalconstConstants.GA_ReadOnly);
if (dataset == null) {
// 无法打开 TIFF 文件
return null;
}
int xSize = dataset.getRasterXSize();
int ySize = dataset.getRasterYSize();
// 读取三个波段(假设分别为 R、G、B)
Band bandRed = dataset.GetRasterBand(1); // 红波段
Band bandGreen = dataset.GetRasterBand(2); // 绿波段
Band bandBlue = dataset.GetRasterBand(3); // 蓝波段
// 分配数组来存储波段数据
int[] redData = new int[xSize * ySize];
int[] greenData = new int[xSize * ySize];
int[] blueData = new int[xSize * ySize];
// 读取波段数据
bandRed.ReadRaster(0, 0, xSize, ySize, redData);
bandGreen.ReadRaster(0, 0, xSize, ySize, greenData);
bandBlue.ReadRaster(0, 0, xSize, ySize, blueData);
// 创建 RGB 图像
BufferedImage originalImage = new BufferedImage(xSize, ySize, BufferedImage.TYPE_INT_RGB);
for (int y = 0; y < ySize; y++) {
for (int x = 0; x < xSize; x++) {
int r = redData[y * xSize + x];
int g = greenData[y * xSize + x];
int b = blueData[y * xSize + x];
int rgb = (r << 16) | (g << 8) | b;
originalImage.setRGB(x, y, rgb);
}
}
// 先裁剪掉黑边
BufferedImage croppedImage = cropBlackBorders(originalImage);
// 然后缩放并居中图像
BufferedImage resizedImage = resizeAndCenterImage(croppedImage, width, height);
// 关闭数据集
dataset.delete();
return resizedImage;
}
// 缩放图片以适应指定大小并保持宽高比
public static BufferedImage resizeAndCenterImage(BufferedImage originalImage, int targetWidth, int targetHeight) {
int originalWidth = originalImage.getWidth();
int originalHeight = originalImage.getHeight();
// 计算缩放比例,保持宽高比
double scaleFactor = Math.min((double) targetWidth / originalWidth, (double) targetHeight / originalHeight);
int scaledWidth = (int) (originalWidth * scaleFactor);
int scaledHeight = (int) (originalHeight * scaleFactor);
// 创建一个新的目标图像(透明背景)
BufferedImage resizedImage = new BufferedImage(targetWidth, targetHeight, BufferedImage.TYPE_INT_ARGB);
// 在新图像上进行绘制
Graphics2D g2d = resizedImage.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
// 计算居中位置
int x = (targetWidth - scaledWidth) / 2;
int y = (targetHeight - scaledHeight) / 2;
// 填充透明背景并绘制缩放后的图像
g2d.drawImage(originalImage, x, y, scaledWidth, scaledHeight, null);
g2d.dispose();
return resizedImage;
}
// 裁剪黑边(裁剪掉纯黑的区域)
public static BufferedImage cropBlackBorders(BufferedImage image) {
int width = image.getWidth();
int height = image.getHeight();
int top = 0, bottom = height - 1, left = 0, right = width - 1;
boolean foundTop = false, foundBottom = false, foundLeft = false, foundRight = false;
// 检测上边界
for (int y = 0; y < height && !foundTop; y++) {
for (int x = 0; x < width; x++) {
if (!isBlack(image.getRGB(x, y))) {
top = y;
foundTop = true;
break;
}
}
}
// 检测下边界
for (int y = height - 1; y >= 0 && !foundBottom; y--) {
for (int x = 0; x < width; x++) {
if (!isBlack(image.getRGB(x, y))) {
bottom = y;
foundBottom = true;
break;
}
}
}
// 检测左边界
for (int x = 0; x < width && !foundLeft; x++) {
for (int y = 0; y < height; y++) {
if (!isBlack(image.getRGB(x, y))) {
left = x;
foundLeft = true;
break;
}
}
}
// 检测右边界
for (int x = width - 1; x >= 0 && !foundRight; x--) {
for (int y = 0; y < height; y++) {
if (!isBlack(image.getRGB(x, y))) {
right = x;
foundRight = true;
break;
}
}
}
// 裁剪图像
return image.getSubimage(left, top, right - left + 1, bottom - top + 1);
}
// 判断一个像素是否是黑色(你可以根据需要调整黑色的判断标准)
private static boolean isBlack(int rgb) {
Color color = new Color(rgb);
// 理论上应该是:color.getRed() == 0 && color.getGreen() == 0 && color.getBlue() == 0; 但是目前测试数据的黑边是色值是:1,1,0
// 白边也去掉
return (color.getRed() == 1 && color.getGreen() == 1 && color.getBlue() == 0) ||
(color.getRed() == 0 && color.getGreen() == 0 && color.getBlue() == 0) ||
(color.getRed() == 255 && color.getGreen() == 255 && color.getBlue() == 255);
}
// 将 BufferedImage 转换为 Base64 字符串
public static String bufferedImageToBase64(BufferedImage image, String format) throws IOException {
if (image == null) {
return null;
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageIO.write(image, format, outputStream);
byte[] imageBytes = outputStream.toByteArray();
String base64Image = Base64.getEncoder().encodeToString(imageBytes);
outputStream.close();
return base64Image;
}
/**
* @Description: 将BufferedImage写入指定路径
*
* @Param: [thumbnail, outputFilePath]
* @Return: void
* @Author yanghaoxing
* @Date 2024/10/12 16:24
*/
public static void saveThumbnailToFile(BufferedImage thumbnail, String outputFilePath) throws IOException {
File outputfile = new File(outputFilePath);
ImageIO.write(thumbnail, "png", outputfile);
}
}
使用:
/**
* @Description: 根据文件id获取该tif图的缩略图
*
* @Param: [fileId]
* @Return: String base64Image
* @Author yanghaoxing
* @Date 2024/10/12 10:24
*/
private String getTiffImageBuffered(List<String> fileIds) {
int width = 255, height = 255;
FileDownloadVo fileDownloadVo = uploadService.getFile(fileIds.get(0));
if (fileDownloadVo != null && fileDownloadVo.getFileStream() != null) {
InputStream inputStream = fileDownloadVo.getFileStream();
try {
// 传入InputStream,读取缩略图的BufferedImage
BufferedImage bufferedImage = TiffThumbnailGenerator.generateThumbnail(inputStream, width, height);
// 转为base64格式
String base64Image = TiffThumbnailGenerator.bufferedImageToBase64(bufferedImage, "png");
return base64Image != null ? "data:image/png;base64," + base64Image : null;
} catch (IOException e) {
return null;
}
}
return null;
}