@PostMapping("/upload2")
public ResponseEntity<?> handleUpload(@RequestParam("file") MultipartFile file) {
CorrectedMarzipanoGenerator generator = new CorrectedMarzipanoGenerator();
try {
String uuid = UUID.randomUUID().toString();
String extension = file.getOriginalFilename().substring(
file.getOriginalFilename().lastIndexOf(".")
);
Path uploadPath = Paths.get(tilesDir, "uploads", uuid + extension);
Files.createDirectories(uploadPath.getParent());
file.transferTo(uploadPath.toFile());
BufferedImage image = ImageIO.read(uploadPath.toFile());
String outputDir = Paths.get(tilesDir, "tiles", uuid).toString();
new File(outputDir).mkdirs();
generator.generate(image, outputDir);
Map<String, Object> response = new HashMap<>();
response.put("status", "success");
response.put("previewUrl", "/tiles/" + uuid + "/preview.jpg");
response.put("levels", 4);
return ResponseEntity.ok(response);
} catch (Exception e) {
Map<String, Object> error = new HashMap<>();
error.put("status", "error");
error.put("message", e.getMessage());
return ResponseEntity.internalServerError().body(error);
}
}
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class CorrectedMarzipanoGenerator {
private static final List<String> FACE_ORDER = Arrays.asList("b", "d", "f", "l", "r", "u");
private static final int TILE_SIZE = 512;
private static final int PREVIEW_WIDTH = 256;
private static final int PREVIEW_HEIGHT = 1536;
private static final int THREAD_POOL_SIZE = Runtime.getRuntime().availableProcessors();
public void generate(BufferedImage equiImage, String outputDir) throws IOException {
if (equiImage.getWidth() != 2 * equiImage.getHeight()) {
throw new IllegalArgumentException("输入图片必须是2:1等距柱状图");
}
generatePreview(equiImage, outputDir + "/preview.jpg");
int cubeSize = equiImage.getHeight() / 2;
ExecutorService executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
try {
executor.submit(() -> {
try {
generateLevel(equiImage, outputDir, 1, cubeSize);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
executor.submit(() -> {
try {
generateLevel(equiImage, outputDir, 2, cubeSize / 2);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
executor.submit(() -> {
try {
generateLevel(equiImage, outputDir, 3, cubeSize / 4);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
executor.submit(() -> {
try {
generateLevel(equiImage, outputDir, 4, cubeSize / 8);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
} finally {
executor.shutdown();
try {
executor.awaitTermination(1, TimeUnit.HOURS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
private void generatePreview(BufferedImage src, String outputPath) throws IOException {
int faceHeight = PREVIEW_HEIGHT / 6;
BufferedImage preview = new BufferedImage(PREVIEW_WIDTH, PREVIEW_HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics2D g = preview.createGraphics();
g.setColor(Color.BLACK);
g.fillRect(0, 0, PREVIEW_WIDTH, PREVIEW_HEIGHT);
for (int i = 0; i < FACE_ORDER.size(); i++) {
String faceName = FACE_ORDER.get(i);
BufferedImage face = extractAndResizeFace(src, faceName, PREVIEW_WIDTH, faceHeight);
g.drawImage(face, 0, i * faceHeight, null);
face.flush();
}
ImageIO.write(preview, "jpg", new File(outputPath));
g.dispose();
}
private void generateLevel(BufferedImage src, String outputDir, int level, int faceSize) throws IOException {
int tilesPerSide = (int) Math.pow(2, level - 1);
int adjustedSize = tilesPerSide * TILE_SIZE;
ExecutorService faceExecutor = Executors.newFixedThreadPool(THREAD_POOL_SIZE / 2);
try {
for (String face : FACE_ORDER) {
faceExecutor.submit(() -> {
try {
BufferedImage faceImage = extractCubeFace(src, face, adjustedSize);
ExecutorService tileExecutor = Executors.newFixedThreadPool(THREAD_POOL_SIZE / 2);
try {
for (int ty = 0; ty < tilesPerSide; ty++) {
final int currentTy = ty;
tileExecutor.submit(() -> {
try {
for (int tx = 0; tx < tilesPerSide; tx++) {
saveTile(
faceImage.getSubimage(
tx * TILE_SIZE,
currentTy * TILE_SIZE,
TILE_SIZE,
TILE_SIZE
),
String.format("%s/%d/%s/%d/%d.jpg", outputDir, level, face, currentTy, tx)
);
}
} catch (IOException e) {
e.printStackTrace();
}
});
}
} finally {
tileExecutor.shutdown();
tileExecutor.awaitTermination(30, TimeUnit.MINUTES);
}
faceImage.flush();
} catch (Exception e) {
e.printStackTrace();
}
});
}
} finally {
faceExecutor.shutdown();
try {
faceExecutor.awaitTermination(1, TimeUnit.HOURS);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
private void addFaceLabel(Graphics2D g, String faceName, int x, int y, int width, int height) {
Font font = new Font("Arial", Font.BOLD, height / 3);
g.setFont(font);
FontMetrics metrics = g.getFontMetrics();
int textWidth = metrics.stringWidth(faceName);
int textX = x + (width - textWidth) / 2;
int textY = y + ((height - metrics.getHeight()) / 2) + metrics.getAscent();
g.setColor(Color.BLACK);
g.drawString(faceName, textX - 1, textY - 1);
g.drawString(faceName, textX + 1, textY - 1);
g.drawString(faceName, textX - 1, textY + 1);
g.drawString(faceName, textX + 1, textY + 1);
g.setColor(Color.RED);
g.drawString(faceName, textX, textY);
}
private BufferedImage addLabelsToFace(BufferedImage faceImage, String faceName, int tilesPerSide) {
BufferedImage labeledFace = new BufferedImage(
faceImage.getWidth(),
faceImage.getHeight(),
BufferedImage.TYPE_INT_RGB
);
Graphics2D g = labeledFace.createGraphics();
g.drawImage(faceImage, 0, 0, null);
for (int ty = 0; ty < tilesPerSide; ty++) {
for (int tx = 0; tx < tilesPerSide; tx++) {
int centerX = tx * TILE_SIZE + TILE_SIZE/2;
int centerY = ty * TILE_SIZE + TILE_SIZE/2;
addTileLabel(
g,
faceName,
centerX - TILE_SIZE/4,
centerY - TILE_SIZE/4,
TILE_SIZE/2,
TILE_SIZE/2
);
}
}
g.dispose();
return labeledFace;
}
private void addTileLabel(Graphics2D g, String faceName, int x, int y, int width, int height) {
Font font = new Font("Arial", Font.BOLD, height / 3);
g.setFont(font);
FontMetrics metrics = g.getFontMetrics();
int textWidth = metrics.stringWidth(faceName);
int textX = x + (width - textWidth) / 2;
int textY = y + ((height - metrics.getHeight()) / 2) + metrics.getAscent();
g.setColor(Color.BLACK);
g.drawString(faceName, textX - 1, textY - 1);
g.drawString(faceName, textX + 1, textY - 1);
g.drawString(faceName, textX - 1, textY + 1);
g.drawString(faceName, textX + 1, textY + 1);
g.setColor(Color.RED);
g.drawString(faceName, textX, textY);
}
private BufferedImage extractCubeFace(BufferedImage src, String face, int size) {
BufferedImage faceImage = new BufferedImage(size, size, BufferedImage.TYPE_INT_RGB);
for (int y = 0; y < size; y++) {
for (int x = 0; x < size; x++) {
double[] uv = getCorrectedUV(x, y, size, face);
int srcX = (int) (uv[0] * (src.getWidth() - 1));
int srcY = (int) (uv[1] * (src.getHeight() - 1));
srcX = Math.max(0, Math.min(srcX, src.getWidth() - 1));
srcY = Math.max(0, Math.min(srcY, src.getHeight() - 1));
faceImage.setRGB(x, y, src.getRGB(srcX, srcY));
}
}
return faceImage;
}
private double[] getCorrectedUV(int x, int y, int size, String face) {
double u = (2.0 * x / size) - 1;
double v = (2.0 * y / size) - 1;
double[] vec;
switch (face) {
case "f":
vec = new double[]{u, -v, 1}; break;
case "r":
vec = new double[]{1, -v, -u}; break;
case "b":
vec = new double[]{-u, -v, -1}; break;
case "l":
vec = new double[]{-1, -v, u}; break;
case "u":
vec = new double[]{u, 1, v}; break;
case "d":
vec = new double[]{u, -1, -v}; break;
default:
throw new IllegalArgumentException("无效面类型: " + face);
}
double len = Math.sqrt(vec[0] * vec[0] + vec[1] * vec[1] + vec[2] * vec[2]);
vec[0] /= len;
vec[1] /= len;
vec[2] /= len;
double theta = Math.atan2(vec[0], vec[2]);
double phi = Math.asin(vec[1]);
return new double[]{
(theta + Math.PI) / (2 * Math.PI),
(Math.PI / 2 - phi) / Math.PI
};
}
private BufferedImage extractAndResizeFace(BufferedImage src, String face, int width, int height) {
int srcSize = src.getHeight() / 2;
BufferedImage faceImage = extractCubeFace(src, face, srcSize);
BufferedImage resized = resize(faceImage, width, height);
faceImage.flush();
return resized;
}
private BufferedImage resize(BufferedImage src, int width, int height) {
BufferedImage resized = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = resized.createGraphics();
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g.drawImage(src, 0, 0, width, height, null);
g.dispose();
return resized;
}
private void saveTile(BufferedImage tile, String path) throws IOException {
File outputFile = new File(path);
File parent = outputFile.getParentFile();
if (!parent.exists()) {
parent.mkdirs();
}
try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outputFile))) {
ImageIO.write(tile, "jpg", bos);
}
}
}