You've already forked FrameTour-BE
feat(image): 支持多角度图片旋转及方向判断
- 在 PhotoProcessContext 中新增 imageRotation 字段用于存储旋转角度 - 修改 ConditionalRotateStage 支持 90、180、270 度旋转 - 优化 ImageOrientationStage 综合判断图片方向逻辑 - 新增 NoOpStage 作为空操作阶段占位符 - 解除 DeviceVideoContinuityCheckTask 的生产环境限制 - 添加完整的单元测试覆盖各种旋转场景和边界情况
This commit is contained in:
@@ -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.enums.ImageType;
|
||||
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.*;
|
||||
|
||||
@@ -81,4 +87,174 @@ class ConditionalRotateStageTest {
|
||||
assertTrue(result.isFailed());
|
||||
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.StageResult;
|
||||
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.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.*;
|
||||
|
||||
@@ -72,4 +80,162 @@ class ImageOrientationStageTest {
|
||||
assertTrue(result.isFailed());
|
||||
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