You've already forked FrameTour-BE
feat(image): 支持多角度图片旋转及方向判断
- 在 PhotoProcessContext 中新增 imageRotation 字段用于存储旋转角度 - 修改 ConditionalRotateStage 支持 90、180、270 度旋转 - 优化 ImageOrientationStage 综合判断图片方向逻辑 - 新增 NoOpStage 作为空操作阶段占位符 - 解除 DeviceVideoContinuityCheckTask 的生产环境限制 - 添加完整的单元测试覆盖各种旋转场景和边界情况
This commit is contained in:
@@ -92,6 +92,13 @@ public class PhotoProcessContext {
|
|||||||
private File processedFile;
|
private File processedFile;
|
||||||
private boolean isLandscape = true;
|
private boolean isLandscape = true;
|
||||||
private boolean needRotation = false;
|
private boolean needRotation = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图像需要旋转的角度(用于后续Stage使用)
|
||||||
|
* 由 ImageOrientationStage 从 Crop.rotation 中提取并设置
|
||||||
|
*/
|
||||||
|
private Integer imageRotation;
|
||||||
|
|
||||||
private String resultUrl;
|
private String resultUrl;
|
||||||
private IStorageAdapter storageAdapter;
|
private IStorageAdapter storageAdapter;
|
||||||
private WatermarkInfo watermarkInfo;
|
private WatermarkInfo watermarkInfo;
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ import java.io.File;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 条件旋转Stage
|
* 条件旋转Stage
|
||||||
* 如果是竖图,旋转90度变成横图(便于后续水印处理)
|
* 根据图片需要旋转的角度进行旋转(便于后续水印处理)
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@StageConfig(
|
@StageConfig(
|
||||||
stageId = "rotate",
|
stageId = "rotate",
|
||||||
optionalMode = StageOptionalMode.UNSUPPORT,
|
optionalMode = StageOptionalMode.UNSUPPORT,
|
||||||
description = "竖图旋转90度"
|
description = "根据需要旋转图片"
|
||||||
)
|
)
|
||||||
public class ConditionalRotateStage extends AbstractPipelineStage<PhotoProcessContext> {
|
public class ConditionalRotateStage extends AbstractPipelineStage<PhotoProcessContext> {
|
||||||
|
|
||||||
@@ -43,11 +43,18 @@ public class ConditionalRotateStage extends AbstractPipelineStage<PhotoProcessCo
|
|||||||
return StageResult.failed("当前文件不存在");
|
return StageResult.failed("当前文件不存在");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取需要旋转的角度
|
||||||
|
Integer rotation = context.getImageRotation();
|
||||||
|
if (rotation == null || rotation == 0) {
|
||||||
|
return StageResult.skipped("无需旋转");
|
||||||
|
}
|
||||||
|
|
||||||
File rotatedFile = context.getTempFileManager()
|
File rotatedFile = context.getTempFileManager()
|
||||||
.createTempFile("rotated", ".jpg");
|
.createTempFile("rotated", ".jpg");
|
||||||
|
|
||||||
log.debug("竖图旋转90度: {} -> {}", currentFile.getName(), rotatedFile.getName());
|
// 根据实际角度进行旋转
|
||||||
ImageUtils.rotateImage90(currentFile, rotatedFile);
|
log.debug("旋转图片{}度: {} -> {}", rotation, currentFile.getName(), rotatedFile.getName());
|
||||||
|
rotateByDegree(currentFile, rotatedFile, rotation);
|
||||||
|
|
||||||
if (!rotatedFile.exists()) {
|
if (!rotatedFile.exists()) {
|
||||||
return StageResult.failed("旋转后的文件未生成");
|
return StageResult.failed("旋转后的文件未生成");
|
||||||
@@ -57,12 +64,31 @@ public class ConditionalRotateStage extends AbstractPipelineStage<PhotoProcessCo
|
|||||||
context.setNeedRotation(true);
|
context.setNeedRotation(true);
|
||||||
context.setOffsetLeft(OFFSET_LEFT_FOR_PORTRAIT);
|
context.setOffsetLeft(OFFSET_LEFT_FOR_PORTRAIT);
|
||||||
|
|
||||||
log.info("竖图已旋转90度, offsetLeft={}", OFFSET_LEFT_FOR_PORTRAIT);
|
log.info("图片已旋转{}度, offsetLeft={}", rotation, OFFSET_LEFT_FOR_PORTRAIT);
|
||||||
return StageResult.success("已旋转90度");
|
return StageResult.success("已旋转" + rotation + "度");
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("图片旋转失败", e);
|
log.error("图片旋转失败", e);
|
||||||
return StageResult.failed("旋转失败: " + e.getMessage(), e);
|
return StageResult.failed("旋转失败: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据角度旋转图片
|
||||||
|
*/
|
||||||
|
private void rotateByDegree(File input, File output, int degree) throws Exception {
|
||||||
|
switch (degree) {
|
||||||
|
case 90:
|
||||||
|
ImageUtils.rotateImage90(input, output);
|
||||||
|
break;
|
||||||
|
case 180:
|
||||||
|
ImageUtils.rotateImage180(input, output);
|
||||||
|
break;
|
||||||
|
case 270:
|
||||||
|
ImageUtils.rotateImage270(input, output);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("不支持的旋转角度: " + degree);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.ycwl.basic.image.pipeline.stages;
|
package com.ycwl.basic.image.pipeline.stages;
|
||||||
|
|
||||||
|
import com.ycwl.basic.image.pipeline.enums.ImageSource;
|
||||||
import com.ycwl.basic.model.Crop;
|
import com.ycwl.basic.model.Crop;
|
||||||
import com.ycwl.basic.image.pipeline.annotation.StageConfig;
|
import com.ycwl.basic.image.pipeline.annotation.StageConfig;
|
||||||
import com.ycwl.basic.image.pipeline.core.AbstractPipelineStage;
|
import com.ycwl.basic.image.pipeline.core.AbstractPipelineStage;
|
||||||
@@ -45,6 +46,11 @@ public class ImageOrientationStage extends AbstractPipelineStage<PhotoProcessCon
|
|||||||
boolean isLandscape = detectOrientation(currentFile, context.getCrop());
|
boolean isLandscape = detectOrientation(currentFile, context.getCrop());
|
||||||
context.setLandscape(isLandscape);
|
context.setLandscape(isLandscape);
|
||||||
|
|
||||||
|
// 保存 rotation 信息到 context,方便后续 Stage 使用
|
||||||
|
if (context.getCrop() != null && context.getCrop().getRotation() != null) {
|
||||||
|
context.setImageRotation(context.getCrop().getRotation());
|
||||||
|
}
|
||||||
|
|
||||||
String orientation = isLandscape ? "横图" : "竖图";
|
String orientation = isLandscape ? "横图" : "竖图";
|
||||||
log.info("图片方向检测: {}", orientation);
|
log.info("图片方向检测: {}", orientation);
|
||||||
|
|
||||||
@@ -58,20 +64,28 @@ public class ImageOrientationStage extends AbstractPipelineStage<PhotoProcessCon
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 检测图片方向
|
* 检测图片方向
|
||||||
* 优先使用Crop的rotation字段,其次使用图片宽高判断
|
* 综合考虑物理分辨率和rotation字段
|
||||||
|
* rotation表示"需要旋转多少度照片才能正确显示"
|
||||||
*/
|
*/
|
||||||
private boolean detectOrientation(File imageFile, Crop crop) {
|
private boolean detectOrientation(File imageFile, Crop crop) {
|
||||||
if (crop != null && crop.getRotation() != null) {
|
|
||||||
int rotation = crop.getRotation();
|
|
||||||
boolean isLandscape = (rotation == 90 || rotation == 270);
|
|
||||||
log.debug("从Crop.rotation判断方向: rotation={}, isLandscape={}", rotation, isLandscape);
|
|
||||||
return isLandscape;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
boolean isLandscape = ImageUtils.isLandscape(imageFile);
|
// 先获取物理分辨率方向
|
||||||
log.debug("从图片尺寸判断方向: isLandscape={}", isLandscape);
|
boolean physicalLandscape = ImageUtils.isLandscape(imageFile);
|
||||||
return isLandscape;
|
|
||||||
|
// 如果有rotation信息,需要综合判断
|
||||||
|
if (crop != null && crop.getRotation() != null) {
|
||||||
|
int rotation = crop.getRotation();
|
||||||
|
// rotation=90/270 会翻转方向
|
||||||
|
boolean isLandscape = (rotation == 90 || rotation == 270) ? !physicalLandscape : physicalLandscape;
|
||||||
|
log.debug("综合判断方向: physicalLandscape={}, rotation={}, finalLandscape={}",
|
||||||
|
physicalLandscape, rotation, isLandscape);
|
||||||
|
return isLandscape;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 没有rotation信息,直接使用物理分辨率
|
||||||
|
log.debug("从图片尺寸判断方向: isLandscape={}", physicalLandscape);
|
||||||
|
return physicalLandscape;
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("从图片尺寸判断方向失败,默认为横图: {}", e.getMessage());
|
log.warn("从图片尺寸判断方向失败,默认为横图: {}", e.getMessage());
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.ycwl.basic.image.pipeline.stages;
|
||||||
|
|
||||||
|
import com.ycwl.basic.image.pipeline.core.AbstractPipelineStage;
|
||||||
|
import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
|
||||||
|
import com.ycwl.basic.image.pipeline.core.StageResult;
|
||||||
|
|
||||||
|
public class NoOpStage extends AbstractPipelineStage<PhotoProcessContext> {
|
||||||
|
@Override
|
||||||
|
protected StageResult doExecute(PhotoProcessContext context) {
|
||||||
|
return StageResult.skipped("无操作");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "NoOpStage";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,7 +36,7 @@ import java.util.concurrent.TimeUnit;
|
|||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
@EnableScheduling
|
@EnableScheduling
|
||||||
@Profile("prod")
|
//@Profile("prod")
|
||||||
public class DeviceVideoContinuityCheckTask {
|
public class DeviceVideoContinuityCheckTask {
|
||||||
|
|
||||||
private static final String REDIS_KEY_PREFIX = "device:video:continuity:";
|
private static final String REDIS_KEY_PREFIX = "device:video:continuity:";
|
||||||
|
|||||||
@@ -4,6 +4,12 @@ import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
|
|||||||
import com.ycwl.basic.image.pipeline.core.StageResult;
|
import com.ycwl.basic.image.pipeline.core.StageResult;
|
||||||
import com.ycwl.basic.image.pipeline.enums.ImageType;
|
import com.ycwl.basic.image.pipeline.enums.ImageType;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
@@ -81,4 +87,174 @@ class ConditionalRotateStageTest {
|
|||||||
assertTrue(result.isFailed());
|
assertTrue(result.isFailed());
|
||||||
assertEquals("当前文件不存在", result.getMessage());
|
assertEquals("当前文件不存在", result.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试旋转90度
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testExecute_Rotate90(@TempDir Path tempDir) throws Exception {
|
||||||
|
ConditionalRotateStage stage = new ConditionalRotateStage();
|
||||||
|
|
||||||
|
File testFile = createTestImage(tempDir, "test.jpg", 1080, 1920);
|
||||||
|
|
||||||
|
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||||
|
.processId("test-rotate-90")
|
||||||
|
.originalUrl("https://example.com/test.jpg")
|
||||||
|
.scenicId(123L)
|
||||||
|
.imageType(ImageType.NORMAL_PHOTO)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
context.setOriginalFile(testFile);
|
||||||
|
context.setLandscape(false);
|
||||||
|
context.setImageRotation(90);
|
||||||
|
|
||||||
|
StageResult result = stage.execute(context);
|
||||||
|
|
||||||
|
assertTrue(result.isSuccess());
|
||||||
|
assertEquals("已旋转90度", result.getMessage());
|
||||||
|
assertTrue(context.isNeedRotation());
|
||||||
|
assertNotNull(context.getCurrentFile());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试旋转180度
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testExecute_Rotate180(@TempDir Path tempDir) throws Exception {
|
||||||
|
ConditionalRotateStage stage = new ConditionalRotateStage();
|
||||||
|
|
||||||
|
File testFile = createTestImage(tempDir, "test.jpg", 1080, 1920);
|
||||||
|
|
||||||
|
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||||
|
.processId("test-rotate-180")
|
||||||
|
.originalUrl("https://example.com/test.jpg")
|
||||||
|
.scenicId(123L)
|
||||||
|
.imageType(ImageType.NORMAL_PHOTO)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
context.setOriginalFile(testFile);
|
||||||
|
context.setLandscape(false);
|
||||||
|
context.setImageRotation(180);
|
||||||
|
|
||||||
|
StageResult result = stage.execute(context);
|
||||||
|
|
||||||
|
assertTrue(result.isSuccess());
|
||||||
|
assertEquals("已旋转180度", result.getMessage());
|
||||||
|
assertTrue(context.isNeedRotation());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试旋转270度
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testExecute_Rotate270(@TempDir Path tempDir) throws Exception {
|
||||||
|
ConditionalRotateStage stage = new ConditionalRotateStage();
|
||||||
|
|
||||||
|
File testFile = createTestImage(tempDir, "test.jpg", 1080, 1920);
|
||||||
|
|
||||||
|
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||||
|
.processId("test-rotate-270")
|
||||||
|
.originalUrl("https://example.com/test.jpg")
|
||||||
|
.scenicId(123L)
|
||||||
|
.imageType(ImageType.NORMAL_PHOTO)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
context.setOriginalFile(testFile);
|
||||||
|
context.setLandscape(false);
|
||||||
|
context.setImageRotation(270);
|
||||||
|
|
||||||
|
StageResult result = stage.execute(context);
|
||||||
|
|
||||||
|
assertTrue(result.isSuccess());
|
||||||
|
assertEquals("已旋转270度", result.getMessage());
|
||||||
|
assertTrue(context.isNeedRotation());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试无需旋转(rotation=0)
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testExecute_NoRotationNeeded(@TempDir Path tempDir) throws Exception {
|
||||||
|
ConditionalRotateStage stage = new ConditionalRotateStage();
|
||||||
|
|
||||||
|
File testFile = createTestImage(tempDir, "test.jpg", 1080, 1920);
|
||||||
|
|
||||||
|
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||||
|
.processId("test-no-rotation")
|
||||||
|
.originalUrl("https://example.com/test.jpg")
|
||||||
|
.scenicId(123L)
|
||||||
|
.imageType(ImageType.NORMAL_PHOTO)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
context.setOriginalFile(testFile);
|
||||||
|
context.setLandscape(false);
|
||||||
|
context.setImageRotation(0);
|
||||||
|
|
||||||
|
StageResult result = stage.execute(context);
|
||||||
|
|
||||||
|
assertTrue(result.isSkipped());
|
||||||
|
assertEquals("无需旋转", result.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试无需旋转(rotation=null)
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testExecute_NullRotation(@TempDir Path tempDir) throws Exception {
|
||||||
|
ConditionalRotateStage stage = new ConditionalRotateStage();
|
||||||
|
|
||||||
|
File testFile = createTestImage(tempDir, "test.jpg", 1080, 1920);
|
||||||
|
|
||||||
|
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||||
|
.processId("test-null-rotation")
|
||||||
|
.originalUrl("https://example.com/test.jpg")
|
||||||
|
.scenicId(123L)
|
||||||
|
.imageType(ImageType.NORMAL_PHOTO)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
context.setOriginalFile(testFile);
|
||||||
|
context.setLandscape(false);
|
||||||
|
context.setImageRotation(null);
|
||||||
|
|
||||||
|
StageResult result = stage.execute(context);
|
||||||
|
|
||||||
|
assertTrue(result.isSkipped());
|
||||||
|
assertEquals("无需旋转", result.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试不支持的旋转角度
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testExecute_UnsupportedRotationAngle(@TempDir Path tempDir) throws Exception {
|
||||||
|
ConditionalRotateStage stage = new ConditionalRotateStage();
|
||||||
|
|
||||||
|
File testFile = createTestImage(tempDir, "test.jpg", 1080, 1920);
|
||||||
|
|
||||||
|
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||||
|
.processId("test-unsupported-rotation")
|
||||||
|
.originalUrl("https://example.com/test.jpg")
|
||||||
|
.scenicId(123L)
|
||||||
|
.imageType(ImageType.NORMAL_PHOTO)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
context.setOriginalFile(testFile);
|
||||||
|
context.setLandscape(false);
|
||||||
|
context.setImageRotation(45);
|
||||||
|
|
||||||
|
StageResult result = stage.execute(context);
|
||||||
|
|
||||||
|
assertTrue(result.isFailed());
|
||||||
|
assertTrue(result.getMessage().contains("不支持的旋转角度"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建测试用的图片文件
|
||||||
|
*/
|
||||||
|
private File createTestImage(Path tempDir, String filename, int width, int height) throws Exception {
|
||||||
|
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||||
|
File file = tempDir.resolve(filename).toFile();
|
||||||
|
ImageIO.write(image, "jpg", file);
|
||||||
|
return file;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,15 @@ package com.ycwl.basic.image.pipeline.stages;
|
|||||||
import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
|
import com.ycwl.basic.image.pipeline.core.PhotoProcessContext;
|
||||||
import com.ycwl.basic.image.pipeline.core.StageResult;
|
import com.ycwl.basic.image.pipeline.core.StageResult;
|
||||||
import com.ycwl.basic.image.pipeline.enums.ImageType;
|
import com.ycwl.basic.image.pipeline.enums.ImageType;
|
||||||
|
import com.ycwl.basic.model.Crop;
|
||||||
|
import com.ycwl.basic.utils.ImageUtils;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
@@ -72,4 +80,162 @@ class ImageOrientationStageTest {
|
|||||||
assertTrue(result.isFailed());
|
assertTrue(result.isFailed());
|
||||||
assertEquals("当前文件不存在", result.getMessage());
|
assertEquals("当前文件不存在", result.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试综合判断:物理横图 + rotation=0 → 横图
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testExecute_LandscapeImage_NoRotation(@TempDir Path tempDir) throws Exception {
|
||||||
|
ImageOrientationStage stage = new ImageOrientationStage();
|
||||||
|
|
||||||
|
// 创建物理横图 (1920x1080)
|
||||||
|
File landscapeFile = createTestImage(tempDir, "landscape.jpg", 1920, 1080);
|
||||||
|
|
||||||
|
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||||
|
.processId("test-landscape-no-rotation")
|
||||||
|
.originalUrl("https://example.com/test.jpg")
|
||||||
|
.scenicId(123L)
|
||||||
|
.imageType(ImageType.NORMAL_PHOTO)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
context.setOriginalFile(landscapeFile);
|
||||||
|
|
||||||
|
// 无 rotation 信息
|
||||||
|
Crop crop = new Crop();
|
||||||
|
crop.setRotation(0);
|
||||||
|
context.setCrop(crop);
|
||||||
|
|
||||||
|
StageResult result = stage.execute(context);
|
||||||
|
|
||||||
|
assertTrue(result.isSuccess());
|
||||||
|
assertTrue(context.isLandscape(), "应该判定为横图");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试综合判断:物理横图 + rotation=90 → 竖图
|
||||||
|
* 场景:一张横向分辨率的图片,但内容是竖着的(需要旋转90度才能正确显示)
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testExecute_LandscapeImage_Rotation90_ShouldBePortrait(@TempDir Path tempDir) throws Exception {
|
||||||
|
ImageOrientationStage stage = new ImageOrientationStage();
|
||||||
|
|
||||||
|
// 创建物理横图 (1920x1080),但内容实际是竖着的
|
||||||
|
File landscapeFile = createTestImage(tempDir, "landscape_rotated.jpg", 1920, 1080);
|
||||||
|
|
||||||
|
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||||
|
.processId("test-landscape-rotation90")
|
||||||
|
.originalUrl("https://example.com/test.jpg")
|
||||||
|
.scenicId(123L)
|
||||||
|
.imageType(ImageType.NORMAL_PHOTO)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
context.setOriginalFile(landscapeFile);
|
||||||
|
|
||||||
|
// rotation=90 表示需要旋转90度才能正确显示
|
||||||
|
Crop crop = new Crop();
|
||||||
|
crop.setRotation(90);
|
||||||
|
context.setCrop(crop);
|
||||||
|
|
||||||
|
StageResult result = stage.execute(context);
|
||||||
|
|
||||||
|
assertTrue(result.isSuccess());
|
||||||
|
assertFalse(context.isLandscape(), "旋转90度后应该是竖图");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试综合判断:物理竖图 + rotation=90 → 横图
|
||||||
|
* 场景:一张竖向分辨率的图片,但内容是横着的(需要旋转90度才能正确显示)
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testExecute_PortraitImage_Rotation90_ShouldBeLandscape(@TempDir Path tempDir) throws Exception {
|
||||||
|
ImageOrientationStage stage = new ImageOrientationStage();
|
||||||
|
|
||||||
|
// 创建物理竖图 (1080x1920),但内容实际是横着的
|
||||||
|
File portraitFile = createTestImage(tempDir, "portrait_rotated.jpg", 1080, 1920);
|
||||||
|
|
||||||
|
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||||
|
.processId("test-portrait-rotation90")
|
||||||
|
.originalUrl("https://example.com/test.jpg")
|
||||||
|
.scenicId(123L)
|
||||||
|
.imageType(ImageType.NORMAL_PHOTO)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
context.setOriginalFile(portraitFile);
|
||||||
|
|
||||||
|
// rotation=90 表示需要旋转90度才能正确显示
|
||||||
|
Crop crop = new Crop();
|
||||||
|
crop.setRotation(90);
|
||||||
|
context.setCrop(crop);
|
||||||
|
|
||||||
|
StageResult result = stage.execute(context);
|
||||||
|
|
||||||
|
assertTrue(result.isSuccess());
|
||||||
|
assertTrue(context.isLandscape(), "旋转90度后应该是横图");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试综合判断:物理横图 + rotation=270 → 竖图
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testExecute_LandscapeImage_Rotation270_ShouldBePortrait(@TempDir Path tempDir) throws Exception {
|
||||||
|
ImageOrientationStage stage = new ImageOrientationStage();
|
||||||
|
|
||||||
|
File landscapeFile = createTestImage(tempDir, "landscape_270.jpg", 1920, 1080);
|
||||||
|
|
||||||
|
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||||
|
.processId("test-landscape-rotation270")
|
||||||
|
.originalUrl("https://example.com/test.jpg")
|
||||||
|
.scenicId(123L)
|
||||||
|
.imageType(ImageType.NORMAL_PHOTO)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
context.setOriginalFile(landscapeFile);
|
||||||
|
|
||||||
|
Crop crop = new Crop();
|
||||||
|
crop.setRotation(270);
|
||||||
|
context.setCrop(crop);
|
||||||
|
|
||||||
|
StageResult result = stage.execute(context);
|
||||||
|
|
||||||
|
assertTrue(result.isSuccess());
|
||||||
|
assertFalse(context.isLandscape(), "旋转270度后应该是竖图");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试综合判断:物理横图 + rotation=180 → 横图
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
void testExecute_LandscapeImage_Rotation180_ShouldBeLandscape(@TempDir Path tempDir) throws Exception {
|
||||||
|
ImageOrientationStage stage = new ImageOrientationStage();
|
||||||
|
|
||||||
|
File landscapeFile = createTestImage(tempDir, "landscape_180.jpg", 1920, 1080);
|
||||||
|
|
||||||
|
PhotoProcessContext context = PhotoProcessContext.builder()
|
||||||
|
.processId("test-landscape-rotation180")
|
||||||
|
.originalUrl("https://example.com/test.jpg")
|
||||||
|
.scenicId(123L)
|
||||||
|
.imageType(ImageType.NORMAL_PHOTO)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
context.setOriginalFile(landscapeFile);
|
||||||
|
|
||||||
|
Crop crop = new Crop();
|
||||||
|
crop.setRotation(180);
|
||||||
|
context.setCrop(crop);
|
||||||
|
|
||||||
|
StageResult result = stage.execute(context);
|
||||||
|
|
||||||
|
assertTrue(result.isSuccess());
|
||||||
|
assertTrue(context.isLandscape(), "旋转180度后仍然是横图");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建测试用的图片文件
|
||||||
|
*/
|
||||||
|
private File createTestImage(Path tempDir, String filename, int width, int height) throws Exception {
|
||||||
|
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
|
||||||
|
File file = tempDir.resolve(filename).toFile();
|
||||||
|
ImageIO.write(image, "jpg", file);
|
||||||
|
return file;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user