Skip to content

Instantly share code, notes, and snippets.

@SoniEx2
Last active August 29, 2015 14:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SoniEx2/c679e771d506210378a5 to your computer and use it in GitHub Desktop.
Save SoniEx2/c679e771d506210378a5 to your computer and use it in GitHub Desktop.

MPNGPNG - Mutli-PNG PNG spec

MPNGPNG v0.22

Authors:

Overview

MPNGPNG is like APNG, but more powerful.

Most stuff here was taken from the APNG spec.

An MPNGPNG image contains siMG (Sub IMaGe) chunks, and may contain a ctLA (ConTroL Animation) chunk, a ctLF (ConTroL Frame) chunk or a ssEC (Sub SECtion) chunk.

MPNGPNG is better than MNG because it lets you shove whole MPNGPNGs in MPNGPNGs, with theoretically no depth limit. The top image color correction applies to the sub images, so you have to chain the color correction stuff, but meh w/e, this was meant to reuse the decoding subroutines for each sub-image anyway.

Terminology

The "default image" is the image described by the standard IDAT chunks, and is the image that is displayed by decoders that do not support MPNGPNG.

The "canvas" is the area on the output device on which the frames are to be displayed. The contents of the canvas are not necessarily available to the decoder. As per the PNG Specification, if a bKGD chunk exists it may be used to fill the canvas if there is no preferable background.

The "output buffer" is a pixel array with dimensions specified by the width and height parameters of the PNG IHDR chunk. Conceptually, each frame is constructed in the output buffer before being composited onto the canvas. The contents of the output buffer are available to the decoder. The corners of the output buffer are mapped to the corners of the canvas.

"Fully transparent black" means red, green, blue and alpha components are all set to zero.

A "frame" is a sub-image that is part of an animation. A "sub-image" may or may not be part of an animation.

For purposes of chunk descriptions, an "unsigned int" shall be a 32-bit unsigned integer in network byte order limited to the range 0 to (2^31)-1; an "unsigned short" shall be a 16-bit unsigned integer in network byte order with the range 0 to (2^16)-1; a "byte" shall be an 8-bit unsigned integer with the range 0 to (2^8)-1.

Error Handling

MPNGPNG is designed to allow incremental display of frames before the entire image has been read. This implies that some errors may not be detected until partway through the animation. It is strongly recommended that when any error is encountered decoders should discard all subsequent frames, stop the animation, and revert to displaying the default image. A decoder which detects an error before the animation has started should display the default image. An error message may be displayed to the user if appropriate.

Structure

A MPNGPNG stream is a normal PNG stream as defined in the PNG Specification, with four additional chunk types describing the animation and providing additional frame data.

To be recognized as a MPNGPNG, at least one valid siMG chunk must appear in the stream. A valid siMG chunk must contain either a ctLF or a ssEC chunk.

A ctLA chunk must appear in the stream when the animation should start. The ctLA structure is described below.

Conceptually, at the beginning of each play the output buffer must be completely initialized to a fully transparent black rectangle, with width and height dimensions from the IHDR chunk.

The default image may be included as the first frame of the animation by the presence of a single ctLF chunk on the main PNG. Otherwise, the default image is not part of the animation.

Subsequent frames are encoded in siMG chunks, which are entire PNG files. Information for each frame about placement and rendering is stored in ctLF and IHDR chunks. The full layout of siMG and ctLF chunks is described below. The IHDR chunk is described in the PNG Specification.

The boundaries of the entire animation are specified by the width and height parameters of the PNG IHDR chunk, regardless of whether the default image is part of the animation. The default image should be appropriately padded with fully transparent pixels if extra space will be needed for later frames.

Each frame is identical for each play, therefore it is safe for applications to cache the frames.

Frame Sequence Numbers

The ctLF chunks have a 4 byte sequence/frame number. [TODO add more relevant stuff here, e.g. frame number or something. the original APNG spec stuff isn't relevant so it was removed.]

ctLA: The Animation Control Chunk

The ctLA chunk is an ancillary chunk as defined in the PNG Specification. It may appear only once, and it also signifies when to start the playback.

The ctLA chunk contains:

| byte | type         | name   | desc                                  |
|  0   | unsigned int | icount | Number of images.                     |
|  4   | unsigned int | lcount | Number of times to loop this MPNGPNG. |

icount indicates the total number of images in the animation. This must equal the number of ctLF chunks. 0 indicates a streaming/live animation or an unspecified number of frames. 1 is a valid value for a single-frame APNG. If this value does not equal the actual number of frames it should be treated as an error.

lcount indicates the number of times that this animation should play; if it is 0, the animation should play indefinitely. If nonzero, the animation should come to rest on the final frame at the end of the last play.

ctLF: The Frame Control Chunk

The ctLF chunk is an ancillary chunk as defined in the PNG Specification.

If the ctLF chunk is present, then the ssEC chunk must not be present.

  • For the default image, if a ctLF chunk is present it must appear on the main PNG.
  • For all other frames, the ctLF chunk must appear inside the siMG.

Exactly one ctLF chunk is required for each frame.

Format:

| byte | type           | name       | desc                                                              |
|  0   | unsigned int   | seqnum     | Frame sequence number.                                            |
|  4   | unsigned int   | x_offset   | X position at which to render this frame                          |
|  8   | unsigned int   | y_offset   | Y position at which to render this frame                          |
|  12  | unsigned short | delay_num  | Frame delay fraction numerator                                    |
|  14  | unsigned short | delay_den  | Frame delay fraction denominator                                  |
|  16  | byte           | dispose_op | Type of frame area disposal to be done after rendering this frame |
|  17  | byte           | blend_op   | Type of frame area rendering for this frame                       |

The frame must be rendered within the region defined by x_offset, y_offset, width, and height, the latter 2 which are available from the IHDR chunk. The offsets must be non-negative, the dimensions must be positive, and the region may not fall outside of the default image. This means that for the default image x_offset and y_offset must be 0 (see below).

Constraints on frame regions:

`x_offset` >= 0
`y_offset` >= 0
`width`    > 0
`height`   > 0
`x_offset` + `width`  <= default image `IHDR` width
`y_offset` + `height` <= default image `IHDR` height

The delay_num and delay_den parameters together specify a fraction indicating the time to display the current frame, in seconds. If the denominator is 0, it is to be treated as if it were 100 (that is, delay_num then specifies 1/100ths of a second). If the the value of the numerator is 0 the decoder should render the next frame as quickly as possible, though viewers may impose a reasonable lower bound.

Frame timings should be independent of the time required for decoding and display of each frame, so that animations will run at the same rate regardless of the performance of the decoder implementation.

dispose_op specifies how the output buffer should be changed at the end of the delay (before rendering the next frame).

Valid values for dispose_op are:

| value | name                          |
|   0   | MPNGPNG_DISPOSE_OP_NONE       |
|   1   | MPNGPNG_DISPOSE_OP_BACKGROUND |
|   2   | MPNGPNG_DISPOSE_OP_PREVIOUS   |
  • MPNGPNG_DISPOSE_OP_NONE: no disposal is done on this frame before rendering the next; the contents of the output buffer are left as is.
  • MPNGPNG_DISPOSE_OP_BACKGROUND: the frame's region of the output buffer is to be cleared to fully transparent black before rendering the next frame.
  • MPNGPNG_DISPOSE_OP_PREVIOUS: the frame's region of the output buffer is to be reverted to the previous contents before rendering the next frame.

If the first ctLF chunk uses a dispose_op of MPNGPNG_DISPOSE_OP_PREVIOUS, it should be treated as APNG_DISPOSE_OP_BACKGROUND.

blend_op specifies whether the frame is to be alpha blended into the current output buffer content, or whether it should completely replace its region in the output buffer.

Valid values for blend_op are:

| value | name                    |
|   0   | MPNGPNG_BLEND_OP_SOURCE |
|   1   | MPNGPNG_BLEND_OP_OVER   |

If blend_op is MPNGPNG_BLEND_OP_SOURCE all color components of the frame, including alpha, overwrite the current contents of the frame's output buffer region. If blend_op is MPNGPNG_BLEND_OP_OVER the frame should be composited onto the output buffer based on its alpha, using a simple OVER operation as described in the "Alpha Channel Processing" section of the PNG specification [PNG-1.2]. Note that the second variation of the sample code is applicable.

Note that for the first frame the two blend modes are functionally equivalent due to the clearing of the output buffer at the beginning of each play.

The ctLF chunk corresponding to the default image, if it exists, has these restrictions:

  • The x_offset and y_offset fields must be 0.

As noted earlier, the output buffer must be completely initialized to fully transparent black at the beginning of each play. This is to ensure that each play of the animation will be identical. Decoders are free to avoid an explicit clear step as long as the result is guaranteed to be identical. For example, if the default image is included in the animation, and uses a blend_op of APNG_BLEND_OP_SOURCE, clearing is not necessary because the entire output buffer will be overwritten.

Note, at the end of the ctLF chunk is a 32-bit CRC checksum. The checksum is calculated using the ctLF chunk and includes the 'ctLF' bytes.

siMG: The Sub-Image Chunk

The siMG chunk contains a PNG file.

Exactly one siMG chunk is required for each frame.

Format:

| byte | type         | name     | desc                                           |
|  0   | unsigned int | streamid | The stream ID.                                 |
|  4   | unsigned int | part     | The part ID. Specifies order of concatenation. |
|  4   | X bytes      | pngdat   | Whole PNG file. Can be split across chunks.    |

Chunks with the same streamid get concatenated together, ordered by part.

ssEC: The Sub-Section Chunk

The ssEC chunk contains information about how to render this sub-image onto the main image. It must be ignored for the top-level image.

If the ssEC chunk is present, then the ctLF chunk must not be present.

Format:

| byte | type         | name     | desc                                               |
|  0   | unsigned int | x_offset | First X position at which to render this sub-image |
|  4   | unsigned int | y_offset | First Y position at which to render this sub-image |
|  .   |      .       |    .     |                       .                            |
|  .   |      .       |    .     |                       .                            |
|  .   |      .       |    .     |                       .                            |

The sub-image must be rendered within the region defined by x_offset, y_offset, width, and height, the latter 2 which are available from the IHDR chunk. The offsets must be non-negative, the dimensions must be positive, and the region may not fall outside of the default image.

Rationale

You can't put PNGs inside PNGs, but you can put MPNGPNGs inside MPNGPNGs.

The chunk names were chosen so to not conflict with APNG's chunk names. This means you can put an APNG inside a MPNGPNG.

You can display multiple animated MPNGPNGs inside a single MPNGPNG.

Version history

MFPNG 0.10 - Initial revision

MPNGPNG 0.11 - Rename from MFPNG to MPNGPNG, add support for "compositing" images, correct some "the following" to "this".

MPNGPNG 0.20 - siMG chunks shouldn't have frame number, that should be on the ctLF chunks, so you don't have to come up with random frame numbers to be able to use ssEC chunks/compositing images.

MPNGPNG 0.21 - Allow siMG to be rendered multiple times onto super-image.

MPNGPNG 0.22 - Streaming.

Copyright

This work is licensed under the Creative Commons Attribution-ShareAlike license (CC-BY-SA) version 3.0 or any later version.

APNG Spec: https://wiki.mozilla.org/APNG_Specification APNG Spec Contributors: https://wiki.mozilla.org/index.php?title=APNG_Specification&action=history

Why use MPNGPNG instead of APNG/MNG?

  1. Composing a single image out of multiple images

MPNGPNG lets you draw e.g. 4 images in a single frame, to make a frame with the 4 images side-by-side. (this isn't very useful on its own, but see below)

  1. Lazy multi-animation syncing

Say you want to put 4 animations running in parallel on the same image. With GIF or APNG, you'd have to find the "sync point" where all 4 animations sync up, e.g.:

Animation 1: |  |  |  |  |  |  |  |  |
Animation 2: |   |   |   |   |   |   |
Animation 3: |     |     |     |     |
Animation 4: |       |       |       |

With MPNGPNG, you can include the 4 animations as 4 separate animations and tell it to compose an image with the 4 animations running in parallel, so that you only have:

Animation 1: |  |
Animation 2: |   |
Animation 3: |     |
Animation 4: |       |

It's pretty obvious that MPNGPNG would be using less space!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment