|
@@ -48,7 +48,7 @@ describe('local request-image cache', () => {
|
|
|
const first = await attachments.saveImage({ data: await image(8, 4), mediaType: 'image/png' })
|
|
const first = await attachments.saveImage({ data: await image(8, 4), mediaType: 'image/png' })
|
|
|
const second = await attachments.saveImage({ data: await image(4, 8), mediaType: 'image/png' })
|
|
const second = await attachments.saveImage({ data: await image(4, 8), mediaType: 'image/png' })
|
|
|
const firstStored = await attachments.readImage(first)
|
|
const firstStored = await attachments.readImage(first)
|
|
|
- const policy = { projection: { kind: 'pixel-budget' as const, maxPixels: 1_000 }, maxBytes: 1024 * 1024 }
|
|
|
|
|
|
|
+ const policy = { width: 8, height: 8, maxBytes: 1024 * 1024 }
|
|
|
|
|
|
|
|
const request = await attachments.readImageRequest(first, policy)
|
|
const request = await attachments.readImageRequest(first, policy)
|
|
|
const batch = await Promise.all([first, second].map(
|
|
const batch = await Promise.all([first, second].map(
|
|
@@ -59,56 +59,51 @@ describe('local request-image cache', () => {
|
|
|
expect(batch.map(value => value.attachment.attachmentId)).toEqual([first.attachmentId, second.attachmentId])
|
|
expect(batch.map(value => value.attachment.attachmentId)).toEqual([first.attachmentId, second.attachmentId])
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- it('rejects invalid request policies', async () => {
|
|
|
|
|
|
|
+ it('rejects invalid request targets', async () => {
|
|
|
const attachments = await store()
|
|
const attachments = await store()
|
|
|
const attachment = await attachments.saveImage({ data: await image(8, 4), mediaType: 'image/png' })
|
|
const attachment = await attachments.saveImage({ data: await image(8, 4), mediaType: 'image/png' })
|
|
|
|
|
|
|
|
- await expect(attachments.readImageRequest(attachment, { projection: { kind: 'pixel-budget' as const, maxPixels: 0 }, maxBytes: 100 }))
|
|
|
|
|
- .rejects.toThrow('Image request maxPixels must be a positive integer')
|
|
|
|
|
- await expect(attachments.readImageRequest(attachment, { projection: { kind: 'pixel-budget' as const, maxPixels: 100 }, maxBytes: 0 }))
|
|
|
|
|
|
|
+ await expect(attachments.readImageRequest(attachment, { width: 0, height: 4, maxBytes: 100 }))
|
|
|
|
|
+ .rejects.toThrow('Image request width must be a positive integer')
|
|
|
|
|
+ await expect(attachments.readImageRequest(attachment, { width: 8, height: 1.5, maxBytes: 100 }))
|
|
|
|
|
+ .rejects.toThrow('Image request height must be a positive integer')
|
|
|
|
|
+ await expect(attachments.readImageRequest(attachment, { width: 8, height: 4, maxBytes: 0 }))
|
|
|
.rejects.toThrow('Image request maxBytes must be a positive integer')
|
|
.rejects.toThrow('Image request maxBytes must be a positive integer')
|
|
|
- await expect(attachments.readImageRequest(attachment, { projection: { kind: 'pixel-budget' as const, maxPixels: 100 }, maxDimension: 0, maxBytes: 100 }))
|
|
|
|
|
- .rejects.toThrow('Image request maxDimension must be a positive integer')
|
|
|
|
|
- for (const field of ['patchSize', 'downsampleRatio', 'maxTokens'] as const) {
|
|
|
|
|
- const projection = { kind: 'token-grid' as const, patchSize: 14, downsampleRatio: 3, maxTokens: 1024, [field]: 1.5 }
|
|
|
|
|
- await expect(attachments.readImageRequest(attachment, { projection, maxBytes: 100 }))
|
|
|
|
|
- .rejects.toThrow(`Image request ${field} must be a positive integer`)
|
|
|
|
|
- }
|
|
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- it('projects onto a token grid, caps the long edge, and keys the cache by projection', async () => {
|
|
|
|
|
|
|
+ it('resizes by the long edge to the exact target and keys the cache by target', async () => {
|
|
|
const attachments = await store()
|
|
const attachments = await store()
|
|
|
- const grid = { kind: 'token-grid' as const, patchSize: 14, downsampleRatio: 3, maxTokens: 1024 }
|
|
|
|
|
|
|
+ const maxBytes = 2 * 1024 * 1024
|
|
|
const square = await attachments.saveImage({ data: await image(2048, 2048), mediaType: 'image/png' })
|
|
const square = await attachments.saveImage({ data: await image(2048, 2048), mediaType: 'image/png' })
|
|
|
const small = await attachments.saveImage({ data: await image(800, 800), mediaType: 'image/png' })
|
|
const small = await attachments.saveImage({ data: await image(800, 800), mediaType: 'image/png' })
|
|
|
const thin = await attachments.saveImage({ data: await image(8000, 40), mediaType: 'image/png' })
|
|
const thin = await attachments.saveImage({ data: await image(8000, 40), mediaType: 'image/png' })
|
|
|
const wide = await attachments.saveImage({ data: await image(1920, 1080), mediaType: 'image/png' })
|
|
const wide = await attachments.saveImage({ data: await image(1920, 1080), mediaType: 'image/png' })
|
|
|
const tall = await attachments.saveImage({ data: await image(1080, 1920), mediaType: 'image/png' })
|
|
const tall = await attachments.saveImage({ data: await image(1080, 1920), mediaType: 'image/png' })
|
|
|
|
|
|
|
|
- const squareRequest = await attachments.readImageRequest(square, { projection: grid, maxBytes: 2 * 1024 * 1024 })
|
|
|
|
|
- const smallRequest = await attachments.readImageRequest(small, { projection: grid, maxBytes: 2 * 1024 * 1024 })
|
|
|
|
|
- const thinRequest = await attachments.readImageRequest(thin, { projection: grid, maxDimension: 4096, maxBytes: 2 * 1024 * 1024 })
|
|
|
|
|
- const budgeted = await attachments.readImageRequest(square, { projection: { kind: 'pixel-budget' as const, maxPixels: 1302 * 1302 }, maxBytes: 2 * 1024 * 1024 })
|
|
|
|
|
- const uncapped = await attachments.readImageRequest(thin, { projection: grid, maxBytes: 2 * 1024 * 1024 })
|
|
|
|
|
- const wideRequest = await attachments.readImageRequest(wide, { projection: grid, maxBytes: 2 * 1024 * 1024 })
|
|
|
|
|
- const tallRequest = await attachments.readImageRequest(tall, { projection: grid, maxBytes: 2 * 1024 * 1024 })
|
|
|
|
|
|
|
+ const squareRequest = await attachments.readImageRequest(square, { width: 1302, height: 1302, maxBytes })
|
|
|
|
|
+ const smallRequest = await attachments.readImageRequest(small, { width: 800, height: 800, maxBytes })
|
|
|
|
|
+ const thinRequest = await attachments.readImageRequest(thin, { width: 4096, height: 20, maxBytes })
|
|
|
|
|
+ const wideRequest = await attachments.readImageRequest(wide, { width: 1708, height: 961, maxBytes })
|
|
|
|
|
+ const tallRequest = await attachments.readImageRequest(tall, { width: 961, height: 1708, maxBytes })
|
|
|
|
|
+ const smaller = await attachments.readImageRequest(square, { width: 1024, height: 1024, maxBytes })
|
|
|
|
|
+ const enlarged = await attachments.readImageRequest(thin, { width: 9000, height: 45, maxBytes })
|
|
|
|
|
|
|
|
expect(squareRequest).toMatchObject({ width: 1302, height: 1302, mediaType: 'image/jpeg' })
|
|
expect(squareRequest).toMatchObject({ width: 1302, height: 1302, mediaType: 'image/jpeg' })
|
|
|
expect(smallRequest).toMatchObject({ width: 800, height: 800, mediaType: 'image/png' })
|
|
expect(smallRequest).toMatchObject({ width: 800, height: 800, mediaType: 'image/png' })
|
|
|
expect(smallRequest.data).toEqual((await attachments.readImage(small)).data)
|
|
expect(smallRequest.data).toEqual((await attachments.readImage(small)).data)
|
|
|
expect(thinRequest).toMatchObject({ width: 4096, height: 20 })
|
|
expect(thinRequest).toMatchObject({ width: 4096, height: 20 })
|
|
|
- expect(uncapped).toMatchObject({ width: 8000, height: 40 })
|
|
|
|
|
expect(wideRequest).toMatchObject({ width: 1708, height: 961 })
|
|
expect(wideRequest).toMatchObject({ width: 1708, height: 961 })
|
|
|
expect(tallRequest).toMatchObject({ width: 961, height: 1708 })
|
|
expect(tallRequest).toMatchObject({ width: 961, height: 1708 })
|
|
|
- expect(budgeted.variantId).not.toBe(squareRequest.variantId)
|
|
|
|
|
- expect(uncapped.variantId).not.toBe(thinRequest.variantId)
|
|
|
|
|
|
|
+ expect(enlarged).toMatchObject({ width: 8000, height: 40 })
|
|
|
|
|
+ expect(smaller.variantId).not.toBe(squareRequest.variantId)
|
|
|
|
|
+ expect(enlarged.variantId).not.toBe(thinRequest.variantId)
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
it('keeps the smallest ladder output when the encoded-byte target is unreachable', async () => {
|
|
it('keeps the smallest ladder output when the encoded-byte target is unreachable', async () => {
|
|
|
const attachments = await store()
|
|
const attachments = await store()
|
|
|
const attachment = await attachments.saveImage({ data: await image(1, 1), mediaType: 'image/png' })
|
|
const attachment = await attachments.saveImage({ data: await image(1, 1), mediaType: 'image/png' })
|
|
|
|
|
|
|
|
- const request = await attachments.readImageRequest(attachment, { projection: { kind: 'pixel-budget' as const, maxPixels: 1 }, maxBytes: 1 })
|
|
|
|
|
|
|
+ const request = await attachments.readImageRequest(attachment, { width: 1, height: 1, maxBytes: 1 })
|
|
|
|
|
|
|
|
expect(request.mediaType).toBe('image/jpeg')
|
|
expect(request.mediaType).toBe('image/jpeg')
|
|
|
expect(request.bytes).toBeGreaterThan(1)
|
|
expect(request.bytes).toBeGreaterThan(1)
|
|
@@ -118,7 +113,7 @@ describe('local request-image cache', () => {
|
|
|
it('regenerates invalid, oversized, incompatible, or mismatched cached variants', async () => {
|
|
it('regenerates invalid, oversized, incompatible, or mismatched cached variants', async () => {
|
|
|
const attachments = await store()
|
|
const attachments = await store()
|
|
|
const attachment = await attachments.saveImage({ data: await image(64, 32), mediaType: 'image/png' })
|
|
const attachment = await attachments.saveImage({ data: await image(64, 32), mediaType: 'image/png' })
|
|
|
- const policy = { projection: { kind: 'pixel-budget' as const, maxPixels: 16 * 16 }, maxBytes: 4_096 }
|
|
|
|
|
|
|
+ const policy = { width: 22, height: 11, maxBytes: 4_096 }
|
|
|
const initial = await attachments.readImageRequest(attachment, policy)
|
|
const initial = await attachments.readImageRequest(attachment, policy)
|
|
|
const hash = String(initial.variantId).slice('sha256:'.length)
|
|
const hash = String(initial.variantId).slice('sha256:'.length)
|
|
|
const path = join(attachments.root, 'request-images', hash.slice(0, 2), hash)
|
|
const path = join(attachments.root, 'request-images', hash.slice(0, 2), hash)
|
|
@@ -167,10 +162,10 @@ describe('local request-image cache', () => {
|
|
|
data: await image(2048, 1024), mediaType: 'image/png', name: 'wide.png',
|
|
data: await image(2048, 1024), mediaType: 'image/png', name: 'wide.png',
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
- const squareRequest = await attachments.readImageRequest(square, { projection: { kind: 'pixel-budget' as const, maxPixels: 640_000 }, maxBytes: 1024 * 1024 })
|
|
|
|
|
- const wideRequest = await attachments.readImageRequest(wide, { projection: { kind: 'pixel-budget' as const, maxPixels: 640_000 }, maxBytes: 1024 * 1024 })
|
|
|
|
|
- const repeated = await attachments.readImageRequest(wide, { projection: { kind: 'pixel-budget' as const, maxPixels: 640_000 }, maxBytes: 1024 * 1024 })
|
|
|
|
|
- const low = await attachments.readImageRequest(wide, { projection: { kind: 'pixel-budget' as const, maxPixels: 512 * 512 }, maxBytes: 1024 * 1024 })
|
|
|
|
|
|
|
+ const squareRequest = await attachments.readImageRequest(square, { width: 800, height: 800, maxBytes: 1024 * 1024 })
|
|
|
|
|
+ const wideRequest = await attachments.readImageRequest(wide, { width: 1130, height: 565, maxBytes: 1024 * 1024 })
|
|
|
|
|
+ const repeated = await attachments.readImageRequest(wide, { width: 1130, height: 565, maxBytes: 1024 * 1024 })
|
|
|
|
|
+ const low = await attachments.readImageRequest(wide, { width: 724, height: 362, maxBytes: 1024 * 1024 })
|
|
|
|
|
|
|
|
expect(squareRequest).toMatchObject({ width: 800, height: 800 })
|
|
expect(squareRequest).toMatchObject({ width: 800, height: 800 })
|
|
|
expect(wideRequest).toMatchObject({ width: 1130, height: 565 })
|
|
expect(wideRequest).toMatchObject({ width: 1130, height: 565 })
|
|
@@ -210,8 +205,8 @@ describe('local request-image cache', () => {
|
|
|
const photo = await attachments.saveImage({ data: photoSource, mediaType: 'image/png' })
|
|
const photo = await attachments.saveImage({ data: photoSource, mediaType: 'image/png' })
|
|
|
const alpha = await attachments.saveImage({ data: alphaSource, mediaType: 'image/png' })
|
|
const alpha = await attachments.saveImage({ data: alphaSource, mediaType: 'image/png' })
|
|
|
|
|
|
|
|
- const photoRequest = await attachments.readImageRequest(photo, { projection: { kind: 'pixel-budget' as const, maxPixels: 128 * 128 }, maxBytes: 1024 * 1024 })
|
|
|
|
|
- const alphaRequest = await attachments.readImageRequest(alpha, { projection: { kind: 'pixel-budget' as const, maxPixels: 128 * 128 }, maxBytes: 4_096 })
|
|
|
|
|
|
|
+ const photoRequest = await attachments.readImageRequest(photo, { width: 128, height: 128, maxBytes: 1024 * 1024 })
|
|
|
|
|
+ const alphaRequest = await attachments.readImageRequest(alpha, { width: 128, height: 128, maxBytes: 4_096 })
|
|
|
|
|
|
|
|
expect(photoRequest.mediaType).toBe('image/jpeg')
|
|
expect(photoRequest.mediaType).toBe('image/jpeg')
|
|
|
expect(alphaRequest.mediaType).toBe('image/webp')
|
|
expect(alphaRequest.mediaType).toBe('image/webp')
|
|
@@ -227,7 +222,7 @@ describe('local request-image cache', () => {
|
|
|
}).toColourspace('rgb16').png().toBuffer())
|
|
}).toColourspace('rgb16').png().toBuffer())
|
|
|
const attachment = await attachments.saveImage({ data: source, mediaType: 'image/png' })
|
|
const attachment = await attachments.saveImage({ data: source, mediaType: 'image/png' })
|
|
|
|
|
|
|
|
- const request = await attachments.readImageRequest(attachment, { projection: { kind: 'pixel-budget' as const, maxPixels: 16 * 16 }, maxBytes: 1024 * 1024 })
|
|
|
|
|
|
|
+ const request = await attachments.readImageRequest(attachment, { width: 22, height: 11, maxBytes: 1024 * 1024 })
|
|
|
|
|
|
|
|
expect(request.bytes).toBeLessThanOrEqual(1024 * 1024)
|
|
expect(request.bytes).toBeLessThanOrEqual(1024 * 1024)
|
|
|
expect(request.width * request.height).toBeLessThanOrEqual(16 * 16)
|
|
expect(request.width * request.height).toBeLessThanOrEqual(16 * 16)
|
|
@@ -241,7 +236,7 @@ describe('local request-image cache', () => {
|
|
|
const source = await complexOpaqueAlphaImage(64, 32)
|
|
const source = await complexOpaqueAlphaImage(64, 32)
|
|
|
const attachment = await attachments.saveImage({ data: source, mediaType: 'image/png' })
|
|
const attachment = await attachments.saveImage({ data: source, mediaType: 'image/png' })
|
|
|
|
|
|
|
|
- const request = await attachments.readImageRequest(attachment, { projection: { kind: 'pixel-budget' as const, maxPixels: 16 * 16 }, maxBytes: 1024 * 1024 })
|
|
|
|
|
|
|
+ const request = await attachments.readImageRequest(attachment, { width: 22, height: 11, maxBytes: 1024 * 1024 })
|
|
|
|
|
|
|
|
expect(request.mediaType).toBe('image/webp')
|
|
expect(request.mediaType).toBe('image/webp')
|
|
|
await expect(sharp(request.data).metadata()).resolves.toMatchObject({ hasAlpha: false })
|
|
await expect(sharp(request.data).metadata()).resolves.toMatchObject({ hasAlpha: false })
|
|
@@ -263,7 +258,7 @@ describe('local request-image cache', () => {
|
|
|
}).png().toBuffer())
|
|
}).png().toBuffer())
|
|
|
const attachment = await attachments.saveImage({ data: source, mediaType: 'image/png' })
|
|
const attachment = await attachments.saveImage({ data: source, mediaType: 'image/png' })
|
|
|
|
|
|
|
|
- const request = await attachments.readImageRequest(attachment, { projection: { kind: 'pixel-budget' as const, maxPixels: 640_000 }, maxBytes: 1024 * 1024 })
|
|
|
|
|
|
|
+ const request = await attachments.readImageRequest(attachment, { width: 800, height: 800, maxBytes: 1024 * 1024 })
|
|
|
|
|
|
|
|
expect(request).toMatchObject({ width: 800, height: 800 })
|
|
expect(request).toMatchObject({ width: 800, height: 800 })
|
|
|
expect(request.bytes).toBeLessThanOrEqual(1024 * 1024)
|
|
expect(request.bytes).toBeLessThanOrEqual(1024 * 1024)
|
|
@@ -276,7 +271,7 @@ describe('local request-image cache', () => {
|
|
|
})
|
|
})
|
|
|
const run = vi.spyOn(CompressionLimiter.prototype, 'run')
|
|
const run = vi.spyOn(CompressionLimiter.prototype, 'run')
|
|
|
const controller = new AbortController()
|
|
const controller = new AbortController()
|
|
|
- const policy = { projection: { kind: 'pixel-budget' as const, maxPixels: 640_000 }, maxBytes: 1024 * 1024 }
|
|
|
|
|
|
|
+ const policy = { width: 1130, height: 565, maxBytes: 1024 * 1024 }
|
|
|
|
|
|
|
|
const cancelled = attachments.readImageRequest(attachment, policy, controller.signal)
|
|
const cancelled = attachments.readImageRequest(attachment, policy, controller.signal)
|
|
|
const completed = attachments.readImageRequest(attachment, policy)
|
|
const completed = attachments.readImageRequest(attachment, policy)
|
|
@@ -306,7 +301,7 @@ describe('local request-image cache', () => {
|
|
|
const controller = new AbortController()
|
|
const controller = new AbortController()
|
|
|
const request = attachments.readImageRequest(
|
|
const request = attachments.readImageRequest(
|
|
|
attachment,
|
|
attachment,
|
|
|
- { projection: { kind: 'pixel-budget' as const, maxPixels: 640_000 }, maxBytes: 1024 * 1024 },
|
|
|
|
|
|
|
+ { width: 1130, height: 565, maxBytes: 1024 * 1024 },
|
|
|
controller.signal,
|
|
controller.signal,
|
|
|
)
|
|
)
|
|
|
await vi.waitFor(() => {
|
|
await vi.waitFor(() => {
|
|
@@ -339,7 +334,7 @@ describe('local request-image cache', () => {
|
|
|
return actualRead(ref, signal)
|
|
return actualRead(ref, signal)
|
|
|
})
|
|
})
|
|
|
const controller = new AbortController()
|
|
const controller = new AbortController()
|
|
|
- const policy = { projection: { kind: 'pixel-budget' as const, maxPixels: 640_000 }, maxBytes: 1024 * 1024 }
|
|
|
|
|
|
|
+ const policy = { width: 1130, height: 565, maxBytes: 1024 * 1024 }
|
|
|
const cancelled = attachments.readImageRequest(attachment, policy, controller.signal)
|
|
const cancelled = attachments.readImageRequest(attachment, policy, controller.signal)
|
|
|
await vi.waitFor(() => {
|
|
await vi.waitFor(() => {
|
|
|
expect(calls).toBe(1)
|
|
expect(calls).toBe(1)
|