lovell / sharp

High performance Node.js image processing, the fastest module to resize JPEG, PNG, WebP, AVIF and TIFF images. Uses the libvips library.
https://sharp.pixelplumbing.com
Apache License 2.0
29.15k stars 1.3k forks source link

`isAnimated` metadata property #3984

Open rexxars opened 8 months ago

rexxars commented 8 months ago

Feature request

What are you trying to achieve?

I need to check whether or not an image is animated (eg animated gif or webp). This may already be possible by checking something like metadata.pages > 1 && (metadata.format === 'gif' || metadata.format === 'webp'), but there may be subtleties that I am not considering, over time other formats such as avif or jxl will come along with animation support too.

Either way, it would be a nice developer experience win to have an easy accessible property for this instead of trying to infer it from the other metadata, is possible. Thoughts?

When you searched for similar feature requests, what did you find that might be related?

Can't quite find a feature request for this, specifically

What would you expect the API to look like?

const {isAnimated} = await sharp('someAnimated.gif').metadata()
console.log(isAnimated) // true

const {isAnimated} = await sharp('someSinglePage.jpg').metadata()
console.log(isAnimated) // false

What alternatives have you considered?

Manually checking the pages property and pairing with format checks, I suppose. PDFs are not animated, but does have pages - so it needs to be more than a pages check I believe?

Please provide sample image(s) that help explain this feature

Shouldn't be necessary for this one.

lovell commented 8 months ago

Multi-page and animated images are treated as the same thing by libvips, so this logic would probably live in sharp itself and very closely represent the sample metadata.pages > 1 && ... statement you've provided. Happy to accept a PR for this.

styfle commented 8 months ago

This sounds useful!

I've used the is-animated package in the past for this detection but having it in sharp would be a great addition. Might be worth comparing their implementation (a cursory look at the code also indicates PNG can be animated too)

Might be better off assuming all formats with multiple pages are animated besides PDF:

const isAnimated = metadata.pages > 1 && metadata.format !== 'pdf'
KishorJena commented 4 months ago

In case anyone need snippet for common animation formats

const animationFormats = ['gif', 'webp', 'png', 'avif', 'tiff']
const isAnimated = metadata.pages > 1 && animationFormats.includes(metadata.format) ) 

image

styfle commented 2 months ago

I benchmarked sharp vs is-animated and it turns out that sharp is much slower by several orders of magnitude for this use case, for example Landscape_1.jpg takes 12.343ms vs 0.002ms.

@lovell Is this expected?

Code

View Code

```js const sharp = require('sharp'); const isAnimated = require('is-animated'); const { readdirSync, readFileSync } = require('fs'); const { join } = require('path'); const fmt = (name, start, end) => `${name}: ${(end - start).toFixed(3)}ms`; for (const file of readdirSync('./test/fixtures', { withFileTypes: true })) { if (file.isDirectory()) { continue; } const buffer = readFileSync(join(file.parentPath, file.name)); const pkgStart = performance.now(); const pkgIsAnimated = isAnimated(buffer); const pkgEnd = performance.now(); const sharpStart = performance.now(); sharp(buffer).metadata().then((meta) => { const sharpIsAnimated = meta.pages > 1 && ['gif', 'png', 'webp', 'avif'].includes(meta.format); const sharpEnd = performance.now(); console.log(`${fmt('sharp', sharpStart, sharpEnd)}, ${fmt('is-animated', pkgStart, pkgEnd)} (${file.name})`); if (sharpIsAnimated !== pkgIsAnimated) { console.error('mismatch', { filename: file.name, sharpIsAnimated, pkgIsAnimated }); } }).catch(e => { // consume error }); } ```

Results

View Results

``` sharp: 14.606ms, is-animated: 0.156ms (16-bit-grey-alpha.png) sharp: 16.327ms, is-animated: 0.033ms (2569067123_aca715a2ee_o.jpg) sharp: 15.450ms, is-animated: 0.025ms (2569067123_aca715a2ee_o.png) sharp: 15.376ms, is-animated: 0.011ms (2x2_fdcce6.png) sharp: 15.328ms, is-animated: 0.004ms (320x240.jpg) sharp: 13.591ms, is-animated: 0.012ms (50020484-00001.png) sharp: 13.707ms, is-animated: 1.524ms (4.webp) sharp: 13.329ms, is-animated: 0.231ms (5_webp_a.webp) sharp: 13.015ms, is-animated: 0.007ms (8bit_depth.tiff) sharp: 12.969ms, is-animated: 0.002ms (Channel_digital_image_CMYK_color_no_profile.jpg) sharp: 12.803ms, is-animated: 0.078ms (Crash_test.gif) sharp: 13.184ms, is-animated: 0.003ms (Channel_digital_image_CMYK_color.jpg) sharp: 12.806ms, is-animated: 0.064ms (Flag_of_the_Netherlands-16bit.png) sharp: 12.511ms, is-animated: 0.009ms (Flag_of_the_Netherlands-alpha.png) sharp: 12.403ms, is-animated: 0.065ms (Flag_of_the_Netherlands.png) sharp: 12.361ms, is-animated: 0.004ms (G31D.TIF) sharp: 12.370ms, is-animated: 0.002ms (G31D_MULTI.TIF) sharp: 12.343ms, is-animated: 0.002ms (Landscape_1.jpg) sharp: 12.350ms, is-animated: 0.002ms (Landscape_2.jpg) sharp: 12.371ms, is-animated: 0.002ms (Landscape_3.jpg) sharp: 12.270ms, is-animated: 0.001ms (Landscape_4.jpg) sharp: 12.210ms, is-animated: 0.001ms (Landscape_5.jpg) sharp: 12.166ms, is-animated: 0.001ms (Landscape_6.jpg) sharp: 12.128ms, is-animated: 0.002ms (Landscape_7.jpg) sharp: 12.094ms, is-animated: 0.001ms (Landscape_8.jpg) sharp: 12.010ms, is-animated: 0.001ms (Portrait_1.jpg) sharp: 12.083ms, is-animated: 0.001ms (Landscape_9.jpg) sharp: 11.932ms, is-animated: 0.001ms (Portrait_3.jpg) sharp: 12.008ms, is-animated: 0.001ms (Portrait_2.jpg) sharp: 11.913ms, is-animated: 0.001ms (Portrait_4.jpg) sharp: 11.873ms, is-animated: 0.001ms (Portrait_5.jpg) sharp: 11.835ms, is-animated: 0.001ms (Portrait_6.jpg) sharp: 11.693ms, is-animated: 0.002ms (Portrait_8.jpg) sharp: 11.818ms, is-animated: 0.001ms (Portrait_7.jpg) sharp: 11.496ms, is-animated: 0.004ms (alpha-layer-0-background.png) sharp: 11.520ms, is-animated: 0.003ms (alpha-layer-1-fill.png) sharp: 11.500ms, is-animated: 0.001ms (alpha-layer-2-ink.jpg) sharp: 11.455ms, is-animated: 0.003ms (alpha-premultiply-1024x768-paper.png) sharp: 11.354ms, is-animated: 0.003ms (alpha-premultiply-2048x1536-paper.png) sharp: 11.324ms, is-animated: 0.006ms (animated-loop-3.gif) sharp: 11.258ms, is-animated: 0.003ms (bandbool.png) sharp: 11.315ms, is-animated: 0.002ms (animated-loop-3.webp) sharp: 11.254ms, is-animated: 0.002ms (big-height.webp) sharp: 11.212ms, is-animated: 0.003ms (blackbug.png) sharp: 11.183ms, is-animated: 0.001ms (booleanTest.jpg) sharp: 11.255ms, is-animated: 0.001ms (centered_image.jpeg) sharp: 11.041ms, is-animated: 0.002ms (concert.jpg) sharp: 11.142ms, is-animated: 0.001ms (cielab-dagams.tiff) sharp: 10.934ms, is-animated: 0.004ms (embedgravitybird.png) sharp: 10.897ms, is-animated: 0.002ms (flowers.jpeg) sharp: 10.746ms, is-animated: 0.004ms (full-transparent.png) sharp: 10.632ms, is-animated: 0.002ms (gamma_dalai_lama_gray.jpg) sharp: 10.344ms, is-animated: 0.004ms (gradients-rgb8.png) sharp: 11.197ms, is-animated: 0.001ms (circle.svg) sharp: 10.321ms, is-animated: 0.004ms (grey-8bit-alpha.png) sharp: 11.376ms, is-animated: 0.001ms (check.svg) sharp: 10.865ms, is-animated: 0.001ms (fogra-0-100-100-0.tif) sharp: 10.315ms, is-animated: 0.013ms (grey-plus-alpha.gif) sharp: 10.180ms, is-animated: 0.002ms (image-in-alpha.png) sharp: 10.117ms, is-animated: 0.003ms (input.above.composite.premultiplied.png) sharp: 10.096ms, is-animated: 0.003ms (input.below.composite.premultiplied.png) sharp: 9.999ms, is-animated: 0.002ms (output.16-bit-grey-alpha-identity.png) sharp: 10.056ms, is-animated: 0.001ms (low-contrast.jpg) sharp: 9.820ms, is-animated: 0.002ms (output.absent.composite.premultiplied.png) sharp: 9.682ms, is-animated: 0.002ms (output.alpha-premultiply-enlargement-2048x1536-paper.png) sharp: 9.625ms, is-animated: 0.003ms (output.alpha-premultiply-reduction-1024x768-paper.png) sharp: 9.598ms, is-animated: 0.004ms (output.composite-multiple.png) sharp: 9.574ms, is-animated: 0.002ms (output.composite.blend.dest-over.png) sharp: 9.549ms, is-animated: 0.002ms (output.composite.blend.over.png) sharp: 9.528ms, is-animated: 0.002ms (output.composite.blend.saturate.png) sharp: 9.504ms, is-animated: 0.002ms (output.composite.blend.xor.png) sharp: 9.014ms, is-animated: 0.002ms (output.extract-alpha-16bit.png) sharp: 8.986ms, is-animated: 0.002ms (output.extract-alpha-2-channel.png) sharp: 8.959ms, is-animated: 0.002ms (output.failj2c) sharp: 8.930ms, is-animated: 0.002ms (output.false.composite.premultiplied.png) sharp: 8.904ms, is-animated: 0.001ms (output.flatten-rgb16-orange.jpg) sharp: 8.879ms, is-animated: 0.001ms (output.greyscale-gamma-0.0.jpg) sharp: 8.856ms, is-animated: 0.001ms (output.greyscale-gamma-2.2.jpg) sharp: 8.823ms, is-animated: 0.003ms (output.greyscale-not.jpg) sharp: 8.566ms, is-animated: 0.002ms (output.jpg) sharp: 8.509ms, is-animated: 0.003ms (output.modulate-hue-angle-120.png) sharp: 8.778ms, is-animated: 0.001ms (output.hilutite.jpg) sharp: 8.454ms, is-animated: 0.003ms (output.modulate-hue-angle-150.png) sharp: 8.395ms, is-animated: 0.003ms (output.modulate-hue-angle-180.png) sharp: 8.333ms, is-animated: 0.003ms (output.modulate-hue-angle-210.png) sharp: 8.274ms, is-animated: 0.003ms (output.modulate-hue-angle-240.png) sharp: 8.214ms, is-animated: 0.003ms (output.modulate-hue-angle-270.png) sharp: 8.701ms, is-animated: 0.001ms (output.icc-cmyk.jpg) sharp: 8.159ms, is-animated: 0.003ms (output.modulate-hue-angle-30.png) sharp: 8.102ms, is-animated: 0.003ms (output.modulate-hue-angle-300.png) sharp: 8.045ms, is-animated: 0.003ms (output.modulate-hue-angle-330.png) sharp: 7.988ms, is-animated: 0.003ms (output.modulate-hue-angle-360.png) sharp: 7.922ms, is-animated: 0.002ms (output.modulate-hue-angle-60.png) sharp: 7.853ms, is-animated: 0.003ms (output.modulate-hue-angle-90.png) sharp: 7.753ms, is-animated: 0.002ms (output.noise-1-channel.png) sharp: 7.509ms, is-animated: 0.002ms (output.noise-3-channels.png) sharp: 7.454ms, is-animated: 0.002ms (output.noise-image-transparent.png) sharp: 7.429ms, is-animated: 0.001ms (output.noise-image.jpg) sharp: 7.393ms, is-animated: 0.001ms (output.recomb-saturation.jpg) sharp: 7.362ms, is-animated: 0.001ms (output.recomb-sepia.jpg) sharp: 7.312ms, is-animated: 0.003ms (output.recomb-sepia.png) sharp: 7.277ms, is-animated: 0.001ms (output.recomb-sepia2.jpg) sharp: 7.250ms, is-animated: 0.002ms (output.text-color-pango.png) sharp: 7.166ms, is-animated: 0.003ms (output.text-composite.png) sharp: 7.142ms, is-animated: 0.003ms (output.text-default.png) sharp: 7.114ms, is-animated: 0.002ms (output.text-dpi.png) sharp: 7.087ms, is-animated: 0.002ms (output.text-width-height.png) sharp: 7.056ms, is-animated: 0.002ms (output.text-with-font.png) sharp: 7.017ms, is-animated: 0.002ms (output.tint-alpha.png) sharp: 6.664ms, is-animated: 0.004ms (output.tint-blue.jpg) sharp: 6.596ms, is-animated: 0.002ms (output.tint-cmyk.jpg) sharp: 6.563ms, is-animated: 0.002ms (output.tint-green.jpg) sharp: 6.527ms, is-animated: 0.001ms (output.tint-red.jpg) sharp: 6.501ms, is-animated: 0.001ms (output.tint-sepia-hex.jpg) sharp: 6.464ms, is-animated: 0.002ms (output.tint-sepia-rgb.jpg) sharp: 6.422ms, is-animated: 0.003ms (output.true.composite.premultiplied.png) sharp: 6.376ms, is-animated: 0.003ms (output.unmodified-by-negate.png) sharp: 6.343ms, is-animated: 0.003ms (output.unmodified-png-with-one-color.png) sharp: 6.308ms, is-animated: 0.006ms (p3.png) sharp: 6.280ms, is-animated: 0.001ms (random.jpg) sharp: 6.152ms, is-animated: 0.002ms (rotating-squares.webp) sharp: 6.208ms, is-animated: 0.007ms (rotating-squares.gif) sharp: 6.083ms, is-animated: 0.003ms (stripesH.png) sharp: 6.052ms, is-animated: 0.003ms (stripesV.png) sharp: 6.158ms, is-animated: 0.001ms (sdr_cosmos12920_cicp1-13-6_yuv444_full_qp10.avif) sharp: 6.009ms, is-animated: 0.004ms (swiss.png) sharp: 5.975ms, is-animated: 0.003ms (tbgn2c16.png) sharp: 5.939ms, is-animated: 0.002ms (test-pattern.png) sharp: 5.914ms, is-animated: 0.004ms (testJoinChannel.png) sharp: 5.844ms, is-animated: 0.001ms (thRandom.jpg) sharp: 5.743ms, is-animated: 0.002ms (trim-mc.png) sharp: 6.129ms, is-animated: 0.001ms (struct-image-04-t.svg) sharp: 5.818ms, is-animated: 0.002ms (truncated.png) sharp: 5.891ms, is-animated: 0.001ms (truncated.jpg) sharp: 6.013ms, is-animated: 0.001ms (tifftag-photoshop.tiff) sharp: 5.852ms, is-animated: 0.001ms (uncompressed_tiff.tiff) sharp: 5.761ms, is-animated: 0.003ms (with-alpha.png) ```

lovell commented 2 months ago

@styfle It looks like this sample code attempts to process everything all at once so will include wait times for libuv worker threads. Pop an await in front of the metadata() call to force each call to be "sync" and the times are more consistent.

``` sharp: 1.118ms, is-animated: 0.165ms (16-bit-grey-alpha.png) sharp: 0.362ms, is-animated: 0.033ms (2569067123_aca715a2ee_o.jpg) sharp: 0.228ms, is-animated: 0.028ms (2569067123_aca715a2ee_o.png) sharp: 0.243ms, is-animated: 0.054ms (2x2_fdcce6.png) sharp: 0.344ms, is-animated: 0.005ms (320x240.jpg) sharp: 0.250ms, is-animated: 1.364ms (4.webp) sharp: 0.215ms, is-animated: 0.008ms (50020484-00001.png) sharp: 0.301ms, is-animated: 0.306ms (5_webp_a.webp) sharp: 0.279ms, is-animated: 0.013ms (8bit_depth.tiff) sharp: 2.040ms, is-animated: 0.009ms (Channel_digital_image_CMYK_color.jpg) sharp: 0.258ms, is-animated: 0.010ms (Channel_digital_image_CMYK_color_no_profile.jpg) sharp: 0.220ms, is-animated: 0.155ms (Crash_test.gif) sharp: 0.137ms, is-animated: 0.021ms (Flag_of_the_Netherlands-16bit.png) sharp: 0.328ms, is-animated: 0.007ms (Flag_of_the_Netherlands-alpha.png) sharp: 0.189ms, is-animated: 0.008ms (Flag_of_the_Netherlands.png) sharp: 0.237ms, is-animated: 0.003ms (G31D.TIF) sharp: 0.214ms, is-animated: 0.003ms (G31D_MULTI.TIF) sharp: 0.385ms, is-animated: 0.003ms (Landscape_1.jpg) sharp: 0.245ms, is-animated: 0.002ms (Landscape_2.jpg) sharp: 0.172ms, is-animated: 0.002ms (Landscape_3.jpg) sharp: 0.174ms, is-animated: 0.003ms (Landscape_4.jpg) sharp: 0.309ms, is-animated: 0.003ms (Landscape_5.jpg) sharp: 0.232ms, is-animated: 0.002ms (Landscape_6.jpg) sharp: 0.181ms, is-animated: 0.002ms (Landscape_7.jpg) sharp: 0.186ms, is-animated: 0.003ms (Landscape_8.jpg) sharp: 0.439ms, is-animated: 0.003ms (Landscape_9.jpg) sharp: 0.229ms, is-animated: 0.002ms (Portrait_1.jpg) sharp: 0.161ms, is-animated: 0.002ms (Portrait_2.jpg) sharp: 0.154ms, is-animated: 0.003ms (Portrait_3.jpg) sharp: 0.305ms, is-animated: 0.002ms (Portrait_4.jpg) sharp: 0.235ms, is-animated: 0.002ms (Portrait_5.jpg) sharp: 0.147ms, is-animated: 0.002ms (Portrait_6.jpg) sharp: 0.153ms, is-animated: 0.003ms (Portrait_7.jpg) sharp: 0.306ms, is-animated: 0.003ms (Portrait_8.jpg) sharp: 0.108ms, is-animated: 0.004ms (alpha-layer-0-background.png) sharp: 0.098ms, is-animated: 0.005ms (alpha-layer-1-fill.png) sharp: 0.124ms, is-animated: 0.003ms (alpha-layer-2-ink.jpg) sharp: 0.193ms, is-animated: 0.004ms (alpha-premultiply-1024x768-paper.png) sharp: 0.118ms, is-animated: 0.005ms (alpha-premultiply-2048x1536-paper.png) sharp: 0.133ms, is-animated: 0.012ms (animated-loop-3.gif) sharp: 0.151ms, is-animated: 0.014ms (animated-loop-3.webp) sharp: 0.104ms, is-animated: 0.004ms (bandbool.png) sharp: 0.128ms, is-animated: 0.002ms (big-height.webp) sharp: 0.096ms, is-animated: 0.004ms (blackbug.png) sharp: 0.124ms, is-animated: 0.002ms (booleanTest.jpg) sharp: 0.097ms, is-animated: 0.001ms (centered_image.jpeg) sharp: 1.097ms, is-animated: 0.001ms (check.svg) sharp: 0.276ms, is-animated: 0.006ms (cielab-dagams.tiff) sharp: 0.234ms, is-animated: 0.004ms (circle.svg) sharp: 0.180ms, is-animated: 0.003ms (concert.jpg) sharp: 0.104ms, is-animated: 0.005ms (d.png) sharp: 0.134ms, is-animated: 0.006ms (embedgravitybird.png) sharp: 0.204ms, is-animated: 0.002ms (flowers.jpeg) sharp: 0.986ms, is-animated: 0.003ms (fogra-0-100-100-0.tif) sharp: 0.113ms, is-animated: 0.007ms (full-transparent.png) sharp: 0.146ms, is-animated: 0.003ms (gamma_dalai_lama_gray.jpg) sharp: 0.119ms, is-animated: 0.006ms (gradients-rgb8.png) sharp: 0.095ms, is-animated: 0.006ms (grey-8bit-alpha.png) sharp: 0.167ms, is-animated: 0.061ms (grey-plus-alpha.gif) sharp: 0.108ms, is-animated: 0.004ms (image-in-alpha.png) sharp: 0.135ms, is-animated: 0.005ms (input.above.composite.premultiplied.png) sharp: 0.083ms, is-animated: 0.004ms (input.below.composite.premultiplied.png) sharp: 0.197ms, is-animated: 0.004ms (low-contrast.jpg) sharp: 0.179ms, is-animated: 0.009ms (p3.png) sharp: 0.198ms, is-animated: 0.045ms (prophoto.png) sharp: 0.222ms, is-animated: 0.006ms (random.jpg) sharp: 0.202ms, is-animated: 0.040ms (rotating-squares.gif) sharp: 0.174ms, is-animated: 0.006ms (rotating-squares.webp) sharp: 0.453ms, is-animated: 0.005ms (sdr_cosmos12920_cicp1-13-6_yuv444_full_qp10.avif) sharp: 0.122ms, is-animated: 0.007ms (stripesH.png) sharp: 0.146ms, is-animated: 0.006ms (stripesV.png) sharp: 0.692ms, is-animated: 0.004ms (struct-image-04-t.svg) sharp: 0.296ms, is-animated: 0.007ms (swiss.png) sharp: 0.108ms, is-animated: 0.007ms (tbgn2c16.png) sharp: 0.144ms, is-animated: 0.005ms (test-pattern.png) sharp: 0.188ms, is-animated: 0.022ms (testJoinChannel.png) sharp: 0.217ms, is-animated: 0.004ms (thRandom.jpg) sharp: 0.468ms, is-animated: 0.006ms (tifftag-photoshop.tiff) sharp: 0.163ms, is-animated: 0.011ms (trim-mc.png) sharp: 0.261ms, is-animated: 0.006ms (truncated.jpg) sharp: 0.199ms, is-animated: 0.007ms (truncated.png) sharp: 0.263ms, is-animated: 0.005ms (uncompressed_tiff.tiff) sharp: 0.202ms, is-animated: 0.007ms (with-alpha.png) ```

The is-animated package inspects the first few bytes of the buffer and discards anything that isn't PNG, GIF or WebP, so will always be very fast for e.g. JPEGs. Interestingly it appears to be slower for large WebP images as it scans the whole buffer looking for the "ANIM" magic bytes so there's a possible performance improvement for it here.

The one thing that stands out from the sharp/libvips results is that images with CMYK profiles appear to take longer, which might suggest a possible improvement in libvips itself relating to parsing/verifying ICC profiles.

lovell commented 2 months ago

I took a quick look at why JPEGs with a CMYK profile are slower...

libvips tells libjpeg it wants to know about the APP2 marker (used for ICC profiles) here:

https://github.com/libvips/libvips/blob/fad198f86d13fffe949641ed48f36844689f5317/libvips/foreign/jpeg2vips.c#L1004C1-L1004C57

This makes libjpeg copy the data from the marker byte-by-byte here:

https://github.com/libjpeg-turbo/libjpeg-turbo/blob/488d42a8a5669fd227f9225258a8d6fec92d204b/jdmarker.c#L806-L817

Testing via callgrind suggests this save_marker function consumes almost all of the CPU time, and appears to rise linearly-ish with size of the ICC profile. CMYK profiles are generally much bigger, hence why these are slower.

The only thing I can think of is to modify the libvips API to allow control over optionally ignoring APP2 (and maybe APP13/14) markers.

kleisauke commented 2 months ago

Testing via callgrind suggests this save_marker function consumes almost all of the CPU time, and appears to rise linearly-ish with size of the ICC profile. CMYK profiles are generally much bigger, hence why these are slower.

This sounds like issue https://github.com/libjpeg-turbo/libjpeg-turbo/issues/764, which will be fixed in the forthcoming libjpeg-turbo v3.0.4.

lovell commented 2 months ago

Thanks Kleis, that would make sense as CMYK profiles are big enough to span multiple APP2 markers. The upstream libjpeg report describes millions of markers whereas there are only 18 in the Channel_digital_image_CMYK_color.jpg example so perhaps there's also something else?

kleisauke commented 2 months ago

Indeed, perhaps it's not the same issue. I wonder if we could avoid calling jpeg_save_markers() and just use the marker processor routine via jpeg_set_marker_processor() instead. /cc @jcupitt

Note that these functions are mutually exclusive - if you call one it overrides any previous call to the other, for the particular marker type specified.