From a3e5231873740d256723efc077ace42e7c14ff96 Mon Sep 17 00:00:00 2001 From: Michael Telatynski <7t3chguy@gmail.com> Date: Mon, 28 Mar 2022 09:38:54 +0100 Subject: [PATCH] Add support for Animated (A)PNG (#8158) --- src/utils/Image.ts | 119 ++++++++++++++++++++++----------- src/utils/blobs.ts | 1 + test/Image-test.ts | 34 +++++++--- test/images/animated-logo.apng | Bin 0 -> 47488 bytes test/images/static-logo.png | Bin 0 -> 1611 bytes 5 files changed, 106 insertions(+), 48 deletions(-) create mode 100644 test/images/animated-logo.apng create mode 100644 test/images/static-logo.png diff --git a/src/utils/Image.ts b/src/utils/Image.ts index 63dcad99115..57ed3eefa69 100644 --- a/src/utils/Image.ts +++ b/src/utils/Image.ts @@ -14,62 +14,105 @@ * limitations under the License. */ +import { arrayHasDiff } from "./arrays"; + export function mayBeAnimated(mimeType: string): boolean { - return ["image/gif", "image/webp"].includes(mimeType); + return ["image/gif", "image/webp", "image/png", "image/apng"].includes(mimeType); } function arrayBufferRead(arr: ArrayBuffer, start: number, len: number): Uint8Array { return new Uint8Array(arr.slice(start, start + len)); } +function arrayBufferReadInt(arr: ArrayBuffer, start: number): number { + const dv = new DataView(arr, start, 4); + return dv.getUint32(0); +} + function arrayBufferReadStr(arr: ArrayBuffer, start: number, len: number): string { return String.fromCharCode.apply(null, arrayBufferRead(arr, start, len)); } export async function blobIsAnimated(mimeType: string, blob: Blob): Promise { - if (mimeType === "image/webp") { - // Only extended file format WEBP images support animation, so grab the expected data range and verify header. - // Based on https://developers.google.com/speed/webp/docs/riff_container#extended_file_format - const arr = await blob.slice(0, 17).arrayBuffer(); - if ( - arrayBufferReadStr(arr, 0, 4) === "RIFF" && - arrayBufferReadStr(arr, 8, 4) === "WEBP" && - arrayBufferReadStr(arr, 12, 4) === "VP8X" - ) { - const [flags] = arrayBufferRead(arr, 16, 1); - // Flags: R R I L E X _A_ R (reversed) - const animationFlagMask = 1 << 1; - return (flags & animationFlagMask) != 0; - } - } else if (mimeType === "image/gif") { - // Based on https://gist.github.com/zakirt/faa4a58cec5a7505b10e3686a226f285 - // More info at http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp - const dv = new DataView(await blob.arrayBuffer(), 10); - - const globalColorTable = dv.getUint8(0); - let globalColorTableSize = 0; - // check first bit, if 0, then we don't have a Global Color Table - if (globalColorTable & 0x80) { - // grab the last 3 bits, to calculate the global color table size -> RGB * 2^(N+1) - // N is the value in the last 3 bits. - globalColorTableSize = 3 * Math.pow(2, (globalColorTable & 0x7) + 1); + switch (mimeType) { + case "image/webp": { + // Only extended file format WEBP images support animation, so grab the expected data range and verify header. + // Based on https://developers.google.com/speed/webp/docs/riff_container#extended_file_format + const arr = await blob.slice(0, 17).arrayBuffer(); + if ( + arrayBufferReadStr(arr, 0, 4) === "RIFF" && + arrayBufferReadStr(arr, 8, 4) === "WEBP" && + arrayBufferReadStr(arr, 12, 4) === "VP8X" + ) { + const [flags] = arrayBufferRead(arr, 16, 1); + // Flags: R R I L E X _A_ R (reversed) + const animationFlagMask = 1 << 1; + return (flags & animationFlagMask) != 0; + } + break; } - // move on to the Graphics Control Extension - const offset = 3 + globalColorTableSize; + case "image/gif": { + // Based on https://gist.github.com/zakirt/faa4a58cec5a7505b10e3686a226f285 + // More info at http://www.matthewflickinger.com/lab/whatsinagif/bits_and_bytes.asp + const dv = new DataView(await blob.arrayBuffer(), 10); + + const globalColorTable = dv.getUint8(0); + let globalColorTableSize = 0; + // check first bit, if 0, then we don't have a Global Color Table + if (globalColorTable & 0x80) { + // grab the last 3 bits, to calculate the global color table size -> RGB * 2^(N+1) + // N is the value in the last 3 bits. + globalColorTableSize = 3 * Math.pow(2, (globalColorTable & 0x7) + 1); + } - const extensionIntroducer = dv.getUint8(offset); - const graphicsControlLabel = dv.getUint8(offset + 1); - let delayTime = 0; + // move on to the Graphics Control Extension + const offset = 3 + globalColorTableSize; - // Graphics Control Extension section is where GIF animation data is stored - // First 2 bytes must be 0x21 and 0xF9 - if ((extensionIntroducer & 0x21) && (graphicsControlLabel & 0xF9)) { - // skip to the 2 bytes with the delay time - delayTime = dv.getUint16(offset + 4); + const extensionIntroducer = dv.getUint8(offset); + const graphicsControlLabel = dv.getUint8(offset + 1); + let delayTime = 0; + + // Graphics Control Extension section is where GIF animation data is stored + // First 2 bytes must be 0x21 and 0xF9 + if ((extensionIntroducer & 0x21) && (graphicsControlLabel & 0xF9)) { + // skip to the 2 bytes with the delay time + delayTime = dv.getUint16(offset + 4); + } + + return !!delayTime; } - return !!delayTime; + case "image/png": + case "image/apng": { + // Based on https://stackoverflow.com/a/68618296 + const arr = await blob.arrayBuffer(); + if (arrayHasDiff([ + 0x89, + 0x50, 0x4E, 0x47, + 0x0D, 0x0A, + 0x1A, + 0x0A, + ], Array.from(arrayBufferRead(arr, 0, 8)))) { + return false; + } + + for (let i = 8; i < blob.size;) { + const length = arrayBufferReadInt(arr, i); + i += 4; + const type = arrayBufferReadStr(arr, i, 4); + i += 4; + + switch (type) { + case "acTL": + return true; + case "IDAT": + return false; + } + i += length + 4; + } + break; + } } return false; diff --git a/src/utils/blobs.ts b/src/utils/blobs.ts index bf7251b61f8..892920d51f7 100644 --- a/src/utils/blobs.ts +++ b/src/utils/blobs.ts @@ -52,6 +52,7 @@ const ALLOWED_BLOB_MIMETYPES = [ 'image/jpeg', 'image/gif', 'image/png', + 'image/apng', 'image/webp', 'video/mp4', diff --git a/test/Image-test.ts b/test/Image-test.ts index 9bd933a3a10..41a63eb0ce3 100644 --- a/test/Image-test.ts +++ b/test/Image-test.ts @@ -29,7 +29,10 @@ describe("Image", () => { expect(mayBeAnimated("image/webp")).toBeTruthy(); }); it("image/png", async () => { - expect(mayBeAnimated("image/png")).toBeFalsy(); + expect(mayBeAnimated("image/png")).toBeTruthy(); + }); + it("image/apng", async () => { + expect(mayBeAnimated("image/apng")).toBeTruthy(); }); it("image/jpeg", async () => { expect(mayBeAnimated("image/jpeg")).toBeFalsy(); @@ -37,25 +40,36 @@ describe("Image", () => { }); describe("blobIsAnimated", () => { - const animatedGif = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "animated-logo.gif"))]); - const animatedWebp = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "animated-logo.webp"))]); - const staticGif = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "static-logo.gif"))]); - const staticWebp = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "static-logo.webp"))]); - it("Animated GIF", async () => { - expect(await blobIsAnimated("image/gif", animatedGif)).toBeTruthy(); + const img = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "animated-logo.gif"))]); + expect(await blobIsAnimated("image/gif", img)).toBeTruthy(); }); it("Static GIF", async () => { - expect(await blobIsAnimated("image/gif", staticGif)).toBeFalsy(); + const img = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "static-logo.gif"))]); + expect(await blobIsAnimated("image/gif", img)).toBeFalsy(); }); it("Animated WEBP", async () => { - expect(await blobIsAnimated("image/webp", animatedWebp)).toBeTruthy(); + const img = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "animated-logo.webp"))]); + expect(await blobIsAnimated("image/webp", img)).toBeTruthy(); }); it("Static WEBP", async () => { - expect(await blobIsAnimated("image/webp", staticWebp)).toBeFalsy(); + const img = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "static-logo.webp"))]); + expect(await blobIsAnimated("image/webp", img)).toBeFalsy(); + }); + + it("Animated PNG", async () => { + const img = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "animated-logo.apng"))]); + expect(await blobIsAnimated("image/png", img)).toBeTruthy(); + expect(await blobIsAnimated("image/apng", img)).toBeTruthy(); + }); + + it("Static PNG", async () => { + const img = new Blob([fs.readFileSync(path.resolve(__dirname, "images", "static-logo.png"))]); + expect(await blobIsAnimated("image/png", img)).toBeFalsy(); + expect(await blobIsAnimated("image/apng", img)).toBeFalsy(); }); }); }); diff --git a/test/images/animated-logo.apng b/test/images/animated-logo.apng new file mode 100644 index 0000000000000000000000000000000000000000..0252cf43bdafdd8db2272090234a360c4ffb2672 GIT binary patch literal 47488 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 literal 0 HcmV?d00001 diff --git a/test/images/static-logo.png b/test/images/static-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..14b8f0ec31c1e40057096b2dada6b2da1a351347 GIT binary patch literal 1611 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yF%}28J29*~C-V}>aY>EvO!M_+ z&;qhK7#Q0#8CXC{fLIEM85o!sFfuR$X-1IP0w%cZi3Q9EHb~*@Xxpg_46Iu{T^vIy z7~kG~=qH`Y!+POe*8TrecT0x~>MS~|@=^BD>6h|cXKu{zf36kcakgLnpMZix0|O%y z3&+4PG{k?rc76W4^7+oV(mw0sD|XlXmHWRgy0ejkhl}Hc!m^-RrvEw;C;R=mr+t~N zEO+znUi-Gc%R-->j&n3xw`v*(hsQ~!-id+&6M9y!Qe|Qa+T@X(!os5RcvV(sL&FrE zOA?Zdj3;-jic(TgQ0+Exb8~PQ;@Zsibf%|$s=2y2->>pXuU`J#ZyD>o=XTa@KNtMD zma#JW=@sWP|GD1ZzhA%q`@_Ed-*YF~|9|_}_WQTr#ZM2t=Q?pG=)QeLf7sq@JH9@M zTVK89{MUWibEE&RyI#M${~xc>=1;Gi&4Sy;JGPn`cVVD8P=mQxq)W_|K8b>7DVan&*Qc6GOs%jaEv zej_vPffi$B4rjWZNBXy28FTKw-^e?sWO~CKmbGX77#VNwG+%D#dEmWc+QR(5c8Q;k zPmkWKH`(^q`=a|3e{HVPk2tG+;!Kbp|K*A7uTGe{`;}}tcky-Kmp{|4a{yhm|GVOz z@4x!h_k90Xz5f4=_tEuw3g_4Uf7AZ{P2KsIB6oBZSiNlwni6Cke!UT5{OP0m-;_V+ zUd58;2Agvg-<<2p_8(8}dM7erQL0pGoP)zN-%uSPfuYPhP%W}==DffAmv8@GJvsJ| z`Fe%!>CazO0<(bh>kSbO4r2P}FMeiXnq+=0abrV+PTai3&skYi^4A`{5M_ok0b;r>mdKI;Vst0I+Ua82|tP literal 0 HcmV?d00001