Compare commits

..

4 Commits

Author SHA1 Message Date
Lovell Fuller
6d18c6fdc6 Add Media Type to metadata response #4492 2026-02-01 21:20:16 +00:00
Lovell Fuller
2291c0b864 Switch from custom VError to standard runtime_error 2026-01-23 22:42:23 +00:00
Lovell Fuller
ed6b7384d0 Ensure TIFF output bitdepth option is limited to 1, 2 or 4 2026-01-23 21:29:40 +00:00
Lovell Fuller
ef77388a73 Force MSVC to use exception handling
As of 8.18.0, libvips C++ wrapper retrieves error messages at
exception construction time rather than lazily when accessed.

On Windows this led to error messages being referenced rather
than copied, leading to access beyond their lifetime and possible
corruption.
2026-01-22 12:52:48 +00:00
14 changed files with 123 additions and 41 deletions

View File

@@ -18,6 +18,7 @@ Dimensions in the response will respect the `page` and `pages` properties of the
A `Promise` is returned when `callback` is not provided. A `Promise` is returned when `callback` is not provided.
- `format`: Name of decoder used to parse image e.g. `jpeg`, `png`, `webp`, `gif`, `svg`, `heif`, `tiff` - `format`: Name of decoder used to parse image e.g. `jpeg`, `png`, `webp`, `gif`, `svg`, `heif`, `tiff`
- `mediaType`: Media Type (MIME Type) e.g. `image/jpeg`, `image/png`, `image/svg+xml`, `image/avif`
- `size`: Total size of image in bytes, for Stream and Buffer input only - `size`: Total size of image in bytes, for Stream and Buffer input only
- `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below) - `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
- `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below) - `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)

View File

@@ -20,7 +20,7 @@ slug: changelog/v0.35.0
* Upgrade to libvips v8.18.0 for upstream bug fixes. * Upgrade to libvips v8.18.0 for upstream bug fixes.
* Improve thread-safety of error (and warning) messages. * Ensure TIFF output `bitdepth` option is limited to 1, 2 or 4.
* Deprecate Windows 32-bit (win32-ia32) prebuilt binaries. * Deprecate Windows 32-bit (win32-ia32) prebuilt binaries.
@@ -43,4 +43,7 @@ slug: changelog/v0.35.0
* Ensure HEIF primary item is used as default page/frame. * Ensure HEIF primary item is used as default page/frame.
[#4487](https://github.com/lovell/sharp/issues/4487) [#4487](https://github.com/lovell/sharp/issues/4487)
* Add image Media Type (MIME Type) to metadata response.
[#4492](https://github.com/lovell/sharp/issues/4492)
* Add WebP `exact` option for control over transparent pixel colour values. * Add WebP `exact` option for control over transparent pixel colour values.

4
lib/index.d.ts vendored
View File

@@ -1479,8 +1479,8 @@ declare namespace sharp {
xres?: number | undefined; xres?: number | undefined;
/** Vertical resolution in pixels/mm (optional, default 1.0) */ /** Vertical resolution in pixels/mm (optional, default 1.0) */
yres?: number | undefined; yres?: number | undefined;
/** Reduce bitdepth to 1, 2 or 4 bit (optional, default 8) */ /** Reduce bitdepth to 1, 2 or 4 bit (optional) */
bitdepth?: 1 | 2 | 4 | 8 | undefined; bitdepth?: 1 | 2 | 4 | undefined;
/** Write 1-bit images as miniswhite (optional, default false) */ /** Write 1-bit images as miniswhite (optional, default false) */
miniswhite?: boolean | undefined; miniswhite?: boolean | undefined;
/** Resolution unit options: inch, cm (optional, default 'inch') */ /** Resolution unit options: inch, cm (optional, default 'inch') */

View File

@@ -567,6 +567,7 @@ function _isStreamInput () {
* A `Promise` is returned when `callback` is not provided. * A `Promise` is returned when `callback` is not provided.
* *
* - `format`: Name of decoder used to parse image e.g. `jpeg`, `png`, `webp`, `gif`, `svg`, `heif`, `tiff` * - `format`: Name of decoder used to parse image e.g. `jpeg`, `png`, `webp`, `gif`, `svg`, `heif`, `tiff`
* - `mediaType`: Media Type (MIME Type) e.g. `image/jpeg`, `image/png`, `image/svg+xml`, `image/avif`
* - `size`: Total size of image in bytes, for Stream and Buffer input only * - `size`: Total size of image in bytes, for Stream and Buffer input only
* - `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below) * - `width`: Number of pixels wide (EXIF orientation is not taken into consideration, see example below)
* - `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below) * - `height`: Number of pixels high (EXIF orientation is not taken into consideration, see example below)

View File

@@ -21,6 +21,7 @@
'defines': [ 'defines': [
'_VIPS_PUBLIC=__declspec(dllexport)', '_VIPS_PUBLIC=__declspec(dllexport)',
'_ALLOW_KEYWORD_MACROS', '_ALLOW_KEYWORD_MACROS',
'_HAS_EXCEPTIONS=1',
'G_DISABLE_ASSERT', 'G_DISABLE_ASSERT',
'G_DISABLE_CAST_CHECKS', 'G_DISABLE_CAST_CHECKS',
'G_DISABLE_CHECKS' 'G_DISABLE_CHECKS'
@@ -148,7 +149,8 @@
['OS == "win"', { ['OS == "win"', {
'defines': [ 'defines': [
'_ALLOW_KEYWORD_MACROS', '_ALLOW_KEYWORD_MACROS',
'_FILE_OFFSET_BITS=64' '_FILE_OFFSET_BITS=64',
'_HAS_EXCEPTIONS=1'
], ],
'link_settings': { 'link_settings': {
'libraries': [ 'libraries': [

View File

@@ -151,13 +151,52 @@ class MetadataWorker : public Napi::AsyncWorker {
} }
// PNG comments // PNG comments
vips_image_map(image.get_image(), readPNGComment, &baton->comments); vips_image_map(image.get_image(), readPNGComment, &baton->comments);
// Media type
std::string mediaType;
switch (imageType) {
case sharp::ImageType::JPEG:
case sharp::ImageType::PNG:
case sharp::ImageType::WEBP:
case sharp::ImageType::JP2:
case sharp::ImageType::TIFF:
case sharp::ImageType::GIF:
case sharp::ImageType::FITS:
case sharp::ImageType::JXL:
baton->mediaType = "image/" + baton->format;
break;
case sharp::ImageType::SVG:
baton->mediaType = "image/svg+xml";
break;
case sharp::ImageType::HEIF:
if (baton->compression == "av1") {
baton->mediaType = "image/avif";
} else if (baton->compression == "hevc") {
baton->mediaType = "image/heic";
}
break;
case sharp::ImageType::PDF:
baton->mediaType = "application/pdf";
break;
case sharp::ImageType::OPENSLIDE:
baton->mediaType = "image/tiff";
break;
case sharp::ImageType::PPM:
baton->mediaType = "image/x-portable-pixmap";
break;
case sharp::ImageType::EXR:
baton->mediaType = "image/x-exr";
break;
case sharp::ImageType::RAD:
baton->mediaType = "image/vnd.radiance";
break;
case sharp::ImageType::UHDR:
baton->mediaType = "image/jpeg";
break;
default:
break;
}
} }
// Handle warnings
std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
baton->warnings.push_back(warning);
warning = sharp::VipsWarningPop();
}
// Clean up // Clean up
vips_error_clear(); vips_error_clear();
vips_thread_shutdown(); vips_thread_shutdown();
@@ -167,12 +206,19 @@ class MetadataWorker : public Napi::AsyncWorker {
Napi::Env env = Env(); Napi::Env env = Env();
Napi::HandleScope scope(env); Napi::HandleScope scope(env);
for (auto& warning : baton->warnings) { // Handle warnings
std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) }); debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) });
warning = sharp::VipsWarningPop();
} }
if (baton->err.empty()) { if (baton->err.empty()) {
Napi::Object info = Napi::Object::New(env); Napi::Object info = Napi::Object::New(env);
info.Set("format", baton->format); info.Set("format", baton->format);
if (!baton->mediaType.empty()) {
info.Set("mediaType", baton->mediaType);
}
if (baton->input->bufferLength > 0) { if (baton->input->bufferLength > 0) {
info.Set("size", baton->input->bufferLength); info.Set("size", baton->input->bufferLength);
} }

View File

@@ -19,6 +19,7 @@ struct MetadataBaton {
sharp::InputDescriptor *input; sharp::InputDescriptor *input;
// Output // Output
std::string format; std::string format;
std::string mediaType;
int width; int width;
int height; int height;
std::string space; std::string space;
@@ -57,7 +58,6 @@ struct MetadataBaton {
size_t gainMapLength; size_t gainMapLength;
MetadataComments comments; MetadataComments comments;
std::string err; std::string err;
std::vector<std::string> warnings;
MetadataBaton(): MetadataBaton():
input(nullptr), input(nullptr),

View File

@@ -1280,21 +1280,12 @@ class PipelineWorker : public Napi::AsyncWorker {
} else { } else {
if (baton->input->failOn == VIPS_FAIL_ON_WARNING) { if (baton->input->failOn == VIPS_FAIL_ON_WARNING) {
(baton->err).append("Warning treated as error due to failOn setting"); (baton->err).append("Warning treated as error due to failOn setting");
baton->errUseWarning = true;
} else { } else {
(baton->err).append("Unknown error"); (baton->err).append("Unknown error");
} }
} }
} }
// Handle warnings
std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
if (baton->input->failOn == VIPS_FAIL_ON_WARNING) {
(baton->err).append("\n").append(warning);
} else {
(baton->warnings).push_back(warning);
}
warning = sharp::VipsWarningPop();
}
// Clean up libvips' per-request data and threads // Clean up libvips' per-request data and threads
vips_error_clear(); vips_error_clear();
vips_thread_shutdown(); vips_thread_shutdown();
@@ -1304,8 +1295,15 @@ class PipelineWorker : public Napi::AsyncWorker {
Napi::Env env = Env(); Napi::Env env = Env();
Napi::HandleScope scope(env); Napi::HandleScope scope(env);
for (auto &warning : baton->warnings) { // Handle warnings
debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) }); std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
if (baton->errUseWarning) {
(baton->err).append("\n").append(warning);
} else {
debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) });
}
warning = sharp::VipsWarningPop();
} }
if (baton->err.empty()) { if (baton->err.empty()) {
int width = baton->width; int width = baton->width;

View File

@@ -204,7 +204,6 @@ struct PipelineBaton {
bool jxlLossless; bool jxlLossless;
VipsBandFormat rawDepth; VipsBandFormat rawDepth;
std::string err; std::string err;
std::vector<std::string> warnings;
bool errUseWarning; bool errUseWarning;
int keepMetadata; int keepMetadata;
int withMetadataOrientation; int withMetadataOrientation;

View File

@@ -96,12 +96,7 @@ class StatsWorker : public Napi::AsyncWorker {
(baton->err).append(err.what()); (baton->err).append(err.what());
} }
} }
// Handle warnings
std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
baton->warnings.push_back(warning);
warning = sharp::VipsWarningPop();
}
// Clean up // Clean up
vips_error_clear(); vips_error_clear();
vips_thread_shutdown(); vips_thread_shutdown();
@@ -111,8 +106,11 @@ class StatsWorker : public Napi::AsyncWorker {
Napi::Env env = Env(); Napi::Env env = Env();
Napi::HandleScope scope(env); Napi::HandleScope scope(env);
for (auto& warning : baton->warnings) { // Handle warnings
std::string warning = sharp::VipsWarningPop();
while (!warning.empty()) {
debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) }); debuglog.Call(Receiver().Value(), { Napi::String::New(env, warning) });
warning = sharp::VipsWarningPop();
} }
if (baton->err.empty()) { if (baton->err.empty()) {
// Stats Object // Stats Object

View File

@@ -45,7 +45,6 @@ struct StatsBaton {
int dominantBlue; int dominantBlue;
std::string err; std::string err;
std::vector<std::string> warnings;
StatsBaton(): StatsBaton():
input(nullptr), input(nullptr),

View File

@@ -36,6 +36,7 @@ describe('AVIF', () => {
density: 72, density: 72,
depth: 'uchar', depth: 'uchar',
format: 'jpeg', format: 'jpeg',
mediaType: 'image/jpeg',
hasAlpha: false, hasAlpha: false,
hasProfile: false, hasProfile: false,
// 32 / (2048 / 858) = 13.40625 // 32 / (2048 / 858) = 13.40625
@@ -64,6 +65,7 @@ describe('AVIF', () => {
compression: 'av1', compression: 'av1',
depth: 'uchar', depth: 'uchar',
format: 'heif', format: 'heif',
mediaType: 'image/avif',
hasAlpha: false, hasAlpha: false,
hasProfile: false, hasProfile: false,
height: 26, height: 26,
@@ -93,6 +95,7 @@ describe('AVIF', () => {
compression: 'av1', compression: 'av1',
depth: 'uchar', depth: 'uchar',
format: 'heif', format: 'heif',
mediaType: 'image/avif',
hasAlpha: false, hasAlpha: false,
hasProfile: false, hasProfile: false,
height: 24, height: 24,
@@ -119,6 +122,7 @@ describe('AVIF', () => {
compression: 'av1', compression: 'av1',
depth: 'uchar', depth: 'uchar',
format: 'heif', format: 'heif',
mediaType: 'image/avif',
hasAlpha: false, hasAlpha: false,
hasProfile: false, hasProfile: false,
height: 13, height: 13,
@@ -148,6 +152,7 @@ describe('AVIF', () => {
compression: 'av1', compression: 'av1',
depth: 'uchar', depth: 'uchar',
format: 'heif', format: 'heif',
mediaType: 'image/avif',
hasAlpha: true, hasAlpha: true,
hasProfile: false, hasProfile: false,
height: 300, height: 300,
@@ -178,6 +183,7 @@ describe('AVIF', () => {
compression: 'av1', compression: 'av1',
depth: 'uchar', depth: 'uchar',
format: 'heif', format: 'heif',
mediaType: 'image/avif',
hasAlpha: false, hasAlpha: false,
hasProfile: false, hasProfile: false,
height: 26, height: 26,
@@ -236,6 +242,7 @@ describe('AVIF', () => {
void exif; void exif;
assert.deepStrictEqual(metadata, { assert.deepStrictEqual(metadata, {
format: 'heif', format: 'heif',
mediaType: 'image/avif',
width: 4096, width: 4096,
height: 800, height: 800,
space: 'srgb', space: 'srgb',
@@ -259,6 +266,7 @@ describe('AVIF', () => {
const { size, ...pngMetadata } = await sharp(data).metadata(); const { size, ...pngMetadata } = await sharp(data).metadata();
assert.deepStrictEqual(pngMetadata, { assert.deepStrictEqual(pngMetadata, {
format: 'png', format: 'png',
mediaType: 'image/png',
width: 4096, width: 4096,
height: 800, height: 800,
space: 'srgb', space: 'srgb',

View File

@@ -19,6 +19,7 @@ describe('Image metadata', () => {
sharp(fixtures.inputJpg).metadata((err, metadata) => { sharp(fixtures.inputJpg).metadata((err, metadata) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', metadata.format); assert.strictEqual('jpeg', metadata.format);
assert.strictEqual('image/jpeg', metadata.mediaType);
assert.strictEqual('undefined', typeof metadata.size); assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(2725, metadata.width); assert.strictEqual(2725, metadata.width);
assert.strictEqual(2225, metadata.height); assert.strictEqual(2225, metadata.height);
@@ -41,6 +42,7 @@ describe('Image metadata', () => {
sharp(fixtures.inputJpgWithExif).metadata((err, metadata) => { sharp(fixtures.inputJpgWithExif).metadata((err, metadata) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', metadata.format); assert.strictEqual('jpeg', metadata.format);
assert.strictEqual('image/jpeg', metadata.mediaType);
assert.strictEqual('undefined', typeof metadata.size); assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(450, metadata.width); assert.strictEqual(450, metadata.width);
assert.strictEqual(600, metadata.height); assert.strictEqual(600, metadata.height);
@@ -66,6 +68,7 @@ describe('Image metadata', () => {
const profile = icc.parse(metadata.icc); const profile = icc.parse(metadata.icc);
assert.strictEqual('object', typeof profile); assert.strictEqual('object', typeof profile);
assert.strictEqual('Generic RGB Profile', profile.description); assert.strictEqual('Generic RGB Profile', profile.description);
assert.strictEqual('image/jpeg', metadata.mediaType);
done(); done();
}); });
}); });
@@ -92,6 +95,7 @@ describe('Image metadata', () => {
sharp(fixtures.inputTiff).metadata((err, metadata) => { sharp(fixtures.inputTiff).metadata((err, metadata) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('tiff', metadata.format); assert.strictEqual('tiff', metadata.format);
assert.strictEqual('image/tiff', metadata.mediaType);
assert.strictEqual('undefined', typeof metadata.size); assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(2464, metadata.width); assert.strictEqual(2464, metadata.width);
assert.strictEqual(3248, metadata.height); assert.strictEqual(3248, metadata.height);
@@ -111,6 +115,7 @@ describe('Image metadata', () => {
assert.strictEqual('undefined', typeof metadata.xmp); assert.strictEqual('undefined', typeof metadata.xmp);
assert.strictEqual('undefined', typeof metadata.xmpAsString); assert.strictEqual('undefined', typeof metadata.xmpAsString);
assert.strictEqual('inch', metadata.resolutionUnit); assert.strictEqual('inch', metadata.resolutionUnit);
assert.strictEqual('image/tiff', metadata.mediaType);
done(); done();
}); });
}); });
@@ -119,6 +124,7 @@ describe('Image metadata', () => {
sharp(fixtures.inputTiffMultipage).metadata((err, metadata) => { sharp(fixtures.inputTiffMultipage).metadata((err, metadata) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('tiff', metadata.format); assert.strictEqual('tiff', metadata.format);
assert.strictEqual('image/tiff', metadata.mediaType);
assert.strictEqual('undefined', typeof metadata.size); assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(2464, metadata.width); assert.strictEqual(2464, metadata.width);
assert.strictEqual(3248, metadata.height); assert.strictEqual(3248, metadata.height);
@@ -142,6 +148,7 @@ describe('Image metadata', () => {
sharp(fixtures.inputPng).metadata((err, metadata) => { sharp(fixtures.inputPng).metadata((err, metadata) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('png', metadata.format); assert.strictEqual('png', metadata.format);
assert.strictEqual('image/png', metadata.mediaType);
assert.strictEqual('undefined', typeof metadata.size); assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(2809, metadata.width); assert.strictEqual(2809, metadata.width);
assert.strictEqual(2074, metadata.height); assert.strictEqual(2074, metadata.height);
@@ -166,6 +173,7 @@ describe('Image metadata', () => {
sharp(fixtures.inputPngTestJoinChannel).metadata((err, metadata) => { sharp(fixtures.inputPngTestJoinChannel).metadata((err, metadata) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('png', metadata.format); assert.strictEqual('png', metadata.format);
assert.strictEqual('image/png', metadata.mediaType);
assert.strictEqual('undefined', typeof metadata.size); assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(320, metadata.width); assert.strictEqual(320, metadata.width);
assert.strictEqual(240, metadata.height); assert.strictEqual(240, metadata.height);
@@ -191,6 +199,7 @@ describe('Image metadata', () => {
sharp(fixtures.inputPngWithTransparency).metadata((err, metadata) => { sharp(fixtures.inputPngWithTransparency).metadata((err, metadata) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('png', metadata.format); assert.strictEqual('png', metadata.format);
assert.strictEqual('image/png', metadata.mediaType);
assert.strictEqual('undefined', typeof metadata.size); assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(2048, metadata.width); assert.strictEqual(2048, metadata.width);
assert.strictEqual(1536, metadata.height); assert.strictEqual(1536, metadata.height);
@@ -225,6 +234,7 @@ describe('Image metadata', () => {
height: 32, height: 32,
isPalette: false, isPalette: false,
isProgressive: false, isProgressive: false,
mediaType: 'image/png',
space: 'b-w', space: 'b-w',
width: 32, width: 32,
autoOrient: { autoOrient: {
@@ -250,6 +260,7 @@ describe('Image metadata', () => {
height: 32, height: 32,
isPalette: false, isPalette: false,
isProgressive: false, isProgressive: false,
mediaType: 'image/png',
space: 'grey16', space: 'grey16',
width: 32, width: 32,
autoOrient: { autoOrient: {
@@ -263,6 +274,7 @@ describe('Image metadata', () => {
sharp(fixtures.inputWebP).metadata((err, metadata) => { sharp(fixtures.inputWebP).metadata((err, metadata) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('webp', metadata.format); assert.strictEqual('webp', metadata.format);
assert.strictEqual('image/webp', metadata.mediaType);
assert.strictEqual('undefined', typeof metadata.size); assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(1024, metadata.width); assert.strictEqual(1024, metadata.width);
assert.strictEqual(772, metadata.height); assert.strictEqual(772, metadata.height);
@@ -285,11 +297,12 @@ describe('Image metadata', () => {
sharp(fixtures.inputWebPAnimated) sharp(fixtures.inputWebPAnimated)
.metadata() .metadata()
.then(({ .then(({
format, width, height, space, channels, depth, format, mediaType, width, height, space, channels, depth,
isProgressive, pages, loop, delay, hasProfile, isProgressive, pages, loop, delay, hasProfile,
hasAlpha hasAlpha
}) => { }) => {
assert.strictEqual(format, 'webp'); assert.strictEqual(format, 'webp');
assert.strictEqual(mediaType, 'image/webp');
assert.strictEqual(width, 80); assert.strictEqual(width, 80);
assert.strictEqual(height, 80); assert.strictEqual(height, 80);
assert.strictEqual(space, 'srgb'); assert.strictEqual(space, 'srgb');
@@ -308,11 +321,12 @@ describe('Image metadata', () => {
sharp(fixtures.inputWebPAnimated, { pages: -1 }) sharp(fixtures.inputWebPAnimated, { pages: -1 })
.metadata() .metadata()
.then(({ .then(({
format, width, height, space, channels, depth, format, mediaType, width, height, space, channels, depth,
isProgressive, pages, pageHeight, loop, delay, isProgressive, pages, pageHeight, loop, delay,
hasProfile, hasAlpha hasProfile, hasAlpha
}) => { }) => {
assert.strictEqual(format, 'webp'); assert.strictEqual(format, 'webp');
assert.strictEqual(mediaType, 'image/webp');
assert.strictEqual(width, 80); assert.strictEqual(width, 80);
assert.strictEqual(height, 720); assert.strictEqual(height, 720);
assert.strictEqual(space, 'srgb'); assert.strictEqual(space, 'srgb');
@@ -332,11 +346,12 @@ describe('Image metadata', () => {
sharp(fixtures.inputWebPAnimatedLoop3) sharp(fixtures.inputWebPAnimatedLoop3)
.metadata() .metadata()
.then(({ .then(({
format, width, height, space, channels, depth, format, mediaType, width, height, space, channels, depth,
isProgressive, pages, loop, delay, hasProfile, isProgressive, pages, loop, delay, hasProfile,
hasAlpha hasAlpha
}) => { }) => {
assert.strictEqual(format, 'webp'); assert.strictEqual(format, 'webp');
assert.strictEqual(mediaType, 'image/webp');
assert.strictEqual(width, 370); assert.strictEqual(width, 370);
assert.strictEqual(height, 285); assert.strictEqual(height, 285);
assert.strictEqual(space, 'srgb'); assert.strictEqual(space, 'srgb');
@@ -355,6 +370,7 @@ describe('Image metadata', () => {
sharp(fixtures.inputGif).metadata((err, metadata) => { sharp(fixtures.inputGif).metadata((err, metadata) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('gif', metadata.format); assert.strictEqual('gif', metadata.format);
assert.strictEqual('image/gif', metadata.mediaType);
assert.strictEqual('undefined', typeof metadata.size); assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(800, metadata.width); assert.strictEqual(800, metadata.width);
assert.strictEqual(533, metadata.height); assert.strictEqual(533, metadata.height);
@@ -375,6 +391,7 @@ describe('Image metadata', () => {
sharp(fixtures.inputGifGreyPlusAlpha).metadata((err, metadata) => { sharp(fixtures.inputGifGreyPlusAlpha).metadata((err, metadata) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('gif', metadata.format); assert.strictEqual('gif', metadata.format);
assert.strictEqual('image/gif', metadata.mediaType);
assert.strictEqual('undefined', typeof metadata.size); assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(2, metadata.width); assert.strictEqual(2, metadata.width);
assert.strictEqual(1, metadata.height); assert.strictEqual(1, metadata.height);
@@ -395,11 +412,12 @@ describe('Image metadata', () => {
sharp(fixtures.inputGifAnimated) sharp(fixtures.inputGifAnimated)
.metadata() .metadata()
.then(({ .then(({
format, width, height, space, channels, depth, format, mediaType, width, height, space, channels, depth,
isProgressive, pages, loop, delay, background, isProgressive, pages, loop, delay, background,
hasProfile, hasAlpha hasProfile, hasAlpha
}) => { }) => {
assert.strictEqual(format, 'gif'); assert.strictEqual(format, 'gif');
assert.strictEqual(mediaType, 'image/gif');
assert.strictEqual(width, 80); assert.strictEqual(width, 80);
assert.strictEqual(height, 80); assert.strictEqual(height, 80);
assert.strictEqual(space, 'srgb'); assert.strictEqual(space, 'srgb');
@@ -419,11 +437,12 @@ describe('Image metadata', () => {
sharp(fixtures.inputGifAnimatedLoop3) sharp(fixtures.inputGifAnimatedLoop3)
.metadata() .metadata()
.then(({ .then(({
format, width, height, space, channels, depth, format, mediaType, width, height, space, channels, depth,
isProgressive, pages, loop, delay, hasProfile, isProgressive, pages, loop, delay, hasProfile,
hasAlpha hasAlpha
}) => { }) => {
assert.strictEqual(format, 'gif'); assert.strictEqual(format, 'gif');
assert.strictEqual(mediaType, 'image/gif');
assert.strictEqual(width, 370); assert.strictEqual(width, 370);
assert.strictEqual(height, 285); assert.strictEqual(height, 285);
assert.strictEqual(space, 'srgb'); assert.strictEqual(space, 'srgb');
@@ -462,6 +481,7 @@ describe('Image metadata', () => {
it('File in, Promise out', (_t, done) => { it('File in, Promise out', (_t, done) => {
sharp(fixtures.inputJpg).metadata().then((metadata) => { sharp(fixtures.inputJpg).metadata().then((metadata) => {
assert.strictEqual('jpeg', metadata.format); assert.strictEqual('jpeg', metadata.format);
assert.strictEqual('image/jpeg', metadata.mediaType);
assert.strictEqual('undefined', typeof metadata.size); assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(2725, metadata.width); assert.strictEqual(2725, metadata.width);
assert.strictEqual(2225, metadata.height); assert.strictEqual(2225, metadata.height);
@@ -508,6 +528,7 @@ describe('Image metadata', () => {
const pipeline = sharp(); const pipeline = sharp();
pipeline.metadata().then((metadata) => { pipeline.metadata().then((metadata) => {
assert.strictEqual('jpeg', metadata.format); assert.strictEqual('jpeg', metadata.format);
assert.strictEqual('image/jpeg', metadata.mediaType);
assert.strictEqual(829183, metadata.size); assert.strictEqual(829183, metadata.size);
assert.strictEqual(2725, metadata.width); assert.strictEqual(2725, metadata.width);
assert.strictEqual(2225, metadata.height); assert.strictEqual(2225, metadata.height);
@@ -559,6 +580,7 @@ describe('Image metadata', () => {
const pipeline = sharp().metadata((err, metadata) => { const pipeline = sharp().metadata((err, metadata) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', metadata.format); assert.strictEqual('jpeg', metadata.format);
assert.strictEqual('image/jpeg', metadata.mediaType);
assert.strictEqual(829183, metadata.size); assert.strictEqual(829183, metadata.size);
assert.strictEqual(2725, metadata.width); assert.strictEqual(2725, metadata.width);
assert.strictEqual(2225, metadata.height); assert.strictEqual(2225, metadata.height);
@@ -583,6 +605,7 @@ describe('Image metadata', () => {
image.metadata((err, metadata) => { image.metadata((err, metadata) => {
if (err) throw err; if (err) throw err;
assert.strictEqual('jpeg', metadata.format); assert.strictEqual('jpeg', metadata.format);
assert.strictEqual('image/jpeg', metadata.mediaType);
assert.strictEqual('undefined', typeof metadata.size); assert.strictEqual('undefined', typeof metadata.size);
assert.strictEqual(2725, metadata.width); assert.strictEqual(2725, metadata.width);
assert.strictEqual(2225, metadata.height); assert.strictEqual(2225, metadata.height);
@@ -642,7 +665,7 @@ describe('Image metadata', () => {
}); });
it('keep existing ICC profile', async () => { it('keep existing ICC profile', async () => {
const data = await sharp(fixtures.inputJpgWithExif, { failOn: 'error' }) const data = await sharp(fixtures.inputJpgWithExif)
.keepIccProfile() .keepIccProfile()
.toBuffer(); .toBuffer();
@@ -675,7 +698,7 @@ describe('Image metadata', () => {
}); });
it('keep existing ICC profile, avoid colour transform', async () => { it('keep existing ICC profile, avoid colour transform', async () => {
const [r, g, b] = await sharp(fixtures.inputPngWithProPhotoProfile, { failOn: 'error' }) const [r, g, b] = await sharp(fixtures.inputPngWithProPhotoProfile)
.keepIccProfile() .keepIccProfile()
.raw() .raw()
.toBuffer(); .toBuffer();
@@ -721,7 +744,7 @@ describe('Image metadata', () => {
}); });
it('transform to invalid ICC profile emits warning', async () => { it('transform to invalid ICC profile emits warning', async () => {
const img = sharp({ create, failOn: 'error' }) const img = sharp({ create })
.png() .png()
.withIccProfile(fixtures.path('invalid-illuminant.icc')); .withIccProfile(fixtures.path('invalid-illuminant.icc'));
@@ -917,6 +940,7 @@ describe('Image metadata', () => {
.metadata() .metadata()
.then(metadata => { .then(metadata => {
assert.strictEqual(metadata.format, 'tiff'); assert.strictEqual(metadata.format, 'tiff');
assert.strictEqual(metadata.mediaType, 'image/tiff');
assert.strictEqual(metadata.width, 317); assert.strictEqual(metadata.width, 317);
assert.strictEqual(metadata.height, 211); assert.strictEqual(metadata.height, 211);
assert.strictEqual(metadata.space, 'rgb16'); assert.strictEqual(metadata.space, 'rgb16');
@@ -931,6 +955,7 @@ describe('Image metadata', () => {
const metadata = await sharp(fixtures.inputAvif).metadata(); const metadata = await sharp(fixtures.inputAvif).metadata();
assert.deepStrictEqual(metadata, { assert.deepStrictEqual(metadata, {
format: 'heif', format: 'heif',
mediaType: 'image/avif',
width: 2048, width: 2048,
height: 858, height: 858,
space: 'srgb', space: 'srgb',
@@ -1014,6 +1039,7 @@ describe('Image metadata', () => {
const metadata = await sharp(fixtures.inputJpgLossless).metadata(); const metadata = await sharp(fixtures.inputJpgLossless).metadata();
assert.deepStrictEqual(metadata, { assert.deepStrictEqual(metadata, {
format: 'jpeg', format: 'jpeg',
mediaType: 'image/jpeg',
width: 227, width: 227,
height: 149, height: 149,
space: 'srgb', space: 'srgb',

View File

@@ -145,6 +145,7 @@ describe('PNG', () => {
width: 68 width: 68
}, },
format: 'png', format: 'png',
mediaType: 'image/png',
width: 68, width: 68,
height: 68, height: 68,
space: 'srgb', space: 'srgb',