diff --git a/source/image-handler/image-handler.ts b/source/image-handler/image-handler.ts index 6dc7899b7..d65e524c4 100644 --- a/source/image-handler/image-handler.ts +++ b/source/image-handler/image-handler.ts @@ -728,6 +728,8 @@ export class ImageHandler { return "png"; case ImageFormatTypes.WEBP: return "webp"; + case ImageFormatTypes.TIF: + return "tiff"; case ImageFormatTypes.TIFF: return "tiff"; case ImageFormatTypes.HEIF: diff --git a/source/image-handler/image-request.ts b/source/image-handler/image-request.ts index a8d729652..dbf75ec19 100644 --- a/source/image-handler/image-request.ts +++ b/source/image-handler/image-request.ts @@ -26,6 +26,7 @@ import { QueryParamMapper } from "./query-param-mapper"; dayjs.extend(customParseFormat); dayjs.extend(utc); + type OriginalImageInfo = Partial<{ contentType: string; expires: string; @@ -73,13 +74,16 @@ export class ImageRequest { ImageFormatTypes.JPEG, ImageFormatTypes.PNG, ImageFormatTypes.WEBP, + ImageFormatTypes.TIF, ImageFormatTypes.TIFF, ImageFormatTypes.HEIF, ImageFormatTypes.GIF, ImageFormatTypes.AVIF, ]; - imageRequestInfo.contentType = `image/${imageRequestInfo.outputFormat}`; + imageRequestInfo.contentType = imageRequestInfo.outputFormat === ImageFormatTypes.TIF + ? ContentTypes.TIFF + : `image/${imageRequestInfo.outputFormat}`; if ( requestType.includes(imageRequestInfo.requestType) && acceptedValues.includes(imageRequestInfo.outputFormat) diff --git a/source/image-handler/lib/enums.ts b/source/image-handler/lib/enums.ts index 41beb7585..f9cc72a2d 100644 --- a/source/image-handler/lib/enums.ts +++ b/source/image-handler/lib/enums.ts @@ -22,6 +22,7 @@ export enum ImageFormatTypes { JPEG = "jpeg", PNG = "png", WEBP = "webp", + TIF = "tif", TIFF = "tiff", HEIF = "heif", HEIC = "heic", diff --git a/source/image-handler/test/image-handler/format.spec.ts b/source/image-handler/test/image-handler/format.spec.ts index cef42b631..de1af6e3c 100644 --- a/source/image-handler/test/image-handler/format.spec.ts +++ b/source/image-handler/test/image-handler/format.spec.ts @@ -109,6 +109,29 @@ describe("modifyImageOutput", () => { expect(resultFormat).toEqual(ImageFormatTypes.JPEG); }); + it("Should return an image in TIFF format when outputFormat is TIF", async () => { + // Arrange + const request: ImageRequestInfo = { + requestType: RequestTypes.DEFAULT, + bucket: "sample-bucket", + key: "sample-image-001.png", + edits: { grayscale: true, flip: true }, + outputFormat: ImageFormatTypes.TIF, + originalImage: image, + }; + const imageHandler = new ImageHandler(s3Client, rekognitionClient); + const sharpImage = sharp(request.originalImage, { failOnError: false }).withMetadata(); + const toFormatSpy = jest.spyOn(sharp.prototype, "toFormat"); + const result = await imageHandler["modifyImageOutput"](sharpImage, request).toBuffer(); + + // Act + const resultFormat = (await sharp(result).metadata()).format; + + // Assert + expect(toFormatSpy).toHaveBeenCalledWith("tiff"); + expect(resultFormat).toEqual(ImageFormatTypes.TIFF); + }); + it("Should return an image in the same format when outputFormat is not provided", async () => { // Arrange const request: ImageRequestInfo = { diff --git a/source/image-handler/test/image-request/setup.spec.ts b/source/image-handler/test/image-request/setup.spec.ts index 3c45017dd..4fe8b9264 100644 --- a/source/image-handler/test/image-request/setup.spec.ts +++ b/source/image-handler/test/image-request/setup.spec.ts @@ -132,6 +132,37 @@ describe("setup", () => { expect(imageRequestInfo).toEqual(expectedResult); }); + it("Should pass when a default image request with TIF format is provided and populate the ImageRequest object with the proper values", async () => { + // Arrange + const event = { + path: "/eyJidWNrZXQiOiJ2YWxpZEJ1Y2tldCIsImtleSI6InZhbGlkS2V5IiwiZWRpdHMiOnsidG9Gb3JtYXQiOiJ0aWYifX0=", + }; + process.env.SOURCE_BUCKETS = "validBucket, validBucket2"; + + // Mock + mockS3Commands.getObject.mockResolvedValue({ Body: mockImageBody }); + + // Act + const imageRequest = new ImageRequest(s3Client, secretProvider); + const imageRequestInfo = await imageRequest.setup(event); + const expectedResult = { + requestType: "Default", + bucket: "validBucket", + key: "validKey", + edits: { toFormat: "tif" }, + outputFormat: "tif", + originalImage: mockImage, + cacheControl: "max-age=31536000,public", + contentType: "image/tiff", + }; + // Assert + expect(mockS3Commands.getObject).toHaveBeenCalledWith({ + Bucket: "validBucket", + Key: "validKey", + }); + expect(imageRequestInfo).toEqual(expectedResult); + }); + it("Should pass when a thumbor image request is provided and populate the ImageRequest object with the proper values", async () => { // Arrange const event = { path: "/filters:grayscale()/test-image-001.jpg" }; diff --git a/source/image-handler/test/thumbor-mapper/filter.spec.ts b/source/image-handler/test/thumbor-mapper/filter.spec.ts index bdbef4dbd..13a2f99ea 100644 --- a/source/image-handler/test/thumbor-mapper/filter.spec.ts +++ b/source/image-handler/test/thumbor-mapper/filter.spec.ts @@ -274,6 +274,20 @@ describe("filter", () => { expect(edits).toEqual(expectedResult); }); + it("Should pass if the filter is successfully translated from Thumbor:quality() for TIF", () => { + // Arrange + const edit = "filters:quality(50)"; + const filetype = ImageFormatTypes.TIF; + + // Act + const thumborMapper = new ThumborMapper(); + const edits = thumborMapper.mapFilter(edit, filetype); + + // Assert + const expectedResult = { tiff: { quality: 50 } }; + expect(edits).toEqual(expectedResult); + }); + it("Should pass if the filter is successfully translated from Thumbor:quality()", () => { // Arrange const edit = "filters:quality(50)"; diff --git a/source/image-handler/thumbor-mapper.ts b/source/image-handler/thumbor-mapper.ts index ff39c07c9..d5ebb405c 100644 --- a/source/image-handler/thumbor-mapper.ts +++ b/source/image-handler/thumbor-mapper.ts @@ -200,11 +200,12 @@ export class ThumborMapper { const toSupportedImageFormatType = (format: ImageFormatTypes): ImageFormatTypes => { if ([ImageFormatTypes.JPG, ImageFormatTypes.JPEG].includes(format)) { return ImageFormatTypes.JPEG; + } else if ([ImageFormatTypes.TIF, ImageFormatTypes.TIFF].includes(format)) { + return ImageFormatTypes.TIFF; } else if ( [ ImageFormatTypes.PNG, ImageFormatTypes.WEBP, - ImageFormatTypes.TIFF, ImageFormatTypes.HEIF, ImageFormatTypes.GIF, ImageFormatTypes.AVIF,