RFC 2083
PNG (Portable Network Graphics) Specification Version 1.0
RFC 2083: PNG (Portable Network Graphics) Specification, Version 1.0 ๐
okay bestie, this is RFC 2083 and it's the PNG spec, version 1.0 ๐ผ๏ธโจ it's from the Network Working Group, written by T. Boutell and friends at Boutell.Com, Inc., dated March 1997. it's Informational, so it's just info for the Internet community and NOT an Internet standard, no cap ๐ it doesn't say it obsoletes or updates any other RFC, so it's just vibing on its own ๐ฆ
Status of This Memo ๐
this doc is just information for the Internet crowd, not a standard of any kind ๐ซถ anyone can distribute it, and distribution is totally unlimited โจ
IESG Note
the IESG (the Internet Engineering Steering Group) takes no position on whether any Intellectual Property Rights statements in this document are valid ๐
Abstract ๐
okay so PNG (Portable Network Graphics) is an extensible file format for storing raster images losslessly, portably, and well-compressed ๐ญ it's a patent-free replacement for GIF, and it can also replace a lot of the common TIFF uses ๐ it handles indexed-color, grayscale, and truecolor images, plus an optional alpha channel โจ sample depths go from 1 to 16 bits ๐ฅ
PNG is built for online viewing (think the World Wide Web) so it's fully streamable with a progressive display option ๐ง it's also super robust: it does full file integrity checking and has simple detection of common transmission errors ๐ก๏ธ and it can store gamma and chromaticity data so colours match better across different platforms ๐จ
this spec defines the Internet Media Type image/png ๐ฆ
Table of Contents ๐
1. Introduction .................................................. 4
2. Data Representation ........................................... 5
2.1. Integers and byte order .................................. 5
2.2. Color values ............................................. 6
2.3. Image layout ............................................. 6
2.4. Alpha channel ............................................ 7
2.5. Filtering ................................................ 8
2.6. Interlaced data order .................................... 8
2.7. Gamma correction ......................................... 10
2.8. Text strings ............................................. 10
3. File Structure ................................................ 11
3.1. PNG file signature ....................................... 11
3.2. Chunk layout ............................................. 11
3.3. Chunk naming conventions ................................. 12
3.4. CRC algorithm ............................................ 15
4. Chunk Specifications .......................................... 15
4.1. Critical chunks .......................................... 15
4.1.1. IHDR Image header .................................. 15
4.1.2. PLTE Palette ....................................... 17
4.1.3. IDAT Image data .................................... 18
4.1.4. IEND Image trailer ................................. 19
4.2. Ancillary chunks ......................................... 19
4.2.1. bKGD Background color .............................. 19
4.2.2. cHRM Primary chromaticities and white point ........ 20
4.2.3. gAMA Image gamma ................................... 21
4.2.4. hIST Image histogram ............................... 21
4.2.5. pHYs Physical pixel dimensions ..................... 22
4.2.6. sBIT Significant bits .............................. 22
4.2.7. tEXt Textual data .................................. 24
4.2.8. tIME Image last-modification time .................. 25
4.2.9. tRNS Transparency .................................. 26
4.2.10. zTXt Compressed textual data ...................... 27
4.3. Summary of standard chunks ............................... 28
4.4. Additional chunk types ................................... 29
5. Deflate/Inflate Compression ................................... 29
6. Filter Algorithms ............................................. 31
6.1. Filter types ............................................. 31
6.2. Filter type 0: None ...................................... 32
6.3. Filter type 1: Sub ....................................... 33
6.4. Filter type 2: Up ........................................ 33
6.5. Filter type 3: Average ................................... 34
6.6. Filter type 4: Paeth...................................... 35
7. Chunk Ordering Rules .......................................... 36
7.1. Behavior of PNG editors .................................. 37
7.2. Ordering of ancillary chunks ............................. 38
7.3. Ordering of critical chunks .............................. 38
8. Miscellaneous Topics .......................................... 39
8.1. File name extension ...................................... 39
8.2. Internet media type ...................................... 39
8.3. Macintosh file layout .................................... 39
8.4. Multiple-image extension ................................. 39
8.5. Security considerations .................................. 40
9. Recommendations for Encoders .................................. 41
9.1. Sample depth scaling ..................................... 41
9.2. Encoder gamma handling ................................... 42
9.3. Encoder color handling ................................... 45
9.4. Alpha channel creation ................................... 47
9.5. Suggested palettes ....................................... 48
9.6. Filter selection ......................................... 49
9.7. Text chunk processing .................................... 49
9.8. Use of private chunks .................................... 50
9.9. Private type and method codes ............................ 51
10. Recommendations for Decoders ................................. 51
10.1. Error checking .......................................... 52
10.2. Pixel dimensions ........................................ 52
10.3. Truecolor image handling ................................ 52
10.4. Sample depth rescaling .................................. 53
10.5. Decoder gamma handling .................................. 54
10.6. Decoder color handling .................................. 56
10.7. Background color ........................................ 57
10.8. Alpha channel processing ................................ 58
10.9. Progressive display ..................................... 62
10.10. Suggested-palette and histogram usage .................. 63
10.11. Text chunk processing .................................. 64
11. Glossary ..................................................... 65
12. Appendix: Rationale .......................................... 69
12.1. Why a new file format? .................................. 69
12.2. Why these features? ..................................... 70
12.3. Why not these features? ................................. 70
12.4. Why not use format X? ................................... 72
12.5. Byte order .............................................. 73
12.6. Interlacing ............................................. 73
12.7. Why gamma? .............................................. 73
12.8. Non-premultiplied alpha ................................. 75
12.9. Filtering ............................................... 75
12.10. Text strings ........................................... 76
12.11. PNG file signature ..................................... 77
12.12. Chunk layout ........................................... 77
12.13. Chunk naming conventions ............................... 78
12.14. Palette histograms ..................................... 80
13. Appendix: Gamma Tutorial ..................................... 81
14. Appendix: Color Tutorial ..................................... 89
15. Appendix: Sample CRC Code .................................... 94
16. Appendix: Online Resources ................................... 96
17. Appendix: Revision History ................................... 96
18. References ................................................... 97
19. Credits ......................................................100
1. Introduction โจ
okay so PNG is the image format that is giving portable, legally unencumbered, well-compressed, and super well-specified ๐ It's a standard for lossless bitmapped image files, so nothing gets wrecked when you save it. ๐
Originally the whole point was to replace GIF ๐ฆ but it ended up with some really cute new features that GIF never had, and they don't cost developers much at all. โจ
Here's the GIF stuff PNG kept, bestie:
- Indexed-color images of up to 256 colors. ๐
- Streamability: files can be read and written serially, so the format can work as a communications protocol for generating and displaying images on the fly. ๐ง
- Progressive display: a properly prepared image can show up while it's still arriving over a link, giving you a low-res version super fast, then the details slowly fill in. ๐ธ
- Transparency: parts of the image can be marked see-through, so the picture isn't stuck being a rectangle. ๐ซถ
- Ancillary information: text comments and other extra data can be stored right in the file. ๐
- Complete hardware and platform independence. ๐ฅ
- Effective, 100% lossless compression. ๐
And the new stuff that GIF couldn't do, which is literally the main character energy:
- Truecolor images of up to 48 bits per pixel. โจ
- Grayscale images of up to 16 bits per pixel. ๐ฉถ
- A full alpha channel (general transparency masks). ๐ฆ
- Image gamma information, so images can display with the right brightness and contrast no matter what machine made them or what machine is showing them. Iconic. ๐
- Reliable, straightforward detection of file corruption. ๐ก๏ธ
- Faster first impression in progressive display mode. ๐จ
PNG is designed to be:
- Simple and portable: developers SHOULD be able to implement PNG easily. ๐
- Legally unencumbered: as far as the PNG authors know, no algorithms under legal challenge are used. (A lot of effort went into checking this, so it's not just vibes ๐ญ)
- Well compressed: both indexed-color and truecolor images get compressed about as well as in any other widely used lossless format, and in most cases even better. ๐
- Interchangeable: any standard-conforming PNG decoder MUST read all conforming PNG files. No leaving anyone on read. ๐ซถ
- Flexible: the format allows for future extensions and private add-ons without messing up the interchangeability of basic PNG. ๐
- Robust: the design supports full file integrity checking, plus simple, quick detection of common transmission errors. ๐ฅ
The main part of this spec defines the file format and gives recommendations for how encoders and decoders should behave. There's an appendix with the rationale behind a lot of the design choices. It's not part of the formal spec, but it genuinely helps implementors understand what's going on, so read it! Cross-references in the main text point to the relevant rationale. There are more appendixes too, also not formal, with tutorials on gamma and color theory plus other extra goodies. ๐
In this specification, the word "must" indicates a mandatory requirement, while "should" indicates recommended behavior.
See Rationale: Why a new file format? (Section 12.1), Why these features? (Section 12.2), Why not these features? (Section 12.3), Why not use format X? (Section 12.4).
Pronunciation
PNG is pronounced "ping". ๐โจ
2. Data Representation ๐ง
This chapter covers the basic data representations used in PNG files, plus how the image data is expected to be represented. Let's get into it! ๐
2.1. Integers and byte order
Any integer that needs more than one byte MUST be in network byte order: the most significant byte comes first, then the less significant bytes in descending order of significance. So it's MSB LSB for two-byte integers and B3 B2 B1 B0 for four-byte integers. ๐ข The highest bit (value 128) of a byte is numbered bit 7, and the lowest bit (value 1) is numbered bit 0. Values are unsigned unless otherwise noted. Values that are explicitly noted as signed are represented in two's complement notation. ๐ธ
See Rationale: Byte order (Section 12.5).
2.2. Color values ๐
Colors can be represented with either grayscale or RGB (red, green, blue) sample data. Grayscale data represents luminance. RGB data represents calibrated color information if the cHRM chunk is present, or uncalibrated device-dependent color if cHRM is absent. ๐จ All color values range from zero (black) up to the most intense value at the maximum for the sample depth. Also, the maximum value at a given sample depth is (2^sampledepth)-1, NOT 2^sampledepth, so don't mix those up, bestie ๐
Sample values aren't necessarily linear. The gAMA chunk specifies the gamma characteristic of the source device, and viewers are strongly encouraged to compensate properly. ๐ See Gamma correction (Section 2.7).
Source data with a precision that PNG doesn't directly support (for example, 5 bit/sample truecolor) MUST be scaled up to the next higher supported bit depth. This scaling is reversible with no loss of data, and it cuts down on the number of cases decoders have to cope with. Iconic efficiency ๐ See Recommendations for Encoders: Sample depth scaling (Section 9.1) and Recommendations for Decoders: Sample depth rescaling (Section 10.4).
2.3. Image layout ๐ผ๏ธ
Conceptually, a PNG image is a rectangular pixel array. Pixels go left-to-right within each scanline, and scanlines go top-to-bottom. ๐ (For progressive display, the data might actually be sent in a different order. See Interlaced data order, Section 2.6.) The size of each pixel is set by the bit depth, which is the number of bits per sample in the image data.
There are three types of pixel supported:
- An indexed-color pixel is a single sample that's an index into a supplied palette. The image bit depth sets the maximum number of palette entries, but NOT the color precision within the palette. ๐จ
- A grayscale pixel is a single sample that's a grayscale level, where zero is black and the largest value for the bit depth is white. โฌ
- A truecolor pixel is three samples: red (zero is black, max is red) comes first, then green (zero is black, max is green), then blue (zero is black, max is blue). The bit depth specifies the size of each sample, NOT the total pixel size. ๐
Grayscale and truecolor pixels can optionally include an alpha sample too, which the next section covers. ๐ฆ
Pixels are always packed into scanlines with no wasted bits between them. Pixels smaller than a byte never cross byte boundaries. They get packed into bytes with the leftmost pixel in the high-order bits and the rightmost pixel in the low-order bits. Permitted bit depths and pixel types are restricted so the packing stays simple and efficient in every case. ๐งฉ
PNG only allows multi-sample pixels with 8- and 16-bit samples, so the samples of one pixel are never packed into one byte. 16-bit samples are stored in network byte order (MSB first). ๐
Scanlines ALWAYS begin on byte boundaries. When pixels have fewer than 8 bits and the scanline width isn't evenly divisible by the number of pixels per byte, the low-order bits in the last byte of each scanline get wasted. What's in those wasted bits is unspecified, so don't read anything into them ๐ซ
Every scanline also gets an extra "filter type" byte added to the beginning (see Filtering, Section 2.5). The filter type byte is NOT considered part of the image data, but it IS included in the datastream sent to the compression step. ๐
2.4. Alpha channel ๐ซง
An alpha channel represents transparency information on a per-pixel basis, and it can be included in grayscale and truecolor PNG images. ๐ฆ
An alpha value of zero means fully transparent, and a value of (2^bitdepth)-1 means a fully opaque pixel. Values in between are partially transparent pixels that can be combined with a background image to make a composite image. (So technically alpha is the degree of opacity of the pixel, but most people call it transparency info, not opacity info, and we're sticking with that custom here ๐
)
Alpha channels can go with images that have 8 or 16 bits per sample, but NOT with images that have fewer than 8 bits per sample. Alpha samples use the same bit depth as the image samples. The alpha sample for each pixel is stored right after that pixel's grayscale or RGB samples. ๐ง
The color values stored for a pixel are NOT affected by the alpha value assigned to it. This is sometimes called "unassociated" or "non-premultiplied" alpha. ๐ (Another common technique is to store sample values premultiplied by the alpha fraction. In effect, that image is already composited against a black background. PNG does NOT use premultiplied alpha.)
Transparency control is also possible without paying the storage cost of a full alpha channel. In an indexed-color image, an alpha value can be defined for each palette entry. In grayscale and truecolor images, a single pixel value can be marked as "transparent". These techniques are controlled by the tRNS ancillary chunk type. ๐ธ
If there's no alpha channel and no tRNS chunk, all pixels in the image are treated as fully opaque. ๐งฑ
Viewers can support transparency control partially, or not at all. No judgment, that's valid ๐ซถ
See Rationale: Non-premultiplied alpha (Section 12.8), Recommendations for Encoders: Alpha channel creation (Section 9.4), and Recommendations for Decoders: Alpha channel processing (Section 10.8).
2.5. Filtering ๐ง
PNG lets the image data get filtered before compression. Filtering can improve how compressible the data is. The filter step doesn't reduce the size of the data itself. Every PNG filter is strictly lossless, so nothing gets lost ๐ฅ
PNG defines several filter algorithms, including "None", which means no filtering. The filter algorithm is specified for each scanline by a filter type byte that comes before the filtered scanline in the precompression datastream. A smart encoder can switch filters from one scanline to the next. Picking which filter to use is up to the encoder, so it's the encoder's call ๐
See Filter Algorithms (Chapter 6) and Rationale: Filtering (Section 12.9).
2.6. Interlaced data order ๐
A PNG image can be stored in interlaced order so it can be shown progressively. The whole point is letting images "fade in" while they're being displayed on the fly. โจ Interlacing makes the file a little bigger on average, but the user gets a meaningful display way sooner, which is worth it honestly ๐ญ
Note that decoders are REQUIRED to be able to read interlaced images, whether or not they actually do progressive display. So even if your decoder doesn't do the fade-in thing, it still has to be able to read them ๐ซถ
With interlace method 0, pixels are stored sequentially from left to right, and scanlines sequentially from top to bottom. No interlacing, just straight-up vibes ๐
Okay so interlacing, bestie, it's literally the coolest trick ๐ โจ Interlace method 1 is called Adam7 after its creator, Adam M. Costello ๐. It sends the image in seven separate passes, and each pass carries a different slice of the pixels ๐ฆ. Which pass a pixel rides in is decided by copying this 8-by-8 pattern over the whole image, starting from the upper left corner ๐:
1 6 4 6 2 6 4 6
7 7 7 7 7 7 7 7
5 6 5 6 5 6 5 6
7 7 7 7 7 7 7 7
3 6 4 6 3 6 4 6
7 7 7 7 7 7 7 7
5 6 5 6 5 6 5 6
7 7 7 7 7 7 7 7
Inside each pass, pixels go left to right along a scanline, and the chosen scanlines go top to bottom ๐ธ. For example, pass 2 holds pixels 4, 12, 20, etc. from scanlines 0, 8, 16, etc., counting from 0,0 at the upper left corner ๐ง. The last pass is the whole of scanlines 1, 3, 5, etc. ๐
Each pass is laid out as if it were a complete image of its own size ๐. So if the full image is 16 by 16 pixels, pass 3 has two scanlines, and each of those has four pixels ๐ซถ. When pixels are smaller than 8 bits, every scanline gets padded so it fills a whole number of bytes (see Image layout, Section 2.3) ๐. Filtering works on this smaller image the usual way, and a filter type byte goes in front of each scanline (see Filter Algorithms, Chapter 6) โญ. The reason the order is set up like this is that every scanline sent in a pass has the same number of pixels, and that matters for some of the filters to work properly ๐.
Caution: If the image has fewer than five columns or fewer than five rows, some passes end up completely empty ๐. Encoders and decoders have to handle that case correctly. Filter type bytes only show up on scanlines that actually have pixels, so an empty pass has no filter type bytes at all ๐ญ.
See Rationale: Interlacing (Section 12.6) and Recommendations for Decoders: Progressive display (Section 10.9) ๐.
2.7. Gamma correction ๐
PNG images can carry a gAMA chunk that describes the image's gamma relative to the original scene ๐ธ. Display programs are really encouraged to use that info, along with what they know about their display device and the room lighting ๐ . The goal is to show the viewer something that matches what the original author saw as closely as possible ๐. If you're not already familiar with gamma stuff, check out Gamma Tutorial (Chapter 13) ๐ง.
Gamma correction doesn't touch the alpha channel, if there is one ๐ฆ. Alpha samples always stand for a linear fraction of full opacity ๐.
For high-precision work, the cHRM chunk can pin down the exact chromaticity of the RGB data ๐ฅ. That gives you more accurate color matching than gamma correction does on its own ๐. If color representation is new to you, head to Color Tutorial (Chapter 14) ๐ซถ.
See Rationale: Why gamma? (Section 12.7), Recommendations for Encoders: Encoder gamma handling (Section 9.2), and Recommendations for Decoders: Decoder gamma handling (Section 10.5) โจ.
2.8. Text strings ๐ฌ
A PNG file can store text about the image, like a description or a copyright notice ๐ธ. Keywords tell you what each text string is for ๐.
ISO 8859-1 (Latin-1) is the character set recommended for text strings [ISO-8859] ๐. It's a superset of 7-bit ASCII, so it's giving full coverage of the basics ๐.
Character codes that aren't defined in Latin-1 should not be used, because they don't have a platform-independent meaning ๐. If one shows up in a PNG text string anyway, its meaning will vary between platforms and decoders ๐ญ. Some systems might not even display every Latin-1 character, but most modern systems can ๐.
Compressed text is also provided for ๐ง.
See Rationale: Text strings (Section 12.10) ๐
3. File Structure ๐
okay so a PNG file is basically a PNG signature followed by a whole line of chunks ๐ this chapter covers the signature and the basic properties of chunks. the individual chunk types get their own chapter next, so stay tuned bestie ๐ฆ
3.1. PNG file signature
the first eight bytes of a PNG file always contain these (decimal) values ๐
137 80 78 71 13 10 26 10
this signature tells you the rest of the file holds one single PNG image, made of a series of chunks that begins with an IHDR chunk and ends with an IEND chunk. main character energy ๐
See Rationale: PNG file signature (Section 12.11).
3.2. Chunk layout
every chunk is made of four parts ๐ง
Length A 4-byte unsigned integer giving the number of bytes in the chunk's data field. the length only counts the data field, not itself, the chunk type code, or the CRC ๐ zero is a valid length. encoders and decoders SHOULD treat the length as unsigned, but its value MUST NOT exceed (2^31)-1 bytes.
Chunk Type A 4-byte chunk type code. for easy reading and examining PNG files, type codes are restricted to uppercase and lowercase ASCII letters (A-Z and a-z, or 65-90 and 97-122 decimal). however, encoders and decoders MUST treat the codes as fixed binary values, not character strings ๐ฅ it would NOT be correct to write the type code IDAT using the EBCDIC equivalents of those letters ๐ additional naming conventions for chunk types get covered in the next section.
Chunk Data The data bytes that fit the chunk type, if any. this field can be zero length โจ
CRC A 4-byte CRC (Cyclic Redundancy Check) calculated on the preceding bytes in the chunk, including the chunk type code and chunk data fields, but NOT including the length field. the CRC is always present, even for chunks with no data ๐ซถ see CRC algorithm (Section 3.4).
the chunk data length can be any number of bytes up to the maximum, so implementors can't assume chunks line up on any boundaries bigger than bytes ๐ธ
chunks can show up in any order, as long as they follow the restrictions for each chunk type. (one big restriction: IHDR MUST appear first and IEND MUST appear last, so the IEND chunk acts as an end-of-file marker ๐) multiple chunks of the same type can show up, but only if that type specifically allows it ๐ฆ
See Rationale: Chunk layout (Section 12.12).
3.3. Chunk naming conventions
okay this section is lowkey my favourite ๐ญ chunk type codes are assigned so a decoder can figure out some properties of a chunk even when it doesn't recognise the type code โจ these rules let PNG be safely and flexibly extended, because a decoder can decide what to do when it runs into an unknown chunk. the naming rules usually don't matter when the decoder DOES recognise the chunk's type ๐
four bits of the type code, namely bit 5 (value 32) of each byte, carry the chunk properties ๐ this means a human can read the assigned properties just by checking whether each letter is uppercase (bit 5 is 0) or lowercase (bit 5 is 1). however, decoders SHOULD test the properties of an unknown chunk by numerically testing the specified bits. checking whether a character is uppercase or lowercase is inefficient, and even incorrect if a locale-specific case definition is used ๐ โโ๏ธ
the property bits are an inherent part of the chunk name, so they're fixed for any chunk type. TEXT and Text are therefore unrelated chunk type codes, NOT the same chunk with different properties ๐ decoders MUST recognise type codes by a simple four-byte literal comparison, and it is incorrect to do case conversion on type codes.
here's what the property bits mean ๐
Ancillary bit: bit 5 of first byte 0 (uppercase) = critical, 1 (lowercase) = ancillary.
chunks that aren't strictly necessary to meaningfully display the contents of the file are called "ancillary" chunks ๐ง a decoder that runs into an unknown chunk with the ancillary bit set to 1 can safely ignore the chunk and go on to display the image. the time chunk (tIME) is an example of an ancillary chunk โฐ
chunks that are necessary for successfully displaying the file's contents are called "critical" chunks ๐จ a decoder that runs into an unknown chunk with the ancillary bit set to 0 MUST indicate to the user that the image contains information it cannot safely interpret. the image header chunk (IHDR) is an example of a critical chunk ๐
Private bit: bit 5 of second byte 0 (uppercase) = public, 1 (lowercase) = private.
a public chunk is one that's part of the PNG specification or is registered in the list of PNG special-purpose public chunk types ๐ applications can also define private (unregistered) chunks for their own purposes ๐ the names of private chunks MUST have a lowercase second letter, while public chunks will always get names with uppercase second letters. decoders do not need to test this bit, since it has no functional significance. it's just an admin convenience so public and private chunk names won't clash ๐ซ see Additional chunk types (Section 4.4) and Recommendations for Encoders: Use of private chunks (Section 9.8).
Reserved bit: bit 5 of third byte MUST be 0 (uppercase) in files conforming to this version of PNG.
the case of the third letter in the chunk name is reserved for possible future expansion ๐ฎ right now, all chunk names MUST have uppercase third letters. (decoders SHOULD NOT complain about a lowercase third letter, though, because a future version of the PNG specification could give this bit a meaning. it's enough to treat a chunk with a lowercase third letter like any other unknown chunk type ๐)
Safe-to-copy bit: bit 5 of fourth byte 0 (uppercase) = unsafe to copy, 1 (lowercase) = safe to copy.
this property bit isn't of interest to pure decoders, but PNG editors (programs that modify PNG files) need it ๐ ๏ธ it defines how unrecognised chunks should be handled in a file that's being modified.
if a chunk's safe-to-copy bit is 1, the chunk may be copied to a modified PNG file whether or not the software recognises the chunk type, and regardless of how much the file was modified ๐ฆ
if a chunk's safe-to-copy bit is 0, it means the chunk depends on the image data. if the program has made any changes to critical chunks, including adding, modifying, deleting, or reordering them, then unrecognised unsafe chunks MUST NOT be copied to the output PNG file. (of course, if the program DOES recognise the chunk, it can choose to output an appropriately modified version ๐)
a PNG editor is always allowed to copy all unrecognised chunks if it has only added, deleted, modified, or reordered ancillary chunks. this means it's not okay for ancillary chunks to depend on other ancillary chunks ๐
PNG editors that don't recognise a critical chunk MUST report an error and refuse to process that PNG file at all ๐ the safe/unsafe mechanism is meant for ancillary chunks. the safe-to-copy bit will always be 0 for critical chunks.
rules for PNG editors get more detail in Chunk Ordering Rules (Chapter 7) ๐
for example, the hypothetical chunk type name "bLOb" has these property bits ๐ซง
bLOb <-- 32 bit chunk type code represented in text form
||||
|||+- Safe-to-copy bit is 1 (lower case letter; bit 5 is 1)
||+-- Reserved bit is 0 (upper case letter; bit 5 is 0)
|+--- Private bit is 0 (upper case letter; bit 5 is 0)
+---- Ancillary bit is 1 (lower case letter; bit 5 is 1)
so this name represents an ancillary, public, safe-to-copy chunk ๐ฅฐ
See Rationale: Chunk naming conventions (Section 12.13).
3.4. CRC algorithm
chunk CRCs are calculated with standard CRC methods that use pre and post conditioning, as defined by ISO 3309 [ISO-3309] or ITU-T V.42 [ITU-V42] ๐งฎ the CRC polynomial is ๐ซ
x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1
the 32-bit CRC register starts out as all 1's, and then the data from each byte is processed from the least significant bit (1) to the most significant bit (128) ๐ข once all the data bytes are processed, the CRC register gets inverted (its ones complement is taken). this value is stored in the file MSB first ๐ for splitting into bytes and ordering, the least significant bit of the 32-bit CRC is defined as the coefficient of the x^31 term ๐ค
practical CRC calculation always uses a precalculated table to speed things up a LOT โก see Sample CRC Code (Chapter 15).
4. Chunk Specifications ๐
this chapter defines the standard types of PNG chunks ๐ฆ
4.1. Critical chunks
all implementations MUST understand and successfully render the standard critical chunks ๐ช a valid PNG image MUST contain an IHDR chunk, one or more IDAT chunks, and an IEND chunk ๐ซถ
4.1.1. IHDR Image header
the IHDR chunk MUST appear FIRST ๐ฅ it contains these fields ๐
Width: 4 bytes
Height: 4 bytes
Bit depth: 1 byte
Color type: 1 byte
Compression method: 1 byte
Filter method: 1 byte
Interlace method: 1 byte
width and height give the image dimensions in pixels. they're 4-byte integers, and zero is an invalid value ๐ โโ๏ธ the maximum for each is (2^31)-1, so languages that struggle with unsigned 4-byte values can still handle them ๐ธ
bit depth is a single-byte integer giving the number of bits per sample or per palette index (NOT per pixel) โจ valid values are 1, 2, 4, 8, and 16, although not every value is allowed for every color type ๐ง
color type is a single-byte integer that describes how the image data should be interpreted ๐ color type codes are sums of these values: 1 (palette used), 2 (color used), and 4 (alpha channel used). valid values are 0, 2, 3, 4, and 6 ๐
bit depth restrictions for each color type exist to keep implementations simple and to rule out combinations that don't compress well ๐๏ธ decoders MUST support all legal combinations of bit depth and color type ๐ the allowed combinations are:
Color Allowed Interpretation
Type Bit Depths
0 1,2,4,8,16 Each pixel is a grayscale sample.
2 8,16 Each pixel is an R,G,B triple.
3 1,2,4,8 Each pixel is a palette index;
a PLTE chunk MUST appear.
4 8,16 Each pixel is a grayscale sample,
followed by an alpha sample.
6 8,16 Each pixel is an R,G,B triple,
followed by an alpha sample.
okay so the sample depth is the same as the bit depth, EXCEPT for color type 3, where it's always 8 bits, no exceptions ๐ ๐
Compression method is a single-byte integer that tells you how the image data got compressed ๐๏ธ Right now only compression method 0 exists (deflate/inflate with a 32K sliding window). All standard PNG images MUST be compressed with this scheme. The field is there for possible future expansion or proprietary variants, so it's giving "room to grow" ๐ฑ Decoders MUST check this byte and report an error if it holds an unrecognized code. See Deflate/Inflate Compression (Chapter 5) for details.
Filter method is another single-byte integer, and it tells you what preprocessing got applied to the image data before compression โจ Only filter method 0 (adaptive filtering with five basic filter types) is defined so far. Same deal as the compression byte: decoders MUST check this byte and report an error if it holds an unrecognized code. See Filter Algorithms (Chapter 6) for details.
Interlace method is a single-byte integer that says what order the image data is sent in ๐งฉ Two values exist right now: 0 (no interlace) or 1 (Adam7 interlace). See Interlaced data order (Section 2.6) for details.
4.1.2. PLTE Palette ๐จ
The PLTE chunk holds from 1 to 256 palette entries, and each one is a three-byte series that looks like this:
Red: 1 byte (0 = black, 255 = red)
Green: 1 byte (0 = black, 255 = green)
Blue: 1 byte (0 = black, 255 = blue)
The number of entries is figured out from the chunk length. A chunk length that isn't divisible by 3 is an error ๐ โโ๏ธ
This chunk MUST appear for color type 3, and it can appear for color types 2 and 6. It MUST NOT appear for color types 0 and 4 ๐ซ If this chunk does appear, it MUST precede the first IDAT chunk. There MUST NOT be more than one PLTE chunk. Strict, but iconic ๐
For color type 3 (indexed color), the PLTE chunk is REQUIRED ๐ The first entry is referenced by pixel value 0, the second by pixel value 1, and so on. The number of palette entries MUST NOT exceed the range that can be represented in the image bit depth (for example, 2^4 = 16 for a bit depth of 4). It's permissible to have fewer entries than the bit depth would allow. In that case, any out-of-range pixel value found in the image data is an error ๐
For color types 2 and 6 (truecolor and truecolor with alpha), the PLTE chunk is OPTIONAL ๐ฆ If it's present, it gives a suggested set of 1 to 256 colors that the truecolor image can be quantized to if the viewer can't display truecolor directly. If PLTE isn't there, that viewer has to pick colors on its own, but it's often better for the encoder to do it once ahead of time. (See Recommendations for Encoders: Suggested palettes, Section 9.5.)
Note that the palette uses 8 bits (1 byte) per sample no matter what the image bit depth is. Even when it's a suggested quantization of a 16-bit truecolor image, the palette is still 8 bits deep ๐
There is no requirement that every palette entry gets used by the image, or that they're all different from each other ๐คทโโ๏ธ
4.1.3. IDAT Image data ๐พ
The IDAT chunk holds the actual image data. To make it, you:
- Start with image scanlines represented as described in Image layout (Section 2.3). The layout and total size of this raw data are set by the fields of IHDR.
- Filter the image data according to the filtering method specified by the IHDR chunk. (Note that with filter method 0, the only one currently defined, this means prepending a filter type byte to each scanline.)
- Compress the filtered data using the compression method specified by the IHDR chunk.
The IDAT chunk contains the output datastream of the compression algorithm ๐
To read the image data, you just reverse the process ๐
There can be multiple IDAT chunks. If there are, they MUST appear consecutively, with no other chunks in between ๐งต The compressed datastream is then the concatenation of the contents of all the IDAT chunks. The encoder can split the compressed datastream into IDAT chunks however it wants. (Multiple IDAT chunks are allowed so encoders can work in a fixed amount of memory. Usually the chunk size matches the encoder's buffer size.) It's really important to know that IDAT chunk boundaries have no semantic significance and can fall at any point in the compressed datastream ๐ง A PNG file where each IDAT chunk holds only one data byte is legal, though remarkably wasteful of space ๐ญ (And zero-length IDAT chunks are legal too, though even more wasteful ๐)
See Filter Algorithms (Chapter 6) and Deflate/Inflate Compression (Chapter 5) for details.
4.1.4. IEND Image trailer ๐
The IEND chunk MUST appear LAST ๐ฌ It marks the end of the PNG datastream, and its data field is empty.
4.2. Ancillary chunks ๐ธ
All ancillary chunks are OPTIONAL, in the sense that encoders don't have to write them and decoders can ignore them ๐ That said, encoders are encouraged to write the standard ancillary chunks when the information is available, and decoders are encouraged to interpret these chunks when it's appropriate and feasible ๐ซถ
The standard ancillary chunks are listed in alphabetical order. That is not necessarily the order they'd show up in a file ๐
4.2.1. bKGD Background color ๐ค
The bKGD chunk specifies a default background color to show the image against. Viewers are not bound to honor this chunk, so a viewer can choose a different background if it wants ๐
For color type 3 (indexed color), the bKGD chunk contains:
Palette index: 1 byte
The value is the palette index of the color to use as the background.
For color types 0 and 4 (grayscale, with or without alpha), bKGD contains:
Gray: 2 bytes, range 0 .. (2^bitdepth)-1
(For consistency, 2 bytes are used no matter what the image bit depth is.) The value is the gray level to use as the background.
For color types 2 and 6 (truecolor, with or without alpha), bKGD contains:
Red: 2 bytes, range 0 .. (2^bitdepth)-1
Green: 2 bytes, range 0 .. (2^bitdepth)-1
Blue: 2 bytes, range 0 .. (2^bitdepth)-1
(For consistency, 2 bytes per sample are used no matter what the image bit depth is.) This is the RGB color to use as the background ๐จ
When it's present, the bKGD chunk MUST precede the first IDAT chunk, and MUST follow the PLTE chunk, if there is one โจ
See Recommendations for Decoders: Background color (Section 10.7).
4.2.2. cHRM Primary chromaticities and white point ๐ค๏ธ
Applications that need a device-independent way to specify colors in a PNG file can use the cHRM chunk. It specifies the 1931 CIE x,y chromaticities of the red, green, and blue primaries used in the image, plus the referenced white point ๐ฌ See Color Tutorial (Chapter 14) for more information.
The cHRM chunk contains:
White Point x: 4 bytes
White Point y: 4 bytes
Red x: 4 bytes
Red y: 4 bytes
Green x: 4 bytes
Green y: 4 bytes
Blue x: 4 bytes
Blue y: 4 bytes
Each value is encoded as a 4-byte unsigned integer, representing the x or y value times 100000. For example, a value of 0.3127 would be stored as the integer 31270 ๐ข
cHRM is allowed in all PNG files, though it's of little value for grayscale images ๐ฉถ
If the encoder doesn't know the chromaticity values, it SHOULD NOT write a cHRM chunk. The absence of a cHRM chunk indicates that the image's primary colors are device-dependent ๐ซ
If the cHRM chunk appears, it MUST precede the first IDAT chunk, and it MUST also precede the PLTE chunk if one is present ๐
See Recommendations for Encoders: Encoder color handling (Section 9.3), and Recommendations for Decoders: Decoder color handling (Section 10.6).
4.2.3. gAMA Image gamma ๐ธ
The gAMA chunk specifies the gamma of the camera (or simulated camera) that produced the image, and so the gamma of the image with respect to the original scene. More precisely, the gAMA chunk encodes the file_gamma value, as defined in Gamma Tutorial (Chapter 13) ๐
The gAMA chunk contains:
Image gamma: 4 bytes
The value is encoded as a 4-byte unsigned integer, representing gamma times 100000. For example, a gamma of 0.45 would be stored as the integer 45000 ๐งฎ
If the encoder doesn't know the image's gamma value, it SHOULD NOT write a gAMA chunk. The absence of a gAMA chunk indicates that the gamma is unknown ๐คทโโ๏ธ
If the gAMA chunk appears, it MUST precede the first IDAT chunk, and it MUST also precede the PLTE chunk if one is present ๐
See Gamma correction (Section 2.7), Recommendations for Encoders: Encoder gamma handling (Section 9.2), and Recommendations for Decoders: Decoder gamma handling (Section 10.5).
4.2.4. hIST Image histogram ๐
The hIST chunk gives the approximate usage frequency of each color in the color palette ๐ A histogram chunk can appear ONLY when a palette chunk appears. If a viewer can't show every color in the palette, the histogram can help it choose which subset of colors to display ๐ฏ
The hIST chunk contains a series of 2-byte (16 bit) unsigned integers. There MUST be exactly one entry for each entry in the PLTE chunk. Each entry is proportional to the fraction of pixels in the image that have that palette index. The exact scale factor is up to the encoder ๐
Histogram entries are approximate, with one exception: a zero entry specifies that the matching palette entry isn't used at all in the image. It is REQUIRED that a histogram entry be nonzero if there are any pixels of that color ๐ฅ
When the palette is a suggested quantization of a truecolor image, the histogram is necessarily approximate, because a decoder may map pixels to palette entries differently than the encoder did. In this situation, zero entries SHOULD NOT appear ๐
The hIST chunk, if it appears, MUST follow the PLTE chunk and MUST precede the first IDAT chunk ๐ง
See Rationale: Palette histograms (Section 12.14), and Recommendations for Decoders: Suggested-palette and histogram usage (Section 10.10). ๐โจ
4.2.5. pHYs Physical pixel dimensions ๐๐ฆ
okay so the pHYs chunk is the one that tells a viewer how big the pixels are meant to look when shown ๐ผ๏ธ๐ it's basically the "display me like this" chunk. it holds:
Pixels per unit, X axis: 4 bytes (unsigned integer)
Pixels per unit, Y axis: 4 bytes (unsigned integer)
Unit specifier: 1 byte
only two unit specifier values are legal, no cap ๐ง
0: unit is unknown
1: unit is the meter
when the unit specifier is 0, pHYs only gives you the pixel aspect ratio, and the actual size of the pixels stays unspecified ๐ธ so it's giving "shape, not size" ๐
Conversion note: one inch is equal to exactly 0.0254 meters ๐โจ
if this ancillary chunk isn't there, the pixels are assumed to be square, and nobody knows how big each pixel physically is ๐คทโโ๏ธ kind of iconic in its own way ๐
if it's present, this chunk must precede the first IDAT chunk ๐ฅ no late entrances allowed bestie ๐
See Recommendations for Decoders: Pixel dimensions (Section 10.2). ๐
4.2.6. sBIT Significant bits ๐๐
okay this one is so clever ngl ๐ง โจ PNG likes to keep things simple for decoders, so it says only certain sample depths can be used, and that sample values should get scaled up to the full range at that depth ๐ but sometimes the original data had fewer significant bits, and that's where sBIT comes in ๐ฆ it stores how many bits were actually significant, so decoders can recover the original data losslessly even if that depth isn't directly supported by PNG ๐ญ๐ซถ the spec recommends that an encoder emit an sBIT chunk if it converted the data from a lower sample depth, which is so responsible of them ๐
for color type 0 (grayscale), the sBIT chunk is a single byte, giving the number of significant bits in the source data ๐ฉถ
for color type 2 (truecolor), it's three bytes, one each for the red, green, and blue channels, in that order ๐
for color type 3 (indexed color), it's also three bytes, for the red, green, and blue components of the palette entries ๐จ
for color type 4 (grayscale with alpha channel), it's two bytes: the significant bits in the source grayscale data, then the source alpha data ๐
for color type 6 (truecolor with alpha channel), it's four bytes, for the red, green, blue and alpha channels ๐ซง
each depth in sBIT has to be greater than zero and less than or equal to the sample depth (which is 8 for indexed-color images, and the bit depth from IHDR for everything else) โจ no zeros allowed, no going over ๐
a decoder doesn't have to pay attention to sBIT ๐โโ๏ธ the stored image is still a valid PNG file at the sample depth IHDR says. but if a decoder wants the original data at its original precision, it can right-shift the stored samples (or the stored palette entries, for an indexed-color image) ๐ and the encoder has to scale the data so the high-order bits match the original data ๐ง
if sBIT shows up, it must precede the first IDAT chunk, and it must also precede the PLTE chunk if there is one ๐๐
See Recommendations for Encoders: Sample depth scaling (Section 9.1) and Recommendations for Decoders: Sample depth rescaling (Section 10.4). ๐
4.2.7. tEXt Textual data ๐๐ธ
so tEXt chunks are where the encoder can stash text about the image ๐๏ธโจ each one holds a keyword and a text string, laid out like this:
Keyword: 1-79 bytes (character string)
Null separator: 1 byte
Text: n bytes (character string)
the keyword and text get separated by a zero byte (a null character) ๐ซถ neither of them can contain a null character, and the text string is not null-terminated, because the chunk length already tells you where it ends ๐ the keyword has to be at least one character and less than 80 characters long ๐ข the text string can be anywhere from zero bytes up to the max chunk size minus the keyword length and separator ๐
you can have as many tEXt chunks as you want, and it's totally fine to repeat the same keyword ๐ฆ
the keyword tells you what kind of info the text is about, and these predefined ones should be used where they fit ๐
Title Short (one line) title or caption for image
Author Name of image's creator
Description Description of image (possibly long)
Copyright Copyright notice
Creation Time Time of original image creation
Software Software used to create the image
Disclaimer Legal disclaimer
Warning Warning of nature of content
Source Device used to create the image
Comment Miscellaneous comment; conversion from
GIF comment
for the Creation Time keyword, the date format from section 5.2.14 of RFC 1123 is suggested, but not required [RFC-1123] ๐๏ธ๐ decoders should still allow free-format text for this keyword or any other one ๐
other keywords can be invented for other purposes ๐ง keywords of general interest can get registered with the maintainers of the PNG spec ๐ฎ but it's also allowed to use private unregistered keywords ๐ (and those should be reasonably self-explanatory, so that different people don't accidentally use the same keyword for incompatible purposes ๐ nobody wants that drama)
both keyword and text are read using the ISO 8859-1 (Latin-1) character set [ISO-8859] ๐ the text string can contain any Latin-1 character ๐ if you want newlines in the text, use a single linefeed character (decimal 10) ๐ and other control characters in the text are discouraged ๐ โโ๏ธ
keywords have their own strict rules, so pay attention ๐ they must contain only printable Latin-1 characters and spaces, meaning only character codes 32-126 and 161-255 decimal are allowed ๐ค to keep humans from misreading them, leading and trailing spaces are forbidden, and so are consecutive spaces ๐ซ the non-breaking space (code 160) isn't permitted in keywords either, since it looks exactly like a regular space ๐ป
keywords must be spelled exactly as registered, so decoders can use simple literal comparisons when hunting for a particular keyword ๐ keywords are case-sensitive, so "Title" and "title" are not the same thing ๐
See Recommendations for Encoders: Text chunk processing (Section 9.7) and Recommendations for Decoders: Text chunk processing (Section 10.11). ๐
4.2.8. tIME Image last-modification time โฐ๐
the tIME chunk tells you when the image was last modified (not when it was first created, so don't mix those up ๐ โโ๏ธ) and it holds:
Year: 2 bytes (complete; for example, 1995, not 95)
Month: 1 byte (1-12)
Day: 1 byte (1-31)
Hour: 1 byte (0-23)
Minute: 1 byte (0-59)
Second: 1 byte (0-60) (yes, 60, for leap seconds; not 61,
a common error)
Universal Time (UTC, also called GMT) should be used rather than local time ๐๐
tIME is meant to be an automatic timestamp that updates whenever the image data changes ๐ค it is recommended that tIME not be changed by PNG editors that don't change the image data ๐ซถ and if you want a user-supplied time instead, check out the Creation Time tEXt keyword ๐
4.2.9. tRNS Transparency ๐ซง๐ฆ
the tRNS chunk says the image uses simple transparency, which means either alpha values for palette entries (for indexed-color images), or a single transparent color (for grayscale and truecolor images) ๐ simple transparency isn't as elegant as a full alpha channel, but it takes up less storage and is enough for lots of common cases ๐
for color type 3 (indexed color), tRNS holds a series of one-byte alpha values, matching the entries in PLTE ๐จ
Alpha for palette index 0: 1 byte
Alpha for palette index 1: 1 byte
... etc ...
each entry says that pixels with that palette index must be treated as having the given alpha value ๐ง alpha values work the same as in an 8-bit full alpha channel: 0 is fully transparent, 255 is fully opaque, no matter the image bit depth โจ tRNS must not contain more alpha values than there are palette entries, but it can contain fewer ๐ any leftover palette entries are assumed to have alpha 255 ๐ so in the super common case where only palette index 0 needs to be transparent, a one-byte tRNS chunk is all you need ๐ซถ
for color type 0 (grayscale), tRNS holds a single gray level value, stored like this:
Gray: 2 bytes, range 0 .. (2^bitdepth)-1
(for consistency, 2 bytes are used no matter the image bit depth ๐ข) pixels of that gray level are treated as transparent (the same as alpha value 0), and every other pixel is treated as fully opaque (alpha value (2^bitdepth)-1) ๐ธ
for color type 2 (truecolor), tRNS holds a single RGB color value, stored like this:
Red: 2 bytes, range 0 .. (2^bitdepth)-1
Green: 2 bytes, range 0 .. (2^bitdepth)-1
Blue: 2 bytes, range 0 .. (2^bitdepth)-1
(again, 2 bytes per sample for consistency, whatever the image bit depth is ๐) pixels of that exact color value are treated as transparent (alpha value 0), and all the other pixels are treated as fully opaque (alpha value (2^bitdepth)-1) ๐ฆ
tRNS is prohibited for color types 4 and 6, since those already have a full alpha channel ๐ซ no double dipping ๐
Note: when you're dealing with 16-bit grayscale or truecolor data, you have to compare both bytes of the sample values to decide if a pixel is transparent ๐ญ decoders can drop the low-order byte for display, but only after the transparency test is done ๐ซข for example, if gray level 0x0001 is marked transparent, it would be wrong to only compare the high-order byte and decide that 0x0002 is transparent too ๐๐
tRNS has a strict schedule ๐ It has to show up before the first IDAT chunk, and if there's a PLTE chunk, tRNS has to come after it ๐
4.2.10. zTXt Compressed textual data โจ
okay so zTXt is basically tEXt's glow-up ๐ It holds textual data just like tEXt does, but it takes advantage of compression. zTXt and tEXt chunks are semantically equivalent, but zTXt is recommended for storing large blocks of text ๐
A zTXt chunk contains:
Keyword: 1-79 bytes (character string)
Null separator: 1 byte
Compression method: 1 byte
Compressed text: n bytes
The keyword and null separator are exactly the same as in the tEXt chunk ๐ซถ The keyword is not compressed, so it's readable right away ๐ The compression method byte tells you which compression method was used in this zTXt chunk. The only value presently defined for it is 0 (deflate/inflate compression) ๐ After that byte comes a compressed datastream that makes up the rest of the chunk. For compression method 0, this datastream follows the zlib datastream format (see Deflate/Inflate Compression, Chapter 5). Decompressing it gives you Latin-1 text that is identical to the text that would be stored in an equivalent tEXt chunk ๐ง
Any number of zTXt and tEXt chunks can appear in the same file โจ For the predefined keywords and the recommended format of the text, look back at the tEXt chunk definition.
See Recommendations for Encoders: Text chunk processing (Section 9.7), and Recommendations for Decoders: Text chunk processing (Section 10.11) ๐
4.3. Summary of standard chunks ๐
Here's the cheat sheet ๐ This table summarizes some properties of the standard chunk types.
Critical chunks (must appear in this order, except PLTE
is optional):
Name Multiple Ordering constraints
OK?
IHDR No Must be first
PLTE No Before IDAT
IDAT Yes Multiple IDATs must be consecutive
IEND No Must be last
Ancillary chunks (need not appear in this order):
Name Multiple Ordering constraints
OK?
cHRM No Before PLTE and IDAT
gAMA No Before PLTE and IDAT
sBIT No Before PLTE and IDAT
bKGD No After PLTE; before IDAT
hIST No After PLTE; before IDAT
tRNS No After PLTE; before IDAT
pHYs No Before IDAT
tIME No None
tEXt Yes None
zTXt Yes None
Standard keywords for tEXt and zTXt chunks ๐
Title Short (one line) title or caption for image
Author Name of image's creator
Description Description of image (possibly long)
Copyright Copyright notice
Creation Time Time of original image creation
Software Software used to create the image
Disclaimer Legal disclaimer
Warning Warning of nature of content
Source Device used to create the image
Comment Miscellaneous comment; conversion from
GIF comment
4.4. Additional chunk types ๐ฆ
Additional public PNG chunk types are defined in the document "PNG Special-Purpose Public Chunks" [PNG-EXTENSIONS] ๐ Chunks described there are expected to be less widely supported than the ones defined in this specification. Still, application authors are encouraged to use those chunk types whenever they're appropriate for their applications ๐ New chunk types can be proposed for that list by contacting the PNG specification maintainers at png-info@uunet.uu.net or at png-group@w3.org ๐
New public chunks will only be registered if they're of use to others and don't violate the design philosophy of PNG ๐ฅ Chunk registration isn't automatic, although the authors intend for it to be straightforward when a new chunk of potentially wide application is needed ๐ธ Also, creating new critical chunk types is discouraged unless absolutely necessary ๐
Applications can also use private chunk types to carry data that isn't of interest to other applications ๐คซ See Recommendations for Encoders: Use of private chunks (Section 9.8).
Decoders must be prepared to encounter unrecognized public or private chunk type codes ๐ง Unrecognized chunk types must be handled as described in Chunk naming conventions (Section 3.3) โจ
5. Deflate/Inflate Compression ๐
PNG compression method 0 (the only compression method presently defined for PNG) specifies deflate/inflate compression with a 32K sliding window ๐ช Deflate is an LZ77 derivative, the same family used in zip, gzip, pkzip and related programs ๐ฆ Extensive research has been done supporting its patent-free status, and portable C implementations are freely available ๐ซถ
Deflate-compressed datastreams within PNG are stored in the "zlib" format, which has this structure:
Compression method/flags code: 1 byte
Additional flags/check bits: 1 byte
Compressed data blocks: n bytes
Check value: 4 bytes
Further details on this format are given in the zlib specification [RFC-1950] ๐
For PNG compression method 0, the zlib compression method/flags code must specify method code 8 ("deflate" compression) and an LZ77 window size of not more than 32K ๐ Note that the zlib compression method number is not the same as the PNG compression method number, so don't mix them up ๐ The additional flags must not specify a preset dictionary ๐ซ
The compressed data within the zlib datastream is stored as a series of blocks. Each block can represent raw (uncompressed) data, LZ77-compressed data encoded with fixed Huffman codes, or LZ77-compressed data encoded with custom Huffman codes ๐งฉ A marker bit in the final block identifies it as the last block, which lets the decoder recognize the end of the compressed datastream ๐ Further details on the compression algorithm and the encoding are given in the deflate specification [RFC-1951] ๐
The check value stored at the end of the zlib datastream is calculated on the uncompressed data that the datastream represents ๐ The algorithm used is not the same as the CRC calculation used for PNG chunk check values, which is a plot twist ๐ญ The zlib check value is mainly useful as a cross-check that the deflate and inflate algorithms are implemented correctly ๐ ๏ธ Verifying the chunk CRCs gives you adequate confidence that the PNG file was transmitted undamaged ๐
In a PNG file, the contents of all the IDAT chunks, concatenated together, make up a zlib datastream as specified above ๐งต This datastream decompresses to filtered image data, as described elsewhere in this document ๐ผ๏ธ
It's important to emphasize that the boundaries between IDAT chunks are arbitrary and can fall anywhere in the zlib datastream ๐ซง There isn't necessarily any correlation between IDAT chunk boundaries and deflate block boundaries or any other feature of the zlib data. For example, it's entirely possible for the terminating zlib check value to be split across IDAT chunks ๐ณ
In the same vein, there's no required correlation between the structure of the image data (i.e., scanline boundaries) and deflate block boundaries or IDAT chunk boundaries ๐ง The complete image data is represented by a single zlib datastream that is stored in some number of IDAT chunks, and a decoder that assumes any more than this is incorrect โ (Of course, some encoder implementations may emit files in which some of these structures are related. But decoders cannot rely on this ๐ โโ๏ธ)
PNG also uses zlib datastreams in zTXt chunks ๐ In a zTXt chunk, the remainder of the chunk after the compression method byte is a zlib datastream as specified above. This datastream decompresses to the user-readable text described by the chunk's keyword ๐ฅฐ Unlike image data, these datastreams aren't split across chunks, so each zTXt chunk contains an independent zlib datastream ๐ฆ
Additional documentation and portable C code for deflate and inflate are available from the Info-ZIP archives at <URL:ftp://ftp.uu.net/pub/archiving/zip/> ๐พ
6. Filter Algorithms โจ
okay bestie, this chapter is all about the filter algorithms that can run before compression ๐ซถ the whole point is to prep the image data so it compresses as well as possible. it's giving "glow-up before the glam squad arrives" ๐
6.1. Filter types
PNG filter method 0 defines five basic filter types ๐
Type Name
0 None
1 Sub
2 Up
3 Average
4 Paeth
(ngl this part is important ๐ filter method 0 in IHDR specifies exactly this set of five filter types. if the set ever gets extended, it gets a different filter method number, so decoders don't have to decompress the data just to find out it has filter types they don't support. iconic, no notes ๐)
the encoder gets to pick which filter algorithm to use on each scanline, one scanline at a time ๐ฆ in the image data that goes to the compression step, every scanline starts with a filter type byte that tells you which algorithm was used on that scanline. main character energy for that one byte ๐
filtering works on bytes, not pixels, no matter the bit depth or color type of the image ๐ the filters operate on the byte sequence you get from a scanline represented the way it's described in Image layout (Section 2.3). and if the image has an alpha channel, the alpha data gets filtered the same way the image data does, no special treatment ๐
when the image is interlaced, each pass of the interlace pattern is treated as its own independent image for filtering purposes ๐ง the filters work on the bytes made from the pixels that actually get transmitted during that pass. the "previous scanline" is the one previously transmitted in the same pass, not the one that's adjacent in the full image. the subimage from any one pass is always rectangular, just smaller in width and/or height than the complete image. and if that subimage is empty, filtering is not applied at all ๐ธ
for all the filters, the bytes "to the left of" the first pixel in a scanline must be treated as zero. for filters that look at the prior scanline, the entire prior scanline must be treated as all zeroes for the first scanline of an image (or of a pass of an interlaced image) ๐
to undo a filter, the decoder must use the decoded value of the prior pixel on the same line, the pixel directly above the current pixel on the prior line, and the pixel just to the left of the pixel above. so at least one scanline's worth of image data has to be stored by the decoder at all times ๐ญ and even though some filter types don't look at the prior scanline, the decoder still has to store each scanline as it decodes it, because the next scanline might use a filter that does look back ๐ซถ
PNG doesn't put any restriction on which filter types can be applied to an image โจ but the filters aren't equally effective on every kind of data, so go peek at Recommendations for Encoders: Filter selection (Section 9.6) ๐
also see Rationale: Filtering (Section 12.9) ๐
6.2. Filter type 0: None
with the None filter, the scanline is transmitted unmodified ๐ literally the only thing you have to do is slap a filter type byte in front of the data. zero drama, zero math ๐
6.3. Filter type 1: Sub
the Sub filter transmits the difference between each byte and the value of the corresponding byte of the prior pixel ๐ฆ
to compute the Sub filter, apply this formula to each byte of the scanline:
Sub(x) = Raw(x) - Raw(x-bpp)
where x ranges from zero to the number of bytes representing the scanline minus one, Raw(x) refers to the raw data byte at that byte position in the scanline, and bpp is defined as the number of bytes per complete pixel, rounding up to one. for example, for color type 2 with a bit depth of 16, bpp is equal to 6 (three samples, two bytes per sample). for color type 0 with a bit depth of 2, bpp is equal to 1 (rounding up). for color type 4 with a bit depth of 16, bpp is equal to 4 (two-byte grayscale sample, plus two-byte alpha sample) ๐
note this computation is done for each byte, regardless of bit depth ๐ in a 16-bit image, each MSB is predicted from the preceding MSB, and each LSB is predicted from the preceding LSB, because of how bpp is defined. the way it's set up is lowkey genius ๐ง
unsigned arithmetic modulo 256 is used, so both the inputs and outputs fit into bytes ๐ the sequence of Sub values is what gets transmitted as the filtered scanline.
for all x < 0, assume Raw(x) = 0 ๐ธ
to reverse the Sub filter after decompression, output this value:
Sub(x) + Raw(x-bpp)
(computed mod 256), where Raw refers to the bytes that are already decoded ๐ซถ
6.4. Filter type 2: Up
the Up filter is basically the Sub filter, except the pixel immediately above the current pixel gets used as the predictor, instead of the one just to its left ๐
to compute the Up filter, apply this formula to each byte of the scanline:
Up(x) = Raw(x) - Prior(x)
where x ranges from zero to the number of bytes representing the scanline minus one, Raw(x) refers to the raw data byte at that byte position in the scanline, and Prior(x) refers to the unfiltered bytes of the prior scanline ๐
note this is done for each byte, regardless of bit depth. unsigned arithmetic modulo 256 is used, so both inputs and outputs fit into bytes. the sequence of Up values is transmitted as the filtered scanline โจ
on the first scanline of an image (or of a pass of an interlaced image), assume Prior(x) = 0 for all x ๐
to reverse the Up filter after decompression, output this value:
Up(x) + Prior(x)
(computed mod 256), where Prior refers to the decoded bytes of the prior scanline ๐
6.5. Filter type 3: Average
the Average filter uses the average of the two neighboring pixels (left and above) to predict the value of a pixel ๐ฆ very "we're in this together" energy ๐ซถ
to compute the Average filter, apply this formula to each byte of the scanline:
Average(x) = Raw(x) - floor((Raw(x-bpp)+Prior(x))/2)
where x ranges from zero to the number of bytes representing the scanline minus one, Raw(x) refers to the raw data byte at that byte position in the scanline, Prior(x) refers to the unfiltered bytes of the prior scanline, and bpp is defined as it was for the Sub filter ๐
note this is done for each byte, regardless of bit depth. the sequence of Average values is transmitted as the filtered scanline ๐ธ
the subtraction of the predicted value from the raw byte must be done modulo 256, so both inputs and outputs fit into bytes. however, the sum Raw(x-bpp)+Prior(x) must be formed without overflow (using at least nine-bit arithmetic) ๐ floor() means the result of the division gets rounded down to the next lower integer if it's fractional. in other words, it's an integer division or right shift operation ๐ง
for all x < 0, assume Raw(x) = 0. on the first scanline of an image (or of a pass of an interlaced image), assume Prior(x) = 0 for all x ๐
to reverse the Average filter after decompression, output this value:
Average(x) + floor((Raw(x-bpp)+Prior(x))/2)
where the result is computed mod 256, but the prediction is calculated the same way it was for encoding ๐ Raw refers to the bytes already decoded, and Prior refers to the decoded bytes of the prior scanline ๐ซถ
6.6. Filter type 4: Paeth
the Paeth filter computes a simple linear function of the three neighboring pixels (left, above, upper left), then chooses as its predictor the neighboring pixel that's closest to the computed value ๐ this technique is due to Alan W. Paeth [PAETH], iconic inventor behavior ๐
to compute the Paeth filter, apply this formula to each byte of the scanline:
Paeth(x) = Raw(x) - PaethPredictor(Raw(x-bpp), Prior(x),
Prior(x-bpp))
where x ranges from zero to the number of bytes representing the scanline minus one, Raw(x) refers to the raw data byte at that byte position in the scanline, Prior(x) refers to the unfiltered bytes of the prior scanline, and bpp is defined as it was for the Sub filter ๐
note this is done for each byte, regardless of bit depth. unsigned arithmetic modulo 256 is used, so both inputs and outputs fit into bytes. the sequence of Paeth values is transmitted as the filtered scanline โจ
the PaethPredictor function is defined by this pseudocode:
function PaethPredictor (a, b, c)
begin
; a = left, b = above, c = upper left
p := a + b - c ; initial estimate
pa := abs(p - a) ; distances to a, b, c
pb := abs(p - b)
pc := abs(p - c)
; return nearest of a,b,c,
; breaking ties in order a,b,c.
if pa <= pb AND pa <= pc then return a
else if pb <= pc then return b
else return c
end
the calculations inside PaethPredictor must be performed exactly, without overflow ๐ arithmetic modulo 256 is only used for the final step, where you subtract the function result from the target byte value ๐ง
โ ๏ธ bestie, the order you break ties in is critical and must not be altered ๐ญ the tie break order is: pixel to the left, pixel above, pixel to the upper left. (this order is different from the one in Paeth's article, so don't copy from there ๐)
for all x < 0, assume Raw(x) = 0 and Prior(x) = 0. on the first scanline of an image (or of a pass of an interlaced image), assume Prior(x) = 0 for all x ๐ธ
to reverse the Paeth filter after decompression, output this value:
Paeth(x) + PaethPredictor(Raw(x-bpp), Prior(x), Prior(x-bpp))
(computed mod 256), where Raw and Prior refer to bytes that are already decoded. exactly the same PaethPredictor function is used by both encoder and decoder, no exceptions, periodt ๐ ๐ฆ
7. Chunk Ordering Rules ๐
okay bestie, so we need ordering rules for every chunk type, because otherwise a PNG editing program has no clue what to do when it runs into an unknown chunk ๐ญ it's giving chaos ๐
We define a "PNG editor" as a program that modifies a PNG file and wants to keep as much of the ancillary info as possible โจ Two examples are a program that adds or modifies text chunks, and a program that adds a suggested palette to a truecolor PNG file. Ordinary image editors are not PNG editors in this sense, because they usually toss all the unrecognized info while reading in an image ๐ (Note: we strongly encourage programs handling PNG files to preserve ancillary information whenever possible. ๐)
Here's a spicy example ๐ถ๏ธ say there's a hypothetical new ancillary chunk that's safe-to-copy and has to show up after PLTE whenever PLTE is there. If our program that adds a suggested PLTE doesn't recognize this new chunk, it might insert PLTE in the wrong spot, right after the new chunk. We could fix this by making PNG editors throw away all unknown chunks, but that is a very unattractive solution ๐ฉ So instead, PNG requires ancillary chunks not to have ordering restrictions like this.
To stop this kind of problem while still leaving room for future extensions, we put some constraints on both the behavior of PNG editors and the ordering requirements that chunks are allowed to have ๐ซถ
7.1. Behavior of PNG editors
The rules for PNG editors are:
- When copying an unknown unsafe-to-copy ancillary chunk, a PNG editor MUST NOT move the chunk relative to any critical chunk. It can relocate the chunk freely relative to other ancillary chunks that occur between the same pair of critical chunks. (This is well defined since the editor MUST NOT add, delete, modify, or reorder critical chunks if it is preserving unknown unsafe-to-copy chunks.)
- When copying an unknown safe-to-copy ancillary chunk, a PNG editor MUST NOT move the chunk from before IDAT to after IDAT or vice versa. (This is well defined because IDAT is always present.) Any other reordering is permitted. ๐ฆ
- When copying a known ancillary chunk type, an editor need only honor the specific chunk ordering rules that exist for that chunk type. However, it can always choose to apply the above general rules instead.
- PNG editors MUST give up on encountering an unknown critical chunk type, because there is no way to be certain that a valid file will result from modifying a file containing such a chunk. (Note that simply discarding the chunk is not good enough, because it might have unknown implications for the interpretation of other chunks.) ๐
These rules are written in terms of copying chunks from an input file to an output file, but they apply in the obvious way if a PNG file is modified in place โจ
See also Chunk naming conventions (Section 3.3).
7.2. Ordering of ancillary chunks
The ordering rules for an ancillary chunk type cannot be any stricter than this:
- Unsafe-to-copy chunks can have ordering requirements relative to critical chunks.
- Safe-to-copy chunks can have ordering requirements relative to IDAT.
The actual ordering rules for any particular ancillary chunk type may be weaker. See for example the ordering rules for the standard ancillary chunk types (Summary of standard chunks, Section 4.3). ๐ธ
Decoders MUST NOT assume more about the positioning of any ancillary chunk than is specified by the chunk ordering rules. In particular, it is never valid to assume that a specific ancillary chunk type occurs with any particular positioning relative to other ancillary chunks. (For example, it is unsafe to assume that your private ancillary chunk occurs immediately before IEND. Even if your application always writes it there, a PNG editor might have inserted some other ancillary chunk after it. But you can safely assume that your chunk will remain somewhere between IDAT and IEND.) ๐ bestie, don't assume your chunk is always last, ok? ๐
7.3. Ordering of critical chunks
Critical chunks can have arbitrary ordering requirements, because PNG editors are REQUIRED to give up if they encounter unknown critical chunks. For example, IHDR has the special ordering rule that it MUST always appear first. ๐
A PNG editor, or indeed any PNG-writing program, MUST know and follow the ordering rules for any critical chunk type that it can emit. ๐ซถ
8. Miscellaneous Topics ๐
8.1. File name extension
On systems where file names customarily include an extension signifying file type, the extension .png is RECOMMENDED for PNG files. Lower case .png is preferred if file names are case-sensitive. ๐
8.2. Internet media type
The Internet Assigned Numbers Authority (IANA) has registered image/png as the Internet Media Type for PNG [RFC-2045, RFC-2048]. For robustness, decoders MAY choose to also support the interim media type image/x-png, which was in use before registration was complete. ๐ง
8.3. Macintosh file layout
In the Apple Macintosh system, the following conventions are RECOMMENDED:
- The four-byte file type code for PNG files is
PNGf. (This code has been registered with Apple for PNG files.) The creator code will vary depending on the creating application. - The contents of the data fork MUST be a PNG file exactly as described in the rest of this specification.
- The contents of the resource fork are unspecified. It MAY be empty or MAY contain application-dependent resources.
- When transferring a Macintosh PNG file to a non-Macintosh system, only the data fork SHOULD be transferred. ๐พ
8.4. Multiple-image extension
PNG itself is strictly a single-image format. However, it may be necessary to store multiple images within one file; for example, this is needed to convert some GIF files. In the future, a multiple-image format based on PNG may be defined. Such a format will be considered a separate file format and will have a different signature. PNG-supporting applications MAY or may not choose to support the multiple-image format. ๐ฆ
See Rationale: Why not these features? (Section 12.3).
8.5. Security considerations
๐จ okay this section is SERIOUS bestie, read it all ๐จ
A PNG file or datastream is composed of a collection of explicitly typed "chunks". Chunks whose contents are defined by the specification could actually contain anything, including malicious code. But there is no known risk that such malicious code could be executed on the recipient's computer as a result of decoding the PNG image. ๐
The possible security risks associated with future chunk types cannot be specified at this time. Security issues will be considered when evaluating chunks proposed for registration as public chunks. There is no additional security risk associated with unknown or unimplemented chunk types, because such chunks will be ignored, or at most be copied into another PNG file. ๐ก๏ธ
The tEXt and zTXt chunks contain data that is meant to be displayed as plain text. It is possible that if the decoder displays such text without filtering out control characters, especially the ESC (escape) character, certain systems or terminals could behave in undesirable and insecure ways. We recommend that decoders filter out control characters to avoid this risk; see Recommendations for Decoders: Text chunk processing (Section 10.11). ๐
Because every chunk's length is available at its beginning, and because every chunk has a CRC trailer, there is a very robust defense against corrupted data and against fraudulent chunks that attempt to overflow the decoder's buffers. Also, the PNG signature bytes provide early detection of common file transmission errors. ๐ซถ
A decoder that fails to check CRCs could be subject to data corruption. The only likely consequence of such corruption is incorrectly displayed pixels within the image. Worse things might happen if the CRC of the IHDR chunk is not checked and the width or height fields are corrupted. See Recommendations for Decoders: Error checking (Section 10.1). ๐ฑ
A poorly written decoder might be subject to buffer overflow, because chunks can be extremely large, up to (2^31)-1 bytes long. But properly written decoders will handle large chunks without difficulty. ๐ช
9. Recommendations for Encoders ๐
This chapter gives some recommendations for encoder behavior. The only absolute requirement on a PNG encoder is that it produce files that conform to the format specified in the preceding chapters. However, best results will usually be achieved by following these recommendations. โจ
9.1. Sample depth scaling
When encoding input samples that have a sample depth that cannot be directly represented in PNG, the encoder MUST scale the samples up to a sample depth that is allowed by PNG. The most accurate scaling method is the linear equation:
output = ROUND(input * MAXOUTSAMPLE / MAXINSAMPLE)
where the input samples range from 0 to MAXINSAMPLE and the outputs range from 0 to MAXOUTSAMPLE (which is (2^sampledepth)-1). ๐งฎ
A close approximation to the linear scaling method can be achieved by "left bit replication", which is shifting the valid bits to begin in the most significant bit and repeating the most significant bits into the open bits. This method is often faster to compute than linear scaling. As an example, assume that 5-bit samples are being scaled up to 8 bits. If the source sample value is 27 (in the range from 0-31), then the original bits are:
4 3 2 1 0
---------
1 1 0 1 1
Left bit replication gives a value of 222:
7 6 5 4 3 2 1 0
----------------
1 1 0 1 1 1 1 0
|=======| |===|
| Leftmost Bits Repeated to Fill Open Bits
|
Original Bits
which matches the value computed by the linear equation. Left bit replication usually gives the same value as linear scaling, and is never off by more than one. ๐ฏ
A distinctly less accurate approximation is obtained by simply left-shifting the input value and filling the low order bits with zeroes. This scheme cannot reproduce white exactly, since it does not generate an all-ones maximum value; the net effect is to darken the image slightly. This method is NOT RECOMMENDED in general, but it does have the effect of improving compression, particularly when dealing with greater-than-eight-bit sample depths. Since the relative error introduced by zero-fill scaling is small at high sample depths, some encoders MAY choose to use it. Zero-fill MUST NOT be used for alpha channel data, however, since many decoders will special-case alpha values of all zeroes and all ones. It is important to represent both those values exactly in the scaled data. ๐ซ๐ค
bestie, the sBIT scaling rule is a whole thing ๐
when the encoder writes an sBIT chunk, the high-order bits of the stored samples have to match the original data. so if the chunk says the sample depth is S, the top S bits of what's stored must agree with the original S-bit values ๐ซถ that's what lets a decoder get the original back just by shifting right โจ the extra low-order bits? totally unconstrained ๐ฆ and ngl every one of the scaling methods above passes this check ๐
when you're scaling UP, it's recommended that the low-order bits get filled consistently for every sample ๐ธ same source value in, same sample value out, no matter where the pixel is. this helps compression because there are fewer distinct sample values to store ๐ but it's not a hard requirement, and some encoders may skip it. like, an encoder could dither the low-order bits instead, which makes the picture look better on screen but makes the file bigger ๐ it's a tradeoff, not a rule.
and then there's the case where the original data's range isn't a power of 2 ๐ the linear scaling equation still works there, but the shifting methods don't. so it's recommended that no sBIT chunk gets written for those images, because sBIT says the original range was exactly 0..2^S-1, and that wouldn't be true ๐ญ
9.2. Encoder gamma handling
See Gamma Tutorial (Chapter 13) if gamma is still a bit of a mystery to you ๐
okay so how you handle gamma in an encoder with the gAMA chunk depends on the sample values' history and on whether they've already been rounded to integers ๐ฅ
if the encoder has the intensity values in floating-point or high-precision integer form (maybe straight from a computer renderer), then it's recommended the encoder do its own gamma encoding before it quantizes to integers for the file โจ doing it at that stage gives fewer banding artifacts at a given sample depth, or lets you use smaller samples and keep the same visual quality. slay ๐
a linear intensity level, written as a floating-point value from 0 to 1, becomes a gamma-encoded sample value like this:
sample = ROUND((intensity ^ encoder_gamma) * MAXSAMPLE)
the file_gamma written in the PNG gAMA chunk is the same as encoder_gamma in that equation, since we're assuming the starting intensity is linear (basically camera_gamma is 1.0 here) ๐ฆ
if the image is only going to a file, encoder_gamma can be picked kinda arbitrarily ๐ values of 0.45 or 0.5 are generally good picks, because they're common in video systems, so most PNG decoders should display these images nicely ๐
some renderers write a PNG file and show the image on screen at the same time ๐ the on-screen pixels should be gamma corrected for the display system and the viewing conditions, so the user sees the scene the way it was meant to look โจ the right correction value is:
screen_gc = viewing_gamma / display_gamma
if the renderer wants to write those same gamma-corrected values to the file (so there's no separate gamma-encoding step for the file), then this screen_gc value should go in the gAMA chunk. that lets a PNG decoder reproduce what the originator saw on screen while rendering, as long as the decoder properly supports arbitrary values in a gAMA chunk ๐ซถ
however, it's just as reasonable for a renderer to gamma correct for the screen using a gamma that fits the viewing conditions, and separately gamma encode the file values using a standard gamma like 0.5 ๐ธ honestly this is the preferable option, since some PNG decoders may not display images with unusual gAMA values accurately ๐
computer graphics renderers often skip gamma encoding and just make sample values directly proportional to scene light intensity ๐ if the encoder gets sample values that are already quantized into linear-light integers, there's no point gamma encoding them now. that would only lose more information ๐ญ the encoder should just write the sample values to the PNG file. this "linear" sample encoding is the same as gamma encoding with a gamma of 1.0, so graphics programs producing linear samples should always emit a gAMA chunk specifying a gamma of 1.0 ๐
when the samples come straight from a piece of hardware, the correct gAMA value depends on that hardware's gamma characteristic ๐ง for video digitizers ("frame grabbers"), gAMA should be 0.45 or 0.5 for NTSC (possibly less for PAL or SECAM), since video camera transfer functions are standardized. image scanners are way less predictable ๐ their output might be linear (gamma 1.0) because CCD sensors are linear themselves, or the scanner hardware might have already applied gamma correction meant to offset dot gain in later printing (gamma of about 0.57), or the scanner might have corrected the samples for display on a CRT (gamma of 0.4-0.5). you'll need to check the scanner's manual, or even scan a calibrated gray wedge, to figure out what a particular scanner does ๐
file format converters generally should NOT try to convert supplied images to a different gamma ๐ โโ๏ธ store the data in the PNG file without conversion, and record the source gamma if it's known. changing gamma during a file conversion causes requantization of the intensity levels being represented, which adds more roundoff error for basically no benefit ๐ญ it's almost always better to copy the sample values intact from input to output.
some supplied images might come in a format (like TIFF) that can describe the image's gamma characteristic ๐ in that case a file format converter is strongly encouraged to write a PNG gAMA chunk matching the known gamma of the source image. heads up though: some file formats specify the gamma of the display system, not the camera ๐ if the input file's gamma value is greater than 1.0, it's almost certainly a display system gamma, so you should use its reciprocal for the PNG gAMA ๐
if the encoder or converter doesn't know how the image was originally made, but does know it has looked satisfactory on a display with gamma display_gamma under lighting conditions where a particular viewing_gamma is appropriate, then the image can be marked with a file_gamma of:
file_gamma = viewing_gamma / display_gamma
this lets viewers of the PNG see the same image the person running the converter saw โจ it might not be the precisely correct image gamma, but a gAMA chunk with an approximately right value is better than leaving it out and forcing PNG decoders to guess at a gamma ๐ซถ
on the other hand, if the file is being converted as part of a "bulk" conversion where nobody's looking at each image, it's better to leave the gAMA chunk out entirely ๐ if the image gamma would have to be guessed, let the decoder do the guessing.
gamma does NOT apply to alpha samples ๐ โโ๏ธ alpha is always represented linearly, always, no exceptions ๐ฆ
see also Recommendations for Decoders: Decoder gamma handling (Section 10.5) ๐
9.3. Encoder color handling
See Color Tutorial (Chapter 14) if color is still confusing for you ๐ธ
okay so if the encoder can figure out the chromaticities of the source display primaries, or make a strong guess based on where the image came from or what hardware ran it, then the encoder is strongly encouraged to output the cHRM chunk ๐ and if it does, the gAMA chunk should be written too, because decoders can't do much with cHRM if gAMA is missing ๐ฅ
video made with recent video equipment probably uses the CCIR 709 primaries and D65 white point [ITU-BT709], which look like this:
R G B White
x 0.640 0.300 0.150 0.3127
y 0.330 0.600 0.060 0.3290
an older but still super popular video standard is SMPTE-C [SMPTE-170M]:
R G B White
x 0.630 0.310 0.155 0.3127
y 0.340 0.595 0.070 0.3290
the original NTSC color primaries haven't been used in decades ๐ you might still spot the NTSC numbers in standards documents, but you won't find any images that actually use them.
scanners that output PNG files should put the filter chromaticities into a cHRM chunk, and the camera_gamma into a gAMA chunk โจ
for hand-drawn or digitally edited images, you have to figure out what monitor they were viewed on while being made ๐จ lots of image editors let you pick your monitor type, because they work in some device-independent space internally. those programs have enough info to write valid cHRM and gAMA chunks, and they should do it automatically ๐
if the encoder is part of a computer image renderer doing full-spectral rendering, the monitor values used to convert from the internal device-independent color space to RGB should go in the cHRM chunk ๐ฆ any colors outside the gamut of the chosen RGB device should be clipped or otherwise constrained to be within the gamut, because PNG does NOT store out-of-gamut colors ๐
โโ๏ธ
if the renderer does its calculations directly in device-dependent RGB space, a cHRM chunk should not be written, unless the scene description and rendering parameters were adjusted to look good on a particular monitor ๐ธ in that case, the data for that monitor (if known) should be used to build a cHRM chunk.
there are often cases where an image's exact origins are unknown, especially if it started life in some other format ๐ง some image formats store calibration info, which can fill in the cHRM chunk. for example, all PhotoCD images use the CCIR 709 primaries and D65 whitepoint, so those values can be written into the cHRM chunk when converting a PhotoCD file ๐ PhotoCD also uses the SMPTE-170M transfer function, which is closely approximated by a gAMA of 0.5. (PhotoCD can store colors outside the RGB gamut, so that image data will need gamut mapping before it's written to PNG format ๐ญ) TIFF 6.0 files can optionally store calibration info, and if it's present it should be used to construct the cHRM chunk ๐ GIF and most other formats don't store any calibration info at all ๐
the thing is, file converters should NOT be changing the colour space of images they're given ๐ Just store the data in the PNG file as-is, and record the source primary chromaticities if you know them. Converting colour spaces at file conversion time is a bad idea because of gamut mismatches and rounding errors. Same deal as gamma: store it losslessly and only do at most one conversion when the image is actually displayed ๐
See also Recommendations for Decoders: Decoder color handling (Section 10.6) ๐
9.4. Alpha channel creation โจ
okay so the alpha channel can be thought of in two ways ๐ฆ One is as a mask that temporarily hides the see-through parts of the image. The other is as a way to make a non-rectangular image. In the mask case, the colour values of fully transparent pixels should be kept around for future use ๐ In the non-rectangular case, those transparent pixels carry no useful data at all. They're just there to fill out the rectangle that PNG requires, so all the fully transparent pixels should get the same colour value for the best compression ๐ฟโจ
Image authors, keep in mind that a decoder might ignore transparency control entirely ๐ So the colours you give transparent pixels should be reasonable background colours whenever that's doable ๐ธ
For apps that don't need a full alpha channel, or that can't afford the hit to compression efficiency, the tRNS transparency chunk is also available ๐ง
If the image has a known background colour, write it in the bKGD chunk. Even decoders that ignore transparency may use the bKGD colour to fill any unused screen area ๐ซถ
If the original image has premultiplied (also called "associated") alpha data, you need to convert it to PNG's non-premultiplied format. Divide each sample value by the matching alpha value, then multiply by the maximum value for the image bit depth, and round to the nearest integer ๐ฅ In valid premultiplied data the sample values never go past their alpha values, so the division result should always land between 0 and 1. And if the alpha value is zero, output black (zeroes) ๐ค
9.5. Suggested palettes ๐
a PLTE chunk can show up in truecolor PNG files ๐ In those files it isn't an essential part of the image data. It's just a suggested palette that viewers can use to show the image on indexed-colour display hardware. Viewers running on truecolour hardware don't care about it at all ๐
If an encoder decides to give a suggested palette, it's recommended to also write a hIST chunk so people know how important each palette entry is ๐ The histogram values are easiest to work out as "nearest neighbor" counts, meaning how often each palette entry gets used if no dithering is applied. (These counts often come for free once you've made the suggested palette, which is honestly so convenient ๐ญ)
For colour type 2 images (truecolour without an alpha channel), it's recommended that the palette and histogram get computed using only the RGB data, ignoring any transparent-colour specification ๐ If the file uses transparency (it has a tRNS chunk), viewers can easily adapt the palette to their own background colour. They just swap the palette entry closest to the tRNS colour for their background colour. (That background might or might not match the file's bKGD colour, if there is one.) ๐
For colour type 6 images (truecolour with alpha channel), it's recommended that a bKGD chunk show up, and that the palette and histogram get computed as if the image were composited against the specified background colour ๐ฆ This is needed so that useful palette entries get made for pixels with fractional alpha values. The palette will probably only be useful to viewers showing the image against that same background colour. And it's recommended that PNG editors delete or recompute the palette if they change or remove the bKGD chunk in a colour type 6 image โจ
If PLTE shows up without bKGD in a colour type 6 image, the circumstances under which the palette was computed are unspecified ๐คทโโ๏ธ
9.6. Filter selection ๐ง
for colour type 3 images (indexed colour), filter type 0 (None) is usually the most effective ๐ Also, colour images with 256 or fewer colours should almost always be stored in indexed colour format, because truecolour format is likely to be way bigger ๐
Filter type 0 is also recommended for bit depths less than 8. For low-bit-depth grayscale images, it might be a net win to expand the image to 8-bit and then apply filtering, but that's rare ๐ค
For truecolour and grayscale images, any of the five filters could turn out to be the most effective ๐ธ If an encoder uses one fixed filter, the Paeth filter is the most likely to be the best ๐
For the best compression of truecolour and grayscale images, the spec recommends an adaptive filtering approach, where a filter gets chosen for each scanline โจ The simple heuristic that did well in early tests goes like this: compute the output scanline using all five filters, then select the filter that gives the smallest sum of absolute values of outputs. (Treat the output bytes as signed differences for this test.) This usually beats any single fixed filter choice ๐ Honestly though, much better heuristics will probably be found as people get more experience with PNG, so keep an eye out ๐
Filtering like this works just as well on interlaced images as on noninterlaced ones ๐ฆ
9.7. Text chunk processing ๐ฌ
Every text chunk needs a nonempty keyword ๐ If you don't have a better description, the generic keyword "Comment" is fine. If you're using a user-supplied keyword, double-check that it meets the restrictions on keywords ๐
PNG text strings are expected to use the Latin-1 character set. Encoders should avoid storing characters that aren't defined in Latin-1, and should do character code remapping if the local system isn't using Latin-1 ๐
Encoders should discourage single lines of text longer than 79 characters, so it stays easy to read ๐
Text items smaller than 1K (1024 bytes) should be output using uncompressed tEXt chunks ๐ Specifically, the basic title and author keywords should always go in uncompressed tEXt chunks, that's a must ๐ค Long disclaimers, on the other hand, are ideal candidates for zTXt ๐
Putting big tEXt and zTXt chunks after the image data (after IDAT) can speed up image display in some situations, since the decoder doesn't have to read through the text to reach the image data ๐ But small text chunks, like the image title, are recommended to show up before IDAT ๐ฆ
9.8. Use of private chunks ๐
Apps can use PNG private chunks to carry information that other apps don't need to understand ๐ง These chunks must be named with lowercase second letters, so they can never clash with a future public chunk definition โจ Heads up though: nothing guarantees that some other app won't use the same private chunk name ๐ So if you use a private chunk type, it's smart to store extra identifying info at the start of the chunk data ๐
Use an ancillary chunk type (lowercase first letter), not a critical one, for all private chunks that store info that isn't absolutely essential to view the image ๐ซถ Making private critical chunks is discouraged, because they make PNG files unportable ๐ Those chunks shouldn't be used in publicly available software or files. If your app really needs private critical chunks, it's recommended to put one near the start of the file, so a standard decoder doesn't have to read very far before realising it can't handle the file ๐
If you want people outside your organisation to understand a chunk type you invented, contact the maintainers of the PNG specification to submit a proposed chunk name and definition for the list of special-purpose public chunks (see Additional chunk types, Section 4.4) ๐ธ A proposed public chunk name (one with an uppercase second letter) must not be used in publicly available software or files until registration has been approved. No shortcuts, bestie ๐ โโ๏ธ
If an ancillary chunk holds textual info that a human user might find interesting, don't invent a special chunk type for it ๐ซ Use a tEXt chunk and define a suitable keyword instead. That way the info is available to users who aren't using your software too ๐
Keywords in tEXt chunks should be reasonably self-explanatory, since the whole point is letting other people figure out what the chunk contains ๐ง If a keyword is generally useful, it can be registered with the maintainers of the PNG specification. But it's permissible to use keywords without registering them first ๐
9.9. Private type and method codes ๐ข
This spec only defines what some of the possible values of some fields mean. For example, only compression method 0 and filter types 0 through 4 are defined ๐ When you invent experimental or private values for any of these fields, use numbers greater than 127. Numbers below 128 are reserved for possible future public extensions of this spec ๐ Heads up, private type codes can make a file unreadable by standard decoders ๐ญ So they're strongly discouraged except for experimental purposes, and they shouldn't show up in publicly available software or files ๐ซ
10. Recommendations for Decoders ๐งโโ๏ธ
This chapter gives some recommendations for decoder behaviour ๐ The only absolute requirement on a PNG decoder is that it successfully reads any file conforming to the format described in the earlier chapters. Still, you'll usually get the best results by following these recommendations โจ
10.1. Error checking ๐ต๏ธโโ๏ธ
To catch common file-transfer problems early, decoders should verify that all eight bytes of the PNG file signature are correct ๐ (See Rationale: PNG file signature, Section 12.11.) A decoder can be extra confident about the file's integrity if the next eight bytes are an IHDR chunk header with the correct chunk length ๐ฏ
Unknown chunk types must be handled as described in Chunk naming conventions (Section 3.3) ๐งฉ An unknown chunk type isn't treated as an error unless it's a critical chunk ๐ซถ
It is strongly recommended that decoders verify the CRC on each chunk ๐ก๏ธโจ
In some situations it's worth checking a chunk's header (length and type code) before reading the chunk data and CRC ๐โจ. The type can be sanity-checked by seeing whether all four bytes are ASCII letters (codes 65-90 and 97-122) ๐. Note that this only needs doing for unrecognized type codes ๐ . If the total file size is known (from file system info, HTTP, etc.), the chunk length can be sanity-checked too ๐ฆ.
Why bother? If CRCs aren't checked, dropped or added data bytes, or a messed-up chunk length, can make the decoder get out of step and read random data as a chunk header ๐ญ. Checking that the type is all letters is a cheap way to catch that early ๐ซถ.
For known-length chunks like IHDR, decoders SHOULD treat an unexpected chunk length as an error ๐ โโ๏ธ. Future extensions will not add new fields to existing chunks. Instead, they'll add brand new chunk types to carry new info ๐ง.
Unexpected values in fields of known chunks (like an unexpected compression method in IHDR) MUST be checked for and treated as errors ๐จ. However, it is RECOMMENDED that unexpected field values only be treated as fatal errors in critical chunks ๐. An unexpected value in an ancillary chunk can be handled by ignoring the whole chunk as if it were an unknown chunk type ๐ธ. (This assumes the chunk's CRC has been verified. Decoders that don't check CRCs should treat any unexpected value as a sign of a corrupted file, because that's safer ๐.)
10.2. Pixel dimensions โจ
Non-square pixels can be represented (see the pHYs chunk), but viewers are not REQUIRED to account for them. A viewer can present any PNG file as though its pixels are square ๐ฒ.
Conversely, viewers running on display hardware with non-square pixels are strongly encouraged to rescale images for proper display ๐.
10.3. Truecolor image handling ๐
To achieve PNG's goal of universal interchangeability, decoders are REQUIRED to accept all types of PNG image: indexed-color, truecolor, and grayscale ๐ซถ. Viewers running on indexed-color display hardware need to be able to reduce truecolor images to indexed format for viewing. This process is usually called "color quantization" ๐จ.
A simple, fast way of doing this is to reduce the image to a fixed palette. Palettes with uniform color spacing ("color cubes") are usually used to minimize the per-pixel computation. For photograph-like images, dithering is RECOMMENDED to avoid ugly contours in what should be smooth gradients. However, dithering introduces graininess that can be objectionable ๐ธ.
The quality of rendering can be improved substantially by using a palette chosen specifically for the image, since a color cube usually has numerous entries that are unused in any particular image. This approach requires more work, first in choosing the palette, and second in mapping individual pixels to the closest available color. PNG allows the encoder to supply a suggested palette in a PLTE chunk, but not all encoders will do so, and the suggested palette may be unsuitable in any case (it may have too many or too few colors). High-quality viewers will therefore need to have a palette selection routine at hand. A large lookup table is usually the most feasible way of mapping individual pixels to palette entries with adequate speed ๐.
Numerous implementations of color quantization are available. The PNG reference implementation, libpng, includes code for the purpose ๐งฐ.
10.4. Sample depth rescaling ๐
Decoders MAY wish to scale PNG data to a lesser sample depth (data precision) for display. For example, 16-bit data will need to be reduced to 8-bit depth for use on most present-day display hardware. Reduction of 8-bit data to 5-bit depth is also common ๐ป.
The most accurate scaling is achieved by the linear equation
output = ROUND(input * MAXOUTSAMPLE / MAXINSAMPLE)
where
MAXINSAMPLE = (2^sampledepth)-1
MAXOUTSAMPLE = (2^desired_sampledepth)-1
A slightly less accurate conversion is achieved by simply shifting right by sampledepth-desired_sampledepth places. For example, to reduce 16-bit samples to 8-bit, you only need to discard the low-order byte. In many situations the shift method is accurate enough for display purposes, and it is certainly much faster โก. (But if gamma correction is being done, sample rescaling can be merged into the gamma correction lookup table, as is illustrated in Decoder gamma handling, Section 10.5.)
When an sBIT chunk is present, the original pre-PNG data can be recovered by shifting right to the sample depth specified by sBIT. Note that linear scaling will not necessarily reproduce the original data, because the encoder is not REQUIRED to have used linear scaling to scale the data up. However, the encoder is REQUIRED to have used a method that preserves the high-order bits, so shifting always works. This is the only case in which shifting might be said to be more accurate than linear scaling ๐ฏ.
When comparing pixel values to tRNS chunk values to detect transparent pixels, it is necessary to do the comparison exactly. Therefore, transparent pixel detection MUST be done before reducing sample precision ๐ซ.
10.5. Decoder gamma handling ๐
See Gamma Tutorial (Chapter 13) if you aren't already familiar with gamma issues ๐.
To produce correct tone reproduction, a good image display program SHOULD take into account the gammas of the image file and the display device, as well as the viewing_gamma appropriate to the lighting conditions near the display. This can be done by calculating
gbright = insample / MAXINSAMPLE
bright = gbright ^ (1.0 / file_gamma)
vbright = bright ^ viewing_gamma
gcvideo = vbright ^ (1.0 / display_gamma)
fbval = ROUND(gcvideo * MAXFBVAL)
where MAXINSAMPLE is the maximum sample value in the file (255 for 8-bit, 65535 for 16-bit, etc), MAXFBVAL is the maximum value of a frame buffer sample (255 for 8-bit, 31 for 5-bit, etc), insample is the value of the sample in the PNG file, and fbval is the value to write into the frame buffer. The first line converts from integer samples into a normalized 0 to 1 floating point value. The second undoes the gamma encoding of the image file to produce a linear intensity value. The third adjusts for the viewing conditions, the fourth corrects for the display system's gamma value, and the fifth converts to an integer frame buffer sample. In practice, the second through fourth lines can be merged into
gcvideo = gbright^(viewing_gamma / (file_gamma*display_gamma))
so as to perform only one power calculation. For color images, the entire calculation is performed separately for R, G, and B values ๐.
It is not necessary to perform transcendental math for every pixel. Instead, compute a lookup table that gives the correct output value for every possible sample value. This requires only 256 calculations per image (for 8-bit accuracy), not one or three calculations per pixel ๐งฎ. For an indexed-color image, a one-time correction of the palette is sufficient, unless the image uses transparency and is being displayed against a nonuniform background.
In some cases even the cost of computing a gamma lookup table may be a concern. In these cases, viewers are encouraged to have precomputed gamma correction tables for file_gamma values of 1.0 and 0.5 with some reasonable choice of viewing_gamma and display_gamma. They should also use the table closest to the gamma indicated in the file. This will produce acceptable results for the majority of real files ๐.
When the incoming image has unknown gamma (no gAMA chunk), choose a likely default file_gamma value, but allow the user to select a new one if the result proves too dark or too light ๐ถ๏ธ.
In practice, it is often difficult to determine what value of display_gamma should be used. In systems with no built-in gamma correction, the display_gamma is determined entirely by the CRT. Assuming a CRT_gamma of 2.5 is RECOMMENDED, unless you have detailed calibration measurements of this particular CRT available ๐บ.
However, many modern frame buffers have lookup tables that are used to perform gamma correction, and on these systems the display_gamma value SHOULD be the gamma of the lookup table and CRT combined. You may not be able to find out what the lookup table contains from within an image viewer application, so you may have to ask the user what the system's gamma value is. Unfortunately, different manufacturers use different ways of specifying what should go into the lookup table, so interpretation of the system gamma value is system-dependent. Gamma Tutorial (Chapter 13) gives some examples ๐คทโโ๏ธ.
The response of real displays is actually more complex than can be described by a single number (display_gamma). If actual measurements of the monitor's light output as a function of voltage input are available, the fourth and fifth lines of the computation above can be replaced by a lookup in these measurements. That finds the actual frame buffer value that most nearly gives the desired brightness ๐ฌ.
The value of viewing_gamma depends on lighting conditions. See Gamma Tutorial (Chapter 13) for more detail. Ideally, a viewer would allow the user to specify viewing_gamma, either directly numerically, or by selecting from "bright surround", "dim surround", and "dark surround" conditions. Viewers that don't want to do this SHOULD just assume a value for viewing_gamma of 1.0, since most computer displays live in brightly-lit rooms ๐ก.
When viewing images that are digitized from video, or that are destined to become video frames, the user might want to set the viewing_gamma to about 1.25, regardless of the actual level of room lighting. This value of viewing_gamma is "built into" NTSC video practice. Displaying an image with that viewing_gamma allows the user to see what a TV set would show under the current room lighting conditions. (This is not the same thing as trying to obtain the most accurate rendition of the content of the scene, which would require adjusting viewing_gamma to correspond to the room lighting level.) This is another reason viewers might want to allow users to adjust viewing_gamma directly ๐ผ.
10.6. Decoder color handling ๐จ
See Color Tutorial (Chapter 14) if you aren't already familiar with color issues ๐๏ธ.
In many cases, decoders will treat image data in PNG files as device-dependent RGB data and display it without modification (except for appropriate gamma correction). This provides the fastest display of PNG images. But unless the viewer uses exactly the same display hardware as the original image author used, the colors will not be exactly the same as the original author saw, particularly for darker or near-neutral colors. The cHRM chunk provides information that allows closer color matching than gamma correction alone can give ๐ท.
Okay so bestie, decoders can use the cHRM data to turn RGB into XYZ and then into a perceptually linear space like CIE LAB ๐ The reason this is so iconic is that distance in CIE LAB actually matches how different two colours look, which RGB and XYZ literally can't do ๐ญ So decoders can partition colours into a really optimal palette. Once that palette goes back into RGB, it can be used for display or written into a PLTE chunk โจ
Image processing apps might also convert image data into CIE LAB for analysis ๐งช
Now, if colour fidelity matters a LOT, like in product design, scientific visualisation, medicine, architecture, or advertising ๐ , decoders can transform the image data from source_RGB into the display_RGB space of the monitor the image is being viewed on. That means calculating the matrix from source_RGB to XYZ, and the matrix from XYZ to display_RGB, and then combining them into one overall transformation ๐ The decoder is the one responsible for gamut mapping, so it's on you, bestie ๐ซถ
Decoders on platforms with a Color Management System (CMS) can hand over the image data, gAMA and cHRM values to the CMS for display or further processing ๐
Decoders with colour printing facilities can use Level 2 PostScript to specify image data in calibrated RGB space or in a device-independent colour space like XYZ. This gives way better colour fidelity than a simple RGB to CMYK conversion ๐จ๏ธโจ The PostScript Language Reference manual has examples of how to do this [POSTSCRIPT]. These decoders are responsible for gamut mapping between source_RGB (as specified in the cHRM chunk) and the target printer. After that, the PostScript interpreter takes over and produces the required colours ๐ธ
Decoders can also use cHRM data to work out an accurate greyscale version of a colour image ๐ฉถ Going from RGB to grey is basically just calculating the Y (luminance) component of XYZ, which is a weighted sum of the R, G and B values. The weights depend on the monitor type, i.e. the values in the cHRM chunk. Decoders may want to do this for PNG files that have no cHRM chunk. In that case, a reasonable default is the CCIR 709 primaries [ITU-BT709]. Do not use the original NTSC primaries unless you really do have an image colour-balanced for that kind of monitor ๐ Very few monitors ever used the NTSC primaries, so those images are probably nonexistent these days ๐
10.7. Background color ๐
The background colour given by bKGD will typically be used to fill unused screen space around the image, as well as any transparent pixels inside the image. (So bKGD is valid and useful even when the image doesn't use transparency, iykyk ๐) If there's no bKGD chunk, the viewer has to make its own decision about a suitable background colour ๐คทโโ๏ธ
Viewers that have a specific background to show the image against (like Web browsers) should ignore the bKGD chunk ๐ In effect, they override bKGD with their preferred background colour or background image. Main character energy ๐
The background colour given by bKGD is not to be considered transparent, even if it happens to match the colour given by tRNS (or, in the case of an indexed-color image, refers to a palette index that tRNS marks as transparent). Otherwise you'd have to imagine something "behind the background" to composite against, and that's just chaos ๐ The background colour is either used as the background or ignored. It is NOT an intermediate layer between the PNG image and some other background ๐ฆ
Honestly this one's a vibe: bKGD is either used or ignored, and that's it. No hidden layers!
Indeed, it's going to be really common for bKGD and tRNS to specify the same colour ๐ That's because a decoder that doesn't implement transparency processing will still give the intended display, at least when no partially-transparent pixels are present ๐ง
10.8. Alpha channel processing ๐ฆ
In the most general case, the alpha channel can be used to composite a foreground image against a background image ๐ The PNG file defines the foreground image and the transparency mask, but NOT the background image. Decoders are not required to support this most general case. It's expected that most of them will be able to support compositing against a single background colour though ๐
The equation for computing a composited sample value is:
output = alpha * foreground + (1-alpha) * background
Here, alpha and the input and output sample values are expressed as fractions in the range 0 to 1. This computation should be performed with linear (non-gamma-encoded) sample values ๐ง For colour images, you do the computation separately for the R, G, and B samples. Periodt ๐
The code below shows the general case of compositing a foreground image over a background image โจ It assumes you have the original pixel data for the background image, and that the output is going to a frame buffer for display. Other variants are possible, so check the comments below the code ๐ The code lets the sample depths and gamma values of the foreground image, background image, and frame buffer/CRT all be different. Don't assume they're the same without checking, bestie ๐ โโ๏ธ
This code is standard C, with line numbers added so the comments below can reference them ๐งโโ๏ธ
01 int foreground[4]; /* image pixel: R, G, B, A */
02 int background[3]; /* background pixel: R, G, B */
03 int fbpix[3]; /* frame buffer pixel */
04 int fg_maxsample; /* foreground max sample */
05 int bg_maxsample; /* background max sample */
06 int fb_maxsample; /* frame buffer max sample */
07 int ialpha;
08 float alpha, compalpha;
09 float gamfg, linfg, gambg, linbg, comppix, gcvideo;
/* Get max sample values in data and frame buffer */
10 fg_maxsample = (1 << fg_sample_depth) - 1;
11 bg_maxsample = (1 << bg_sample_depth) - 1;
12 fb_maxsample = (1 << frame_buffer_sample_depth) - 1;
/*
* Get integer version of alpha.
* Check for opaque and transparent special cases;
* no compositing needed if so.
*
* We show the whole gamma decode/correct process in
* floating point, but it would more likely be done
* with lookup tables.
*/
13 ialpha = foreground[3];
14 if (ialpha == 0) {
/*
* Foreground image is transparent here.
* If the background image is already in the frame
* buffer, there is nothing to do.
*/
15 ;
16 } else if (ialpha == fg_maxsample) {
/*
* Copy foreground pixel to frame buffer.
*/
17 for (i = 0; i < 3; i++) {
18 gamfg = (float) foreground[i] / fg_maxsample;
19 linfg = pow(gamfg, 1.0/fg_gamma);
20 comppix = linfg;
21 gcvideo = pow(comppix,viewing_gamma/display_gamma);
22 fbpix[i] = (int) (gcvideo * fb_maxsample + 0.5);
23 }
24 } else {
/*
* Compositing is necessary.
* Get floating-point alpha and its complement.
* Note: alpha is always linear; gamma does not
* affect it.
*/
25 alpha = (float) ialpha / fg_maxsample;
26 compalpha = 1.0 - alpha;
27 for (i = 0; i < 3; i++) {
/*
* Convert foreground and background to floating
* point, then linearize (undo gamma encoding).
*/
28 gamfg = (float) foreground[i] / fg_maxsample;
29 linfg = pow(gamfg, 1.0/fg_gamma);
30 gambg = (float) background[i] / bg_maxsample;
31 linbg = pow(gambg, 1.0/bg_gamma);
/*
* Composite.
*/
32 comppix = linfg * alpha + linbg * compalpha;
/*
* Gamma correct for display.
* Convert to integer frame buffer pixel.
*/
33 gcvideo = pow(comppix,viewing_gamma/display_gamma);
34 fbpix[i] = (int) (gcvideo * fb_maxsample + 0.5);
35 }
36 }
Variations:
- If the output is going to another PNG image file instead of a frame buffer, lines 21, 22, 33, and 34 should be changed to something like this ๐
/*
* Gamma encode for storage in output file.
* Convert to integer sample value.
*/
gamout = pow(comppix, outfile_gamma);
outpix[i] = (int) (gamout * out_maxsample + 0.5);
Also, it becomes necessary to process background pixels when alpha is zero, rather than just skipping pixels. So line 15 needs to be replaced by copies of lines 17-23, but processing background instead of foreground pixel values ๐
- If the sample depths of the output file, foreground file, and background file are all the same, and the three gamma values also match, then the no-compositing code in lines 14-23 reduces to nothing more than copying pixel values from the input file to the output file when alpha is one, or copying pixel values from the background to the output file when alpha is zero. Since alpha is typically either zero or one for the vast majority of pixels in an image, this is a great savings ๐ค No gamma computations are needed for most pixels ๐
- When the sample depths and gamma values all match, it might look really attractive to skip the gamma decoding and encoding (lines 28-31, 33-34) and just perform line 32 using gamma-encoded sample values. Although this doesn't hurt image quality too badly, the time savings are small if alpha values of zero and one are special-cased as recommended here ๐ซ
- If the original pixel values of the background image are no longer available, and you only have the processed frame buffer pixels left over from displaying the background image, then lines 30 and 31 need to extract intensity from the frame buffer pixel values using code like this ๐งฉ
/*
* Decode frame buffer value back into linear space.
*/
gcvideo = (float) fbpix[i] / fb_maxsample;
linbg = pow(gcvideo, display_gamma / viewing_gamma);
However, some roundoff error can result, so it's better to have the original background pixels available if at all possible. ๐ฅน
- Note that lines 18-22 are performing exactly the same gamma computation that is done when no alpha channel is present. So, if you handle the no-alpha case with a lookup table, you can use the same lookup table here. Lines 28-31 and 33-34 can also be done with (different) lookup tables.
- Of course, everything here can be done in integer arithmetic. Just be careful to maintain sufficient precision all the way through.
Note: in floating point, you don't need overflow or underflow checks ๐ซถ, because the input sample values are guaranteed to be between 0 and 1, and compositing always gives a result that lands in between the input values (inclusive). With integer arithmetic, you might need some roundoff-error analysis to guarantee no overflow or underflow. ๐งฎ
When displaying a PNG image with full alpha channel, it's super important to be able to composite the image against some background, even if it's only black. ๐ค Ignoring the alpha channel will make PNG images that were converted from an associated-alpha representation look wrong. ๐ญ (Of course, if the alpha channel is a separate transparency mask, then ignoring alpha is a useful option, because it lets you recover the hidden parts of the image. ๐ต๏ธโโ๏ธ)
Even if the decoder author doesn't want to build true compositing logic, it's simple to deal with images that only contain zero and one alpha values. ๐ (This is implicitly true for grayscale and truecolor PNG files that use a tRNS chunk; for indexed-color PNG files, it's easy to check whether tRNS contains any values other than 0 and 255.) In this simple case, transparent pixels get replaced by the background color, while everything else stays the same. โจ If a decoder only has this much transparency capability, it should deal with a full alpha channel by treating all nonzero alpha values as fully opaque. That means do not replace partially transparent pixels by the background. This approach won't give very good results for images converted from associated-alpha formats, but it's better than doing nothing. ๐ธ
10.9. Progressive display ๐ฆ
When receiving images over slow transmission links, decoders can improve perceived performance by displaying interlaced images progressively. โณ This means that as each pass is received, an approximation to the complete image is displayed based on the data received so far. One simple yet pleasing effect can be obtained by expanding each received pixel to fill a rectangle covering the yet-to-be-transmitted pixel positions below and to the right of the received pixel. ๐ This process can be described by the following pseudocode:
Starting_Row [1..7] = { 0, 0, 4, 0, 2, 0, 1 }
Starting_Col [1..7] = { 0, 4, 0, 2, 0, 1, 0 }
Row_Increment [1..7] = { 8, 8, 8, 4, 4, 2, 2 }
Col_Increment [1..7] = { 8, 8, 4, 4, 2, 2, 1 }
Block_Height [1..7] = { 8, 8, 4, 4, 2, 2, 1 }
Block_Width [1..7] = { 8, 4, 4, 2, 2, 1, 1 }
pass := 1
while pass <= 7
begin
row := Starting_Row[pass]
while row < height
begin
col := Starting_Col[pass]
while col < width
begin
visit (row, col,
min (Block_Height[pass], height - row),
min (Block_Width[pass], width - col))
col := col + Col_Increment[pass]
end
row := row + Row_Increment[pass]
end
pass := pass + 1
end
Here, the function visit(row,column,height,width) obtains the next transmitted pixel and paints a rectangle of the specified height and width, whose upper-left corner is at the specified row and column, using the color indicated by the pixel. ๐จ Note that row and column are measured from 0,0 at the upper left corner. ๐
If the decoder is merging the received image with a background image, it may be more convenient to just paint the received pixel positions. That is, the visit() function sets only the pixel at the specified row and column, not the whole rectangle. ๐ซ This produces a "fade-in" effect as the new image gradually replaces the old one. ๐ฅฐ An advantage of this approach is that proper alpha or transparency processing can be done as each pixel is replaced. Painting a rectangle as described above will overwrite background-image pixels that may be needed later, if the pixels eventually received for those positions turn out to be wholly or partially transparent. ๐ฌ Of course, this is only a problem if the background image isn't stored anywhere offscreen.
10.10. Suggested-palette and histogram usage ๐
In truecolor PNG files, the encoder may have provided a suggested PLTE chunk for use by viewers running on indexed-color hardware. ๐ฅ๏ธ
If the image has a tRNS chunk, the viewer will need to adapt the suggested palette for use with its desired background color. To do this, replace the palette entry closest to the tRNS color with the desired background color. Or just add a palette entry for the background color, if the viewer can handle more colors than there are PLTE entries. ๐จ
For images of color type 6 (truecolor with alpha channel), any suggested palette should have been designed for display of the image against a uniform background of the color specified by bKGD. Viewers should probably ignore the palette if they intend to use a different background, or if the bKGD chunk is missing. Viewers can use a suggested palette for display against a different background than it was intended for, but the results may not be very good. ๐
If the viewer presents a transparent truecolor image against a background that's more complex than a single color, it's unlikely that the suggested palette will be optimal for the composite image. In this case, it's best to perform a truecolor compositing step on the truecolor PNG image and background image, then color-quantize the resulting image. ๐ง
The histogram chunk is useful when the viewer can't provide as many colors as are used in the image's palette. If the viewer is only short a few colors, it's usually adequate to drop the least-used colors from the palette. To reduce the number of colors substantially, it's best to choose entirely new representative colors, rather than trying to use a subset of the existing palette. This amounts to performing a new color quantization step. However, the existing palette and histogram can be used as the input data, which avoids a scan of the image data. ๐ง
If no palette or histogram chunk is provided, a decoder can develop its own, at the cost of an extra pass over the image data. Alternatively, a default palette (probably a color cube) can be used. ๐ง
See also Recommendations for Encoders: Suggested palettes (Section 9.5). ๐
10.11. Text chunk processing ๐
If practical, decoders should have a way to display to the user all tEXt and zTXt chunks found in the file. ๐ฌ Even if the decoder doesn't recognize a particular text keyword, the user might be able to understand it. ๐ซถ
PNG text isn't supposed to contain any characters outside the ISO 8859-1 "Latin-1" character set (that is, no codes 0-31 or 127-159), except for the newline character (decimal 10). But decoders might run into such characters anyway. ๐ณ Some of these characters can be safely displayed (for example, TAB, FF, and CR, decimal 9, 12, and 13, respectively). Others, especially the ESC character (decimal 27), could pose a security hazard because unexpected actions may be taken by display hardware or software. ๐จ To prevent such hazards, decoders should not attempt to directly display any non-Latin-1 characters (except for newline and perhaps TAB, FF, CR) encountered in a tEXt or zTXt chunk. Instead, ignore them or display them in a visible notation such as "\nnn". See Security considerations (Section 8.5). ๐
Even though encoders are supposed to represent newlines as LF, it is recommended that decoders not rely on this. It's best to recognize all the common newline combinations (CR, LF, and CR-LF) and display each as a single newline. โฉ๏ธ TAB can be expanded to the proper number of spaces needed to arrive at a column multiple of 8. โก๏ธ
Decoders running on systems with non-Latin-1 character set encoding should provide character code remapping so that Latin-1 characters are displayed correctly. ๐ Some systems may not provide all the characters defined in Latin-1. Mapping unavailable characters to a visible notation such as "\nnn" is a good fallback. ๐ In particular, character codes 127-255 should be displayed only if they are printable characters on the decoding system. Some systems may interpret such codes as control characters. For security, decoders running on such systems should not display such characters literally. ๐
โโ๏ธ
Decoders should be prepared to display text chunks that contain any number of printing characters between newline characters, even though encoders are encouraged to avoid creating lines in excess of 79 characters. ๐ Long lines happen, bestie, so be ready for them. ๐
11. Glossary โจ
a^b Exponentiation, bestie: a raised to the power b. ๐งฎ C programmers, be careful not to read this as exclusive-or ๐. In gamma-related calculations, zero raised to any power is valid and must give a zero result.
Alpha Alpha is the value that tells you how see-through a pixel is. The more transparent it is, the less it blocks whatever background it's sitting on ๐ฆ. In PNG, alpha is really about opacity: zero alpha means a completely transparent pixel, and maximum alpha means a completely opaque one. Most people still talk about alpha as transparency info instead of opacity info, so we're keeping that vibe here ๐ .
Ancillary chunk A chunk that adds extra info โจ. A decoder can still make a meaningful image without processing it, though maybe not the best possible image ๐ธ.
Bit depth The number of bits per palette index (for indexed-color PNGs) or per sample (for every other color type) ๐. It's the same value that shows up in IHDR.
Byte Eight bits, also called an octet ๐.
Channel All the samples of the same kind in an image. For example, all the blue samples in a truecolor image ๐. ("Component" also gets used, but not in this spec.) A sample is where a channel and a pixel intersect ๐.
Chromaticity A pair of values, x,y, that pin down the hue of a perceived color, but not how bright it is ๐.
Chunk A section of a PNG file ๐งฉ. Every chunk has a type, shown by its chunk type name. Most types also carry some data, and the format and meaning of that data are set by the type name.
Composite As a verb, to build one image by merging a foreground image onto a background image, using transparency info to decide where the background peeks through ๐ซถ. The foreground is said to be "composited against" the background.
CRC Cyclic Redundancy Check ๐. It's a check value designed to catch most transmission errors. The decoder works out the CRC for the data it received and compares it to the CRC the encoder calculated and appended to the data. If they don't match, the data got corrupted in transit ๐.
Critical chunk A chunk that the decoder must understand and process in order to make a meaningful image from a PNG file ๐.
CRT Cathode Ray Tube: a classic type of computer display hardware ๐บ.
Datastream A sequence of bytes. We use this instead of "file" when we're talking about a byte sequence that's only part of a file ๐ง. It also makes clear that a PNG image might be generated and consumed "on the fly" and never show up in a stored file at all ๐.
Deflate The name of the compression algorithm used in standard PNG files, and also in zip, gzip, pkzip, and other compression programs ๐๏ธ. Deflate is a member of the LZ77 family of compression methods.
Filter A transformation applied to image data, hoping it'll become more compressible ๐ช. PNG only uses lossless (reversible) filter algorithms.
Frame buffer The final digital storage area for the image a computer display shows ๐ฅ๏ธ. Software puts an image onscreen by loading it into the frame buffer.
Gamma The brightness of the mid-level tones in an image ๐ค๏ธ. More precisely, it's a parameter describing the shape of the transfer function for one or more stages in an imaging pipeline. The transfer function is given by the expression
output = input ^ gamma
where both input and output are scaled to the range 0 to 1.
Grayscale An image representation where each pixel is a single sample for overall luminance, running from black to white โชโซ. PNG also lets grayscale images store an alpha sample for each pixel.
Indexed color An image representation where each pixel is one sample that works as an index into a palette or lookup table ๐จ. The palette entry it picks is the actual color of the pixel.
Lossless compression Any compression method that guarantees the original data can be rebuilt exactly, bit-for-bit ๐ฏ.
Lossy compression Any compression method that rebuilds the original data only approximately, not exactly ๐ซ .
LSB Least Significant Byte of a multi-byte value ๐ข.
Luminance The perceived brightness, or grayscale level, of a color โ๏ธ. Luminance and chromaticity together fully define a perceived color.
LUT Look Up Table. In general, it's a table used to transform data ๐. In frame buffer hardware, a LUT can map indexed-color pixels to a chosen set of truecolor values, or do gamma correction. In software, a LUT is a fast way to implement any one-variable mathematical function โก.
MSB Most Significant Byte of a multi-byte value ๐.
Palette The set of colors available in an indexed-color image ๐ท. In PNG, a palette is an array of colors defined by red, green, and blue samples. Alpha values can also be defined for palette entries, through the tRNS chunk.
Pixel The info stored for a single grid point in the image ๐. The complete image is a rectangular array of pixels.
PNG editor A program that modifies a PNG file and keeps ancillary info, including chunks it doesn't recognize ๐ ๏ธ. Such a program must obey the rules given in Chunk Ordering Rules (Chapter 7).
Sample A single number in the image data, like the red value of a pixel ๐ด. A pixel is made of one or more samples. When we're talking about physical data layout (especially in Image layout, Section 2.3), "sample" means a number stored in the image array. It would be more precise, but way less readable, to say "sample or palette index" in that context ๐ค. Everywhere else in the spec, "sample" means a color value or alpha value. In the indexed-color case, those are palette entries, not palette indexes.
Sample depth The precision, in bits, of color values and alpha values ๐ฏ. In indexed-color PNGs, sample depth is always 8 by definition of the PLTE chunk. For other color types, it's the same as the bit depth.
Scanline One horizontal row of pixels within an image โ.
Truecolor An image representation where pixel colors are defined by storing three samples per pixel, for red, green, and blue intensities ๐. PNG also lets truecolor images store an alpha sample for each pixel.
White point The chromaticity of a computer display's nominal white value ๐ค.
zlib A particular format for data that's been compressed with deflate-style compression ๐ฆ. It's also the name of a library that implements this method. PNG implementations don't need to use the zlib library, but they must conform to its format for compressed data.
12. Appendix: Rationale
(This appendix is not part of the formal PNG specification. ๐)
This appendix spills the tea on the reasoning behind some of PNG's design decisions ๐ซ. Many of these decisions were the subject of serious debate ๐ค. The authors freely admit another group might have made different calls, but honestly, they think their choices are defensible and consistent ๐ .
12.1. Why a new file format?
Does the world really need another graphics format? ๐ค The authors say yes. GIF is no longer freely usable, and no other commonly used format can directly replace it, as we'll get into below. They could have adapted an existing format, like GIF with an unpatented compression scheme ๐งฉ. But that would still need new code, and it wouldn't be much easier to implement than a whole new file format. (PNG is designed to be simple to implement, except for the compression engine, which would be needed anyway ๐ง.) They felt this was an excellent chance to design a new format that fixes some known GIF limitations ๐.
12.2. Why these features?
PNG's features are meant to cover what apps used to do with GIF's special strengths ๐. GIF is great for online communication because it streams well and can display progressively โฉ. PNG shares those qualities ๐ซถ.
PNG also fixes some of GIF's widely known shortcomings ๐ฉน. Most importantly, PNG supports truecolor images ๐. The authors don't know of any widely used image format that losslessly compresses truecolor images as effectively as PNG does ๐. They hope PNG makes truecolor images more practical and more widespread โจ.
Some form of transparency control is desirable when images are displayed against a background or alongside other images ๐ฆ. GIF gave a simple transparent-color specification for this ๐ข. PNG supports a full alpha channel as well as transparent-color specifications, which allows both highly flexible transparency and compression efficiency ๐.
Robustness against transmission errors has been a big deal ๐ก๏ธ. For example, images sent across the Internet are often mistakenly processed as text, which corrupts the file ๐ญ. PNG is designed so those errors can be detected quickly and reliably ๐.
PNG was expressly designed to not depend completely on a single compression technique ๐ท. Deflate/inflate compression is mentioned in this document, but PNG would still exist without it ๐ซง.
12.3. Why not these features?
Some features were deliberately left out of PNG ๐ โโ๏ธ. These choices were made to simplify implementation, promote portability and interchangeability, and make the format as simple and foolproof as possible for users ๐. In particular:
- There is no uncompressed variant of PNG. ๐ โโ๏ธ You can store uncompressed data by using only uncompressed deflate blocks (a feature normally used to guarantee that deflate does not make incompressible data much larger), but PNG software must support full deflate/inflate. Any software that doesn't is not compliant with the PNG standard. Portability and compression are the two most important features of PNG, and they're absolute requirements for online applications. Users demand them, bestie. Failing to support full deflate/inflate messes up both goals ๐
- There is no lossy compression in PNG. ๐ซ๐ธ Formats like JFIF already handle lossy compression really well. Also, lossy methods like JPEG are far from foolproof, because a bad quality level can ruin an image. To avoid user confusion and accidental loss of information, we think it's best to keep lossy and lossless formats strictly separate. Lossy compression is also complex to implement. Adding JPEG support to a PNG decoder might increase its size by an order of magnitude ๐ฑ That would make some decoders skip the feature entirely, and that would wreck our goal of interchangeability.
- There is no support for CMYK or other unusual color spaces. ๐จ Again, this is about promoting portability. CMYK especially is way too device-dependent to be useful as a portable image representation.
- There is no standard chunk for thumbnail views of images. ๐ผ๏ธ We talked to software vendors who use thumbnails in their products, and it became clear that most of them wouldn't use a "standard" thumbnail chunk. For one thing, every vendor has a different idea of what the dimensions and characteristics of a thumbnail ought to be. Also, some vendors keep thumbnails in separate files so they can handle different image formats, and they're not gonna stop doing that just because one new format has a thumbnail chunk. Proprietary chunks with vendor-specific thumbnails seem more practical than a common thumbnail format.
It's worth noting that private extensions to PNG could easily add these features. We won't, however, include them in the basic PNG standard. ๐
PNG also doesn't support multiple images in one file. โ๏ธ This is because a lot of applications don't need that and won't support multiple images per file anyway. Besides, single images are a totally different kind of object from sequences of images. Instead of making false promises about interchangeability, we drew a clear line between single-image and multi-image formats. PNG is a single-image format. (But see Multiple-image extension, Section 8.4. ๐)
12.4. Why not use format X? ๐ค
Lots of existing formats were considered before we decided to build PNG. None of them could meet the requirements we felt were important for PNG. ๐ค
GIF is no longer suitable as a universal standard because of legal entanglements. โ๏ธ Just replacing GIF's compression method would avoid that problem, but GIF also doesn't support truecolor images, alpha channels, or gamma correction. The spec has more subtle problems too. Only a small subset of the GIF89 spec is actually portable across a variety of implementations, and there's no codification of the most portable part of the spec. Not ideal, no cap.
TIFF is far too complex to meet our goals of simplicity and interchangeability. ๐ Defining a TIFF subset would fix that objection, but it would frustrate users who reasonably assume that a file saved as TIFF from their existing software would load into a program that supports our flavor of TIFF. Also, TIFF isn't designed for stream processing, has no provision for progressive display, and doesn't currently offer any good, legally unencumbered, lossless compression method. Iykyk ๐
IFF got suggested too, but it isn't suitable in detail. The available image representations are either too machine-specific or not adequately compressed. ๐งฉ The overall chunk structure of IFF is a super useful concept, and PNG borrowed it liberally, but we didn't try to be bit-for-bit compatible with the IFF chunk structure. That's because of detailed issues, most notably the fact that IFF FORMs aren't designed to be serially writable. ๐
Lossless JPEG isn't suitable because it doesn't provide for storing indexed-color images. ๐ซ Plus, its lossless truecolor compression is often worse than PNG's. Sorry, JPEG ๐ญ
12.5. Byte order ๐ข
People have asked why PNG uses network byte order. ๐คทโโ๏ธ We picked one byte ordering and used it consistently. Which order in particular matters very little, but network byte order has an advantage: routines to convert to and from it are already available on any platform that supports TCP/IP networking, including all PC platforms. The functions are trivial and will be included in the reference implementation. Easy peasy ๐
12.6. Interlacing ๐
PNG's two-dimensional interlacing scheme is more complex to implement than GIF's line-wise interlacing. It also costs a little more in file size. ๐ฆ However, it yields an initial image eight times faster than GIF. The first pass transmits only 1/64th of the pixels, compared to 1/8th for GIF. โก That initial image is coarse, but it's useful in many situations. For example, if the image is a World Wide Web imagemap that the user has seen before, PNG's first pass is often enough to figure out where to click. ๐ฑ๏ธ The PNG scheme also looks better than GIF's, because horizontal and vertical resolution never differ by more than a factor of two. This avoids the odd "stretched" look you get when interlaced GIFs are filled in by replicating scanlines. ๐ตโ๐ซ
Preliminary results show that small text in an interlaced PNG image is typically readable about twice as fast as in an equivalent GIF. That means it's readable after PNG's fifth pass, or 25% of the image data, instead of after GIF's third pass, or 50%. โจ This is again due to PNG's more balanced increase in resolution. Balance is everything, babe ๐
12.7. Why gamma? ๐
It might seem natural to standardize on storing sample values that are linearly proportional to light intensity (that is, have gamma of 1.0). ๐ But honestly, it's common for images to have a gamma of less than 1. There are three good reasons for this:
- For reasons detailed in Gamma Tutorial (Chapter 13), all video cameras apply a "gamma correction" function to the intensity information. ๐ฅ This causes the video signal to have a gamma of about 0.5 relative to the light intensity in the original scene. So images obtained by frame-grabbing video already have a gamma of about 0.5.
- The human eye has a nonlinear response to intensity, so linear encoding of samples either wastes sample codes in bright areas of the image, or provides too few sample codes to avoid banding artifacts in dark areas of the image, or both. ๐๏ธ At least 12 bits per sample are needed to avoid visible artifacts in linear encoding with a 100:1 image intensity range. An image gamma in the range 0.3 to 0.5 allocates sample values in a way that roughly matches the eye's response. That means 8 bits per sample are enough to avoid artifacts caused by insufficient sample precision in almost all images. ๐ This makes "gamma encoding" a much better way of storing digital images than the simpler linear encoding.
- Lots of images are created on PCs or workstations with no gamma correction hardware and no software willing to provide gamma correction either. ๐ฅ๏ธ In these cases, the images have had their lighting and color chosen to look best on that platform. You could say they have "manual" gamma correction built in. To see what the image author intended, you need to treat these images as having a
file_gammavalue in the range 0.4-0.6, depending on the room lighting level the author was working in. ๐ก
In practice, image gamma values around 1.0 and around 0.5 are both widely found. Older image standards like GIF often don't account for this at all. The JFIF standard specifies that images in that format should use linear samples, but many JFIF images found on the Internet actually have a gamma somewhere near 0.4 or 0.5. ๐ฌ The variety of images out there and the variety of systems people display them on have led to widespread problems with images looking "too dark" or "too light." Ugh, relatable ๐
PNG expects viewers to compensate for image gamma at the time the image is displayed. ๐ Another possible approach would be to expect encoders to convert all images to a uniform gamma at encoding time. That method would speed up viewers slightly, but it has some fundamental flaws:
- Gamma correction is inherently lossy due to quantization and roundoff error. ๐ Requiring conversion at encoding time causes irreversible loss. Since PNG is meant to be a lossless storage format, that's a big no, so we should store unmodified source data.
- The encoder might not know the source gamma value. If the decoder does gamma correction at viewing time, it can adjust the gamma (change the displayed brightness) in response to feedback from a human user. The encoder has no such recourse. ๐ โโ๏ธ
- Whatever "standard" gamma we settled on would be wrong for some displays. So viewers would still need gamma correction capability anyway. ๐ซ
Since there will always be images with no gamma or an incorrect recorded gamma, good viewers will need to include gamma adjustment code anyway. Gamma correction at viewing time is thus the right way to go. โ
See Gamma Tutorial (Chapter 13) for more information. ๐
12.8. Non-premultiplied alpha ๐ฆ
PNG uses "unassociated" or "non-premultiplied" alpha, so images with separate transparency masks can be stored losslessly. ๐ซง Another common technique, "premultiplied alpha", stores pixel values premultiplied by the alpha fraction. In effect, the image is already composited against a black background. Any image data hidden by the transparency mask is irretrievably lost with that method, since multiplying by a zero alpha value always produces zero. Gone forever, bestie ๐ญ
Some image rendering techniques generate images with premultiplied alpha, where the alpha value actually represents how much of the pixel is covered by the image. ๐ญ This representation can be converted to PNG by dividing the sample values by alpha, except where alpha is zero. The result will look good in a viewer that handles alpha properly, but it won't look very good if the viewer ignores the alpha channel. ๐ธ
Ok so we're picking up right where the alpha stuff left off, bestie ๐ โจ
We never wanted every PNG viewer to have to handle both forms of alpha ๐ซถ So we went with non-premultiplied alpha as the lossless, more general option ๐ธ
12.9. Filtering ๐ฆ
PNG has filtering because filtering can seriously shrink the compressed size of truecolor and grayscale images ๐ฅ It's sometimes useful on indexed-color images too, just less often ๐
The filter algorithms work on bytes, not pixels ๐ That keeps things simple and fast, and it costs very little in compression performance โจ Tests showed that filtering is usually ineffective for images with fewer than 8 bits per sample, so pixelwise filtering for those would be pointless ๐ For 16 bit/sample data, bytewise filtering is nearly as effective as pixelwise filtering, because MSBs get predicted from adjacent MSBs and LSBs get predicted from adjacent LSBs ๐ง
The encoder is allowed to change filters for each new scanline ๐ This adds no extra complexity for decoders, since a decoder is required to contain defiltering logic for every filter type anyway ๐ซถ The only cost is an extra byte per scanline in the pre-compression datastream ๐ Our tests showed that when the same filter is picked for every scanline, that extra byte compresses away to almost nothing, so there's barely any storage cost compared to a fixed filter for the whole image ๐ญ And honestly, the potential benefits of adaptive filtering are too big to ignore ๐ฅ Even with the simplistic filter-choice heuristics found so far, adaptive filtering usually beats fixed filters ๐ In particular, an adaptive filter can change behavior for successive passes of an interlaced image, and a fixed filter cannot ๐ฆ
12.10. Text strings ๐ธ
Most graphics file formats let you store some textual info along with the image ๐ But lots of apps need more than that. They want to store several identifiable pieces of text ๐ For example, a database using PNG files to store medical X-rays would probably want the patient's name, the doctor's name, etc. ๐ฉบ A simple way to do that would be to invent new private chunks for text ๐ The problem is that other apps would have no idea what's in those chunks, so they'd just ignore them ๐ญ So instead, we recommend storing textual info in standard tEXt chunks with suitable keywords โจ Using tEXt tells any PNG viewer that the chunk has text that might be interesting to a human user ๐ซถ So someone looking at the file in another viewer can still see the text, and can even understand it if the keywords are reasonably self-explanatory ๐ง (To that end, we recommend spelled-out keywords, not abbreviations that a person would struggle to understand. Saving a few bytes on a keyword is false economy ๐ )
The ISO 8859-1 (Latin-1) character set was chosen as a compromise between functionality and portability ๐ Some platforms can only display 7-bit ASCII characters, while others can handle characters beyond Latin-1 ๐ We felt Latin-1 is widely useful and reasonably portable ๐ฆ It's a direct subset of character sets commonly used on popular platforms like Microsoft Windows and X Windows ๐ It can also be handled on Macintosh systems with a simple remapping of characters ๐
There's currently no provision for text using character sets other than Latin-1 ๐ฅบ We get that the need for other character sets will grow ๐ But PNG already asks programmers to implement a bunch of new and unfamiliar features, and text representation isn't PNG's main purpose ๐ Since PNG allows for the creation and public registration of new ancillary chunks of general interest, we expect text chunks for other character sets, such as Unicode, will eventually get registered and gradually become more popular โญ
12.11. PNG file signature ๐
The first eight bytes of a PNG file always contain these values:
(decimal) 137 80 78 71 13 10 26 10
(hexadecimal) 89 50 4e 47 0d 0a 1a 0a
(ASCII C notation) \211 P N G \r \n \032 \n
This signature does two jobs ๐ It identifies the file as a PNG file, and it catches common file-transfer problems right away ๐ฅ The first two bytes tell PNG files apart on systems that expect the first two bytes to uniquely identify the file type ๐ง The first byte is a non-ASCII value, which lowers the chance that a text file gets misrecognized as a PNG ๐ It also catches bad transfers that clear bit 7 ๐ Bytes two through four name the format ๐ The CR-LF sequence catches bad transfers that alter newline sequences ๐ฆ The control-Z character stops file display under MS-DOS ๐ญ And the final line feed checks for the inverse of the CR-LF translation problem โจ
A decoder may further verify that the next eight bytes contain an IHDR chunk header with the correct chunk length ๐ซถ This will catch bad transfers that drop or alter null (zero) bytes ๐ธ
Note that there's no version number in the signature, or anywhere else in the file ๐ This is on purpose: the chunk mechanism is a better, more flexible way to handle format extensions, as explained in Chunk naming conventions (Section 12.13) ๐
12.12. Chunk layout ๐ง
The chunk design lets decoders skip unrecognized or uninteresting chunks ๐ฆ All they have to do is skip the right number of bytes, which comes from the length field ๐
Limiting chunk length to (2^31)-1 bytes avoids possible problems for implementations that can't conveniently handle 4-byte unsigned values ๐ In practice, chunks will usually be way shorter than that anyway ๐
Each chunk gets its own CRC so badly transferred images can be detected as quickly as possible ๐ฅ That means critical data, like the image dimensions, can be validated before anything uses it โจ
The chunk length is left out of the CRC so the CRC can be calculated as the data is generated ๐ซถ That avoids a second pass over the data in cases where the chunk length isn't known in advance ๐ Leaving the length out doesn't add any extra risk of missing file corruption, though ๐ If the length is wrong, the CRC check will still fail, because the CRC gets computed over the wrong set of bytes and then tested against the wrong value from the file ๐ญ
12.13. Chunk naming conventions ๐
The chunk naming conventions allow safe, flexible extension of the PNG format ๐ This is way better than a format version number, because it works feature by feature instead of being one overall indicator ๐ฆ Decoders can process newer files if and only if the files use no unknown critical features, which shows up as finding unknown critical chunks ๐ฅ Unknown ancillary chunks can be safely ignored ๐ง We decided against an overall format version number because experience showed that version numbers hurt portability as much as they help ๐ Version numbers tend to get set unnecessarily high, which leads older decoders to reject files they could have processed (this was a serious problem for years after the GIF89 spec came out, for example) ๐ญ Also, private extensions can be made either critical or ancillary, and standard decoders should react appropriately, and overall version numbers are no help for private extensions ๐
A hypothetical chunk for vector graphics would be critical, because if it got ignored, important parts of the intended image would be missing ๐ A chunk carrying the Mandelbrot set coordinates for a fractal image would be ancillary, because other apps could still display the image without understanding what it represents ๐ธ In general, a chunk type should be made critical only if it's impossible to display a reasonable representation of the intended image without interpreting that chunk ๐
The public/private property bit makes sure any newly defined public chunk type name can't conflict with proprietary chunks that might be in use somewhere ๐ However, this doesn't protect users of private chunk names from someone else using the same name for a different purpose ๐ฌ It's a good idea to put additional identifying info at the start of the data for any private chunk type ๐ซถ
When a PNG file gets modified, certain ancillary chunks may need to change to reflect changes in other chunks โจ For example, a histogram chunk needs to change if the image data changes ๐ If the file editor doesn't recognize histogram chunks, copying them blindly to a new output file is incorrect, and such chunks should be dropped ๐ The safe/unsafe property bit lets ancillary chunks be marked appropriately ๐
Not every modification scenario is covered by the safe/unsafe semantics ๐ฅฒ In particular, chunks that depend on the total file contents aren't supported (an example is an index of IDAT chunk locations within the file, since adding a comment chunk would inadvertently break the index ๐) Definition of such chunks is discouraged ๐ฆ If it's absolutely necessary for a particular application, such chunks can be made critical chunks, with the consequent loss of portability to other applications ๐ญ In general, ancillary chunks can depend on critical chunks but not on other ancillary chunks ๐ฅ It's expected that mutually dependent info should be put into a single chunk ๐ซถ
Sometimes it may be unavoidable to make one ancillary chunk depend on another ๐ Although the chunk property bits aren't enough to represent this case, there's a simple solution: in the dependent chunk, record the CRC of the chunk it depends on ๐ง That way it can be determined whether that chunk has been changed by some other program ๐
The same technique can be useful for other purposes โจ For example, if a program relies on the palette being in a particular order, it can store a private chunk containing the CRC of the PLTE chunk ๐ธ If that value matches when the file is read in again, it gives high confidence that the palette hasn't been tampered with ๐ Note that it's not necessary to mark the private chunk unsafe-to-copy when using this technique, so such a private chunk can survive other editing of the file ๐
12.14. Palette histograms ๐
The palette tea is spilling, bestie ๐ซ๐ A viewer might not be able to show every color in the palette, because some colors can be reserved by the window system ๐ค To get the best results in that situation, it helps to know how often each palette index actually shows up, so you can pick the best palette for dithering or drop the least-used colors โจ Images usually get created once and viewed a bajillion times, so it makes sense for the encoder to calculate this, though it is not mandatory for the encoder to provide it ๐ธ
Other image formats mostly handled this by saying the palette entries should appear in order of how often they're used. ngl that's an inferior solution ๐ The viewer can't figure out how much damage dropping the last few colors will do, and a sorted palette doesn't give enough info to pick a target palette for dithering when the viewer has to cut the number of colors a lot. A palette histogram gives you everything you need to choose that target palette without making a pass over the image data ๐ฆ Main character info, honestly.
13. Appendix: Gamma Tutorial ๐
(This appendix is not part of the formal PNG specification.)
It would be convenient for graphics programmers if all of the components of an imaging system were linear ๐ฅน The voltage coming out of an electronic camera would be directly proportional to the intensity (power) of light in the scene, the light a CRT emits would be directly proportional to its input voltage, and so on. But real-world devices do not behave like that ๐ญ All CRT displays, almost all photographic film, and many electronic cameras have nonlinear signal-to-light-intensity or intensity-to-signal characteristics.
Fortunately, all of these nonlinear devices have a transfer function that is approximated fairly well by a single type of mathematical function: a power function โจ This power function has the general equation:
output = input ^ gamma
where ^ denotes exponentiation, and "gamma" (often printed using the Greek letter gamma, hence the name) is simply the exponent of the power function.
By convention, "input" and "output" are both scaled to the range 0..1, with 0 representing black and 1 representing maximum white (or red, etc). Normalized this way, the power function is completely described by a single number, the exponent "gamma" ๐
So, given a particular device, we can measure its output as a function of its input, fit a power function to this measured transfer function, extract the exponent, and call it gamma. We often say "this device has a gamma of 2.5" as a shorthand for "this device has a power-law response with an exponent of 2.5" ๐ We can also talk about the gamma of a mathematical transform, or of a lookup table in a frame buffer, so long as the input and output of the thing are related by the power-law expression above.
How do gammas combine? ๐งฉ
Real imaging systems have several components, and more than one of these can be nonlinear. If all of the components have transfer characteristics that are power functions, then the transfer function of the entire system is also a power function. The exponent (gamma) of the whole system's transfer function is just the product of all the individual exponents (gammas) of the separate stages in the system ๐ฅ
Also, stages that are linear pose no problem, since a power function with an exponent of 1.0 is really a linear function. So a linear transfer function is just a special case of a power function, with a gamma of 1.0 ๐
Thus, as long as our imaging system contains only stages with linear and power-law transfer functions, we can meaningfully talk about the gamma of the entire system. This is indeed the case with most real imaging systems ๐
What should overall gamma be? ๐ค
If the overall gamma of an imaging system is 1.0, its output is linearly proportional to its input. This means the ratio between the intensities of any two areas in the reproduced image will be the same as it was in the original scene. It might seem like this should always be the goal of an imaging system: to accurately reproduce the tones of the original scene. Alas, that is not the case ๐
When the reproduced image is to be viewed in "bright surround" conditions, where other white objects nearby in the room have about the same brightness as white in the image, then an overall gamma of 1.0 does indeed give real-looking reproduction of a natural scene. Photographic prints viewed under room light and computer displays in bright room light are typical "bright surround" viewing conditions โ๏ธ
However, sometimes images are intended to be viewed in "dark surround" conditions, where the room is substantially black except for the image. This is typical of the way movies and slides (transparencies) are viewed by projection. Under these circumstances, an accurate reproduction of the original scene results in an image that human viewers judge as "flat" and lacking in contrast ๐ฌ It turns out the projected image needs a gamma of about 1.5 relative to the original scene for viewers to judge it "natural." Thus, slide film is designed to have a gamma of about 1.5, not 1.0 ๐๏ธ
There is also an intermediate condition called "dim surround," where the rest of the room is still visible to the viewer, but is noticeably darker than the reproduced image itself. This is typical of television viewing, at least in the evening, as well as subdued-light computer work areas. In dim surround conditions, the reproduced image needs a gamma of about 1.25 relative to the original scene in order to look natural ๐บ
The requirement for boosted contrast (gamma) in dark surround conditions is due to the way the human visual system works, and applies equally well to computer monitors. Thus, a PNG viewer trying to achieve the maximum realism for the images it displays really needs to know what the room lighting conditions are, and adjust the gamma of the displayed image accordingly. ๐ซถ
If asking the user about room lighting conditions is inappropriate or too difficult, just assume that the overall gamma (viewing_gamma as defined below) should be 1.0 or 1.25. That's all that most systems that implement gamma correction do ๐ท
What is a CRT's gamma? ๐บ
All CRT displays have a power-law transfer characteristic with a gamma of about 2.5. This is due to the physical processes involved in controlling the electron beam in the electron gun, and has nothing to do with the phosphor ๐ฌ
An exception to this rule is fancy "calibrated" CRTs that have internal electronics to alter their transfer function. If you have one of these, you probably should believe what the manufacturer tells you its gamma is. But in all other cases, assuming 2.5 is likely to be pretty accurate ๐
There are various images around that purport to measure gamma, usually by comparing the intensity of an area containing alternating white and black with a series of areas of continuous gray of different intensity. These are usually not reliable. Test images that use a "checkerboard" pattern of black and white are the worst, because a single white pixel will be reproduced considerably darker than a large area of white ๐ An image that uses alternating black and white horizontal lines (such as the "gamma.png" test image at ftp://ftp.uu.net/graphics/png/images/suite/gamma.png) is much better, but even it may be inaccurate at high "picture" settings on some CRTs ๐ง
If you have a good photometer, you can measure the actual light output of a CRT as a function of input voltage and fit a power function to the measurements. However, note that this procedure is very sensitive to the CRT's black level adjustment, somewhat sensitive to its picture adjustment, and also affected by ambient light. Furthermore, CRTs spread some light from bright areas of an image into nearby darker areas; a single bright spot against a black background may be seen to have a "halo" โจ Your measuring technique will need to minimize the effects of this.
Because of the difficulty of measuring gamma, using either test images or measuring equipment, you're usually better off just assuming gamma is 2.5 rather than trying to measure it ๐
What is gamma correction? ๐จ
A CRT has a gamma of 2.5, and we can't change that ๐ซ To get an overall gamma of 1.0 (or somewhere near that) for an imaging system, we need at least one other component of the "image pipeline" that is nonlinear. If there is only one nonlinear stage in addition to the CRT, then it's traditional to say that the CRT has a certain gamma, and that the other nonlinear stage provides "gamma correction" to compensate for the CRT. However, exactly where the "correction" is done depends on circumstance.
In all broadcast video systems, gamma correction is done in the camera. This choice was made in the days when television electronics were all analog, and a good gamma-correction circuit was expensive to build. The original NTSC video standard required cameras to have a transfer function with a gamma of 1/2.2, or about 0.45. Recently, a more complex two-part transfer function has been adopted [SMPTE-170M], but its behavior can be well approximated by a power function with a gamma of 0.5. When the resulting image is displayed on a CRT with a gamma of 2.5, the image on screen ends up with a gamma of about 1.25 relative to the original scene, which is appropriate for "dim surround" viewing ๐น
These days, video signals are often digitized and stored in computer frame buffers. This works fine, but remember that gamma correction is "built into" the video signal, and so the digitized video has a gamma of about 0.5 relative to the original scene ๐ง
Computer rendering programs often produce linear samples. To display these correctly, intensity on the CRT needs to be directly proportional to the sample values in the frame buffer. This can be done with a special hardware lookup table between the frame buffer and the CRT hardware. The lookup table (often called LUT) is loaded with a mapping that implements a power function with a gamma of 0.4, thus providing "gamma correction" for the CRT gamma ๐ธ
Thus, gamma correction sometimes happens before the frame buffer, sometimes after. As long as images created in a particular environment are always displayed in that environment, everything is fine. But when people try to exchange images, differences in gamma correction conventions often result in images that seem far too bright and washed out, or far too dark and contrasty ๐ฉ
Gamma-encoded samples are good ๐
So, is it better to do gamma correction before or after the frame buffer?
In an ideal world, sample values would be stored in floating point, there would be lots of precision, and it wouldn't really matter much. But in reality, we're always trying to store images in as few bits as we can ๐ฅบ
If we use samples that are linearly proportional to intensity and do the gamma correction in the frame buffer LUT, we need at least 12 bits each for red, green, and blue to get enough intensity precision ๐ญ. With anything less, we'll sometimes see "contour bands" or "Mach bands" in the darker parts of the image, where two neighbouring sample values are still far enough apart in intensity that the difference shows up. Not a vibe ๐
BUT here's the plot twist ๐ฆ. Through an interesting coincidence, the human eye's perception of brightness relates to the physical intensity of light in a way that looks a lot like the power function used for gamma correction. If we apply gamma correction to measured (or calculated) light intensity before we quantize it to an integer for storage in a frame buffer, we can get away with way fewer bits to store the image. In fact, 8 bits per colour is almost always enough to avoid contouring artifacts. Since gamma correction is so closely tied to human perception, we're assigning our 256 available sample codes to intensity values in a way that roughly matches how visible those changes are to the eye. Compared to a linear-sample image, we give fewer sample values to the brighter parts of the tonal range and more sample values to the darker parts ๐
So, for the same apparent image quality, images using gamma-encoded sample values only need about two-thirds as many bits of storage as images using linear samples. Iconic efficiency ๐
General gamma handling โจ
When more than two nonlinear transfer functions are involved in the image pipeline, the term "gamma correction" gets way too vague ๐ If we look at a pipeline that involves capturing (or calculating) an image, storing it in an image file, reading the file, and displaying it on some kind of display screen, there are at least 5 places where a nonlinear transfer function could show up. So let's give each one its own name for its characteristic gamma:
camera_gamma: the characteristic of the image sensorencoding_gamma: the gamma of any transformation performed by the software writing the image filedecoding_gamma: the gamma of any transformation performed by the software reading the image fileLUT_gamma: the gamma of the frame buffer LUT, if presentCRT_gamma: the gamma of the CRT, generally 2.5
We also get a few extra names ๐
file_gamma: the gamma of the image in the file, relative to the original scene. This is:
file_gamma = camera_gamma * encoding_gamma
display_gamma: the gamma of the "display system" downstream of the frame buffer. This is:
display_gamma = LUT_gamma * CRT_gamma
viewing_gamma: the overall gamma we want to get to make images look pleasing, generally 1.0 to 1.5.
The file_gamma value, as defined above, is what goes in the gAMA chunk of a PNG file. If file_gamma isn't 1.0, we know gamma correction has been applied to the sample values in the file, so we could call them "gamma corrected" samples. However, there can be so many different gamma values in the display chain, and some of them aren't known when the image is written, so the samples aren't really being "corrected" for any specific display condition. We're really using a power function to encode an intensity range into a small integer field, so it's more accurate to say "gamma encoded" samples instead of "gamma corrected" samples ๐
When displaying an image file, the image decoding program is responsible for making the overall gamma of the system equal to the desired viewing_gamma, by choosing the decoding_gamma appropriately. When displaying a PNG file, the gAMA chunk gives us the file_gamma value. The display_gamma might be known for this machine, it might come from the system software, or the user might have to be asked what it is. The correct viewing_gamma depends on lighting conditions, and that will generally have to come from the user ๐ซถ
Ultimately, you should have:
file_gamma * decoding_gamma * display_gamma = viewing_gamma
Some specific examples ๐ธ
In digital video systems, camera_gamma is about 0.5 by declaration of the various video standards documents. CRT_gamma is 2.5 as usual, while encoding_gamma, decoding_gamma, and LUT_gamma are all 1.0. As a result, viewing_gamma ends up being about 1.25 โจ
On frame buffers that have hardware gamma correction tables, and that are calibrated to display linear samples correctly, display_gamma is 1.0.
Many workstations, X terminals, and PC displays lack gamma correction lookup tables. Here, LUT_gamma is always 1.0, so display_gamma is 2.5.
On the Macintosh, there is a LUT. By default, it's loaded with a table whose gamma is about 0.72, which gives a display_gamma (LUT and CRT combined) of about 1.8. Some Macs have a "Gamma" control panel that lets you change the gamma to 1.0, 1.2, 1.4, 1.8, or 2.2. These settings load alternate LUTs designed to give a display_gamma equal to the label on the selected button. So the "Gamma" control panel setting can be used directly as display_gamma in decoder calculations ๐
On recent SGI systems, there's a hardware gamma-correction table whose contents are controlled by the (privileged) "gamma" program. The gamma of the table is actually the reciprocal of the number that "gamma" prints, and it does not include the CRT gamma. To get the display_gamma, you need to find the SGI system gamma (either by looking in a file or asking the user) and then calculate:
display_gamma = 2.5 / SGI_system_gamma
You'll find SGI systems with the system gamma set to 1.0 and 2.2 (or higher), but the default when machines ship is 1.7 ๐
A note about video gamma ๐บ
The original NTSC video standards specified a simple power-law camera transfer function with a gamma of 1/2.2 or 0.45. This isn't possible to implement exactly in analog hardware, because the function has infinite slope at x=0, so all cameras deviated from this ideal to some degree ๐ต More recently, a new camera transfer function that is physically realizable has been accepted as a standard [SMPTE-170M]. It is:
Vout = 4.5 * Vin if Vin < 0.018
Vout = 1.099 * (Vin^0.45) - 0.099 if Vin >= 0.018
Here, Vin and Vout are measured on a scale of 0 to 1. Although the exponent stays at 0.45, the multiplication and subtraction change the shape of the transfer function, so it's no longer a pure power function. If you want to do extremely precise calculations on video signals, you should use the expression above (or its inverse, as required).
However, PNG doesn't provide a way to say that an image uses this exact transfer function. The gAMA chunk always assumes a pure power-law function. If we plot the two-part transfer function above along with the family of pure power functions, we find that a power function with a gamma of about 0.5 to 0.52 (not 0.45) most closely approximates it. So when writing a PNG file with data digitized from the output of a modern video camera, the gAMA chunk should contain 0.5 or 0.52, not 0.45. The remaining difference between the true transfer function and the power function is insignificant for almost all purposes. (In fact, the alignment errors in most cameras are likely to be bigger than the difference between these functions.) The designers of PNG decided that the simplicity and flexibility of a power-law definition for gAMA mattered more than being able to describe the SMPTE-170M transfer curve exactly ๐
The PAL and SECAM video standards specify a power-law camera transfer function with a gamma of 1/2.8 or 0.36, not the 1/2.2 of NTSC. However, this is too low in practice, so real cameras are likely to have their gamma set close to NTSC practice. Just guessing 0.45 or 0.5 will probably give you viewable results, but if you want precise values, you'll likely have to measure the particular camera ๐ง
Further reading ๐
If you have access to the World Wide Web, read Charles Poynton's excellent "Gamma FAQ" [GAMMA-FAQ] for more information about gamma. Trust, it's worth it ๐
14. Appendix: Color Tutorial ๐จโจ
(This appendix is not part of the formal PNG specification, so think of it as a bestie-level explainer ๐ )
About chromaticity
Okay so the cHRM chunk teams up with the gAMA chunk to share precise color info ๐. That way a PNG can look way more accurate when it's displayed or printed, which is literally impossible without this info. The earlier chapters explain how that info gets encoded in a PNG. This tutorial is the color theory behind it, for anyone who's never looked at it before ๐ค
Heads up ๐จ: showing an image with the wrong gamma causes way bigger color errors than skipping chromaticity data does. So first make sure your monitor setup and gamma correction are right, and then stress about chromaticity ๐
The problem
The color of an object depends on more than just the exact spectrum of light coming off it. It also depends on the observer ๐: their species, what else they can see at the same time, even what they looked at a second ago! And two totally different spectra can give you the exact same color sensation ๐ฆ. Color isn't an objective thing out in the real world. It's a subjective, biological feeling โจ. Still, if we make some simplifying assumptions (like, we're talking about human vision), we can build a math model of color and get really good accuracy ๐
Device-dependent color
Put the same RGB data on three different monitors side by side and you'll see a super noticeable change in color balance on each one ๐ญ. That's because every monitor puts out a slightly different shade and intensity of red, green, and blue light. RGB is a device-dependent color model, which means the color you actually get depends on the device ๐. It also means one color, like RGB 87, 146, 116 on one monitor, might need to be written as RGB 98, 123, 104 on another monitor to look the same ๐ธ
Device-independent color
A full physical description of a color would need the exact spectral power distribution of the light source. Lowkey a lot ๐ตโ๐ซ. Luckily, the human eye and brain don't need an exact copy of a spectrum to be happy. There are device-independent color models that describe pretty well how humans will see a color ๐ง. The most important one, the one every other model can be related back to, was developed by the International Lighting Committee (CIE, in French) and it's called XYZ โจ
In XYZ, X is a weighted sum of the power distribution across the whole visible spectrum. Y and Z are too, just with different weights ๐. So any spectral power distribution gets squished down to just three floating point numbers. The weights came from color matching experiments on human subjects in the 1920s ๐ฐ๏ธ. CIE XYZ has been an International Standard since 1931, and it's iconic for a few reasons ๐:
- two colors with the same XYZ values will look the same to humans
- two colors with different XYZ values will not look the same
- the Y value represents all the brightness information (luminance)
- the XYZ color of any object can be objectively measured
Color models based on XYZ have been used for years by people who need really accurate color control, like lighting engineers for film and TV, and paint and dye manufacturers, and so on ๐ฌ. So they're proven in industry. Accurate, device-independent color started spreading from high-end specialized fields into the mainstream in the late 1980s and early 1990s, and PNG noticed that trend ๐๐ซถ
Calibrated, device-dependent color
Image file formats have traditionally used uncalibrated, device-dependent color ๐. If you know the exact details of the original display device, you can convert a particular image's device-dependent colors into device-independent ones ๐ฅ. If we assume we're working with CRTs (which are way easier than printers), all we need to know is the XYZ values of each primary color, and the CRT_gamma ๐
So why doesn't PNG just store images in XYZ instead of RGB? Two big reasons ๐ค. First, XYZ would need more bits of precision, which means bigger files ๐ฆ. Second, every program would have to convert the image data before viewing it ๐ฉ. Calibrated or not, all the RGB variants are close enough that casual viewers can just display the data without any color correction โจ. By storing calibrated RGB, PNG stays compatible with the programs that already expect RGB, and it still includes enough info to convert to XYZ for apps that need precise colors ๐. So we get the best of both worlds ๐
What are chromaticity and luminance?
Chromaticity is an objective measurement of an object's color, without the brightness info ๐ธ. It uses two parameters, x and y, which you can easily calculate from XYZ:
x = X / (X + Y + Z)
y = Y / (X + Y + Z)
XYZ colors with the same chromaticity values will look like the same hue, but their absolute brightness can be different ๐จ. Notice that x and y are dimensionless ratios, so they come out the same no matter what units you used for X, Y, and Z ๐ง
The Y value of an XYZ color is directly proportional to its absolute brightness, and it's called the luminance of the color ๐ก. You can describe a color with XYZ coordinates, or with chromaticity x,y plus luminance Y. The XYZ form has a huge perk, though: it's linearly related to (linear, gamma=1.0) RGB color spaces ๐ฆ
How are computer monitor colors described?
The "white point" of a monitor is the chromaticity x,y of its nominal white, which is the color you get when R=G=B=maximum โฌโจ
It's customary to describe monitor colors by giving the chromaticities of the individual phosphors R, G, and B, plus the white point ๐บ. The white point lets you infer the relative brightnesses of the three phosphors, which the chromaticities alone can't tell you ๐ง
The absolute brightness of the monitor isn't specified, and honestly for computer graphics we mostly don't care about absolute brightness levels ๐ . Instead of absolute XYZ values (which use physical units of radiated power, like candelas per square meter), it's way more convenient to work in "relative XYZ" units, where the monitor's nominal white has a luminance (Y) of 1.0 ๐. With that assumption, it's simple to compute the XYZ coordinates of the monitor's white, red, green, and blue from their chromaticity values ๐ซถ
Why does cHRM use x,y instead of XYZ? Simply because that's how manufacturers print the info on their spec sheets ๐๐. Usually the first thing a program does is convert the cHRM chromaticities into relative XYZ space ๐
What can I do with it?
If a PNG file has both the gAMA and cHRM chunks, you can convert the source_RGB values to XYZ, and that unlocks a lot ๐:
- do accurate grayscale conversion (just use the Y component)
- convert to RGB for your own monitor (to see the original colors)
- print the image in Level 2 PostScript with better color fidelity than a simple RGB to CMYK conversion could give you
- calculate an optimal color palette
- pass the image data to a color management system
- etc. โจ
How do I convert from source_RGB to XYZ?
First, make a few simplifying assumptions, like the monitor is really jet black with no input, and the guns don't interfere with each other ๐ค. Then, if you know the CIE XYZ values for each of red, green, and blue on a particular monitor, you put them into a matrix m ๐งฎ:
Xr Xg Xb
m = Yr Yg Yb
Zr Zg Zb
Here we're assuming we're working with linear RGB floating point data in the range 0..1 ๐. If the gamma isn't 1.0, make it 1.0 on the floating point data first ๐. Then convert source_RGB to XYZ with matrix multiplication ๐ซ:
X R
Y = m G
Z B
In other words, X = Xr*R + Xg*G + Xb*B, and Y and Z work the same way ๐ธ. You can go the other way too ๐:
R X
G = im Y
B Z
where im is the inverse of the matrix m ๐ฆ
What is a gamut?
The gamut of a device is the subset of visible colors that device can display. (And no, it has nothing to do with gamma ๐ โโ๏ธ.) The gamut of an RGB device can be visualized as a polyhedron in XYZ space ๐บ. Its vertices line up with the device's black, blue, red, green, magenta, cyan, yellow, and white ๐
Different devices have different gamuts, meaning one device can show certain colors (usually super saturated ones) that another device just can't ๐ค. You can figure out the gamut of a particular RGB device from its R, G, and B chromaticities and its white point (the same values given in the cHRM chunk) ๐งพ. The gamut of a color printer is more complicated, and it can only be determined by measurement ๐. Printer gamuts are typically smaller than monitor gamuts, though, so there can be lots of colors in a displayable image that just can't be printed physically ๐ฅฒ
When you convert image data from one device to another, you usually run into gamut mismatches, which are colors that can't be represented exactly on the destination device ๐. The process of making the colors fit, which can be as simple as a clip or as fancy as elaborate nonlinear scaling, is called gamut mapping ๐. The goal is a reasonable visual representation of the original image ๐ โจ
Further reading
References [COLOR-1] through [COLOR-5] dig way deeper into color theory ๐๐
15. Appendix: Sample CRC Code โจ
okay so this one is the GOAT of the whole doc ๐ง it's a practical implementation of the CRC (Cyclic Redundancy Check) that PNG chunks use. if you want the formal specs, check out ISO 3309 [ISO-3309] or ITU-T V.42 [ITU-V42] ๐
the code is in ANSI C, so if you're not a C girlie, here's a little cheat sheet ๐
&is the bitwise AND operator.^is the bitwise exclusive-OR operator. (caution bestie: elsewhere in this doc,^means exponentiation, so don't mix them up ๐)>>is the bitwise right shift operator. when it's applied to an unsigned quantity (like here), the right shift fills the left with zeroes ๐ซถ!is the logical NOT operator.++means "n++" increments the variable n. literally just +1 ๐0xstarts a hexadecimal (base 16) constant. the suffixLmeans it's a long value (at least 32 bits) ๐ฅ
/* Table of CRCs of all 8-bit messages. */
unsigned long crc_table[256];
/* Flag: has the table been computed? Initially false. */
int crc_table_computed = 0;
/* Make the table for a fast CRC. */
void make_crc_table(void)
{
unsigned long c;
int n, k;
for (n = 0; n < 256; n++) {
c = (unsigned long) n;
for (k = 0; k < 8; k++) {
if (c & 1)
c = 0xedb88320L ^ (c >> 1);
else
c = c >> 1;
}
crc_table[n] = c;
}
crc_table_computed = 1;
}
/* Update a running CRC with the bytes buf[0..len-1]--the CRC
should be initialized to all 1's, and the transmitted value
is the 1's complement of the final running CRC (see the
crc() routine below)). */
unsigned long update_crc(unsigned long crc, unsigned char *buf,
int len)
{
unsigned long c = crc;
int n;
if (!crc_table_computed)
make_crc_table();
for (n = 0; n < len; n++) {
c = crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8);
}
return c;
}
/* Return the CRC of the bytes buf[0..len-1]. */
unsigned long crc(unsigned char *buf, int len)
{
return update_crc(0xffffffffL, buf, len) ^ 0xffffffffL;
}
16. Appendix: Online Resources โจ
(this appendix is not part of the formal PNG spec, just fyi ๐ฆ)
this appendix gives you the locations of some internet resources for PNG software devs. the internet is always changing, so the list is incomplete and subject to change ๐
Archive sites
the latest released versions of this doc and related info can always be found at the PNG FTP archive site, ftp://ftp.uu.net/graphics/png/. the PNG spec is available in several formats, including HTML, plain text, and PostScript ๐๐
Reference implementation and test images
a reference implementation in portable C is available from the PNG FTP archive site, ftp://ftp.uu.net/graphics/png/src/. the reference implementation is freely usable in all applications, including commercial applications ๐
test images are available from ftp://ftp.uu.net/graphics/png/images/. ๐ธ
Electronic mail
the maintainers of the PNG spec can be contacted by e-mail at png-info@uunet.uu.net or at png-group@w3.org ๐
PNG home page
there's a World Wide Web home page for PNG at http://quest.jpl.nasa.gov/PNG/. this page is a central location for current info about PNG and PNG-related tools ๐ธ
17. Appendix: Revision History โจ
(still not part of the formal PNG spec, babe ๐)
the PNG format has been frozen since the Ninth Draft of March 7, 1995, and all future changes are intended to be backwards compatible. the revisions since the Ninth Draft are just clarifications, improvements in presentation, and additions of supporting material. honestly so iconic ๐
on 1 October 1996, the PNG spec was approved as a W3C (World Wide Web Consortium) Recommendation. W3C ATE that ๐
Changes since the Tenth Draft of 5 May, 1995
- Clarified meaning of a suggested-palette PLTE chunk in a truecolor image that uses transparency
- Clarified exact semantics of sBIT and allowed sample depth scaling procedures
- Clarified status of spaces in tEXt chunk keywords
- Distinguished private and public extension values in type and method fields
- Added a "Creation Time" tEXt keyword
- Macintosh representation of PNG specified
- Added discussion of security issues
- Added more extensive discussion of gamma and chromaticity handling, including tutorial appendixes
- Clarified terminology, notably sample depth vs. bit depth
- Added a glossary
- Editing and reformatting
18. References ๐
[COLOR-1]
Hall, Roy, Illumination and Color in Computer Generated Imagery. Springer-Verlag, New York, 1989. ISBN 0-387-96774-5.
[COLOR-2]
Kasson, J., and W. Plouffe, "An Analysis of Selected Computer Interchange Color Spaces", ACM Transactions on Graphics, vol 11 no 4 (1992), pp 373-405.
[COLOR-3]
Lilley, C., F. Lin, W.T. Hewitt, and T.L.J. Howard, Colour in Computer Graphics. CVCP, Sheffield, 1993. ISBN 1-85889-022-5. Also available from <URL:http://info.mcc.ac.uk/CGU/ITTI/Col/colour_announce.html>
[COLOR-4]
Stone, M.C., W.B. Cowan, and J.C. Beatty, "Color gamut mapping and the printing of digital images", ACM Transactions on Graphics, vol 7 no 3 (1988), pp 249-292.
[COLOR-5]
Travis, David, Effective Color Displays --- Theory and Practice. Academic Press, London, 1991. ISBN 0-12-697690-2.
[GAMMA-FAQ]
Poynton, C., "Gamma FAQ". <URL:http://www.inforamp.net/%7Epoynton/Poynton-colour.html>
[ISO-3309]
International Organization for Standardization, "Information Processing Systems --- Data Communication High-Level Data Link Control Procedure --- Frame Structure", IS 3309, October 1984, 3rd Edition.
[ISO-8859]
International Organization for Standardization, "Information Processing --- 8-bit Single-Byte Coded Graphic Character Sets --- Part 1: Latin Alphabet No. 1", IS 8859-1, 1987. Also see sample files at ftp://ftp.uu.net/graphics/png/documents/iso_8859-1.*
[ITU-BT709]
International Telecommunications Union, "Basic Parameter Values for the HDTV Standard for the Studio and for International Programme Exchange", ITU-R Recommendation BT.709 (formerly CCIR Rec. 709), 1990.
[ITU-V42]
International Telecommunications Union, "Error-correcting Procedures for DCEs Using Asynchronous-to-Synchronous Conversion", ITU-T Recommendation V.42, 1994, Rev. 1.
[PAETH]
Paeth, A.W., "Image File Compression Made Easy", in Graphics Gems II, James Arvo, editor. Academic Press, San Diego, 1991. ISBN 0-12-064480-0.
[POSTSCRIPT]
Adobe Systems Incorporated, PostScript Language Reference Manual, 2nd edition. Addison-Wesley, Reading, 1990. ISBN 0-201-18127-4.
[PNG-EXTENSIONS]
PNG Group, "PNG Special-Purpose Public Chunks". Available in several formats from ftp://ftp.uu.net/graphics/png/documents/pngextensions.*
[RFC-1123]
Braden, R., Editor, "Requirements for Internet Hosts --- Application and Support", STD 3, RFC 1123, USC/Information Sciences Institute, October 1989. <URL:ftp://ds.internic.net/rfc/rfc1123.txt>
[RFC-2045]
Freed, N., and N. Borenstein, "Multipurpose Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies", RFC 2045, Innosoft, First Virtual, November 1996. <URL:ftp://ds.internic.net/rfc/rfc2045.txt>
[RFC-2048]
Freed, N., Klensin, J., and J. Postel, "Multipurpose Internet Mail Extensions (MIME) Part Four: Registration Procedures", RFC 2048, Innosoft, MCI, USC/Information Sciences Institute, November 1996. <URL:ftp://ds.internic.net/rfc/rfc2048.txt>
[RFC-1950]
Deutsch, P. and J-L. Gailly, "ZLIB Compressed Data Format Specification version 3.3", RFC 1950, Aladdin Enterprises, May 1996. <URL:ftp://ds.internic.net/rfc/rfc1950.txt>
[RFC-1951]
Deutsch, P., "DEFLATE Compressed Data Format Specification version 1.3", RFC 1951, Aladdin Enterprises, May 1996. <URL:ftp://ds.internic.net/rfc/rfc1951.txt>
[SMPTE-170M]
Society of Motion Picture and Television Engineers, "Television --- Composite Analog Video Signal --- NTSC for Studio Applications", SMPTE-170M, 1994.
19. Credits ๐
Editor
Thomas Boutell, boutell@boutell.com
Contributing Editor
Tom Lane, tgl@sss.pgh.pa.us
Authors
Authors' names are presented in alphabetical order. ๐ซ
- Mark Adler, madler@alumni.caltech.edu
- Thomas Boutell, boutell@boutell.com
- Christian Brunschen, cb@df.lth.se
- Adam M. Costello, amc@cs.berkeley.edu
- Lee Daniel Crocker, lee@piclab.com
- Andreas Dilger, adilger@enel.ucalgary.ca
- Oliver Fromme, fromme@rz.tu-clausthal.de
- Jean-loup Gailly, gzip@prep.ai.mit.edu
- Chris Herborth, chrish@qnx.com
- Alex Jakulin, Aleks.Jakulin@snet.fri.uni-lj.si
- Neal Kettler, kettler@cs.colostate.edu
- Tom Lane, tgl@sss.pgh.pa.us
- Alexander Lehmann, alex@hal.rhein-main.de
- Chris Lilley, chris@w3.org
- Dave Martindale, davem@cs.ubc.ca
- Owen Mortensen, 104707.650@compuserve.com
- Keith S. Pickens, ksp@swri.edu
- Robert P. Poole, lionboy@primenet.com
- Glenn Randers-Pehrson, glennrp@arl.mil or randeg@alumni.rpi.edu
- Greg Roelofs, newt@pobox.com
- Willem van Schaik, willem@gintic.gov.sg
- Guy Schalnat
- Paul Schmidt, pschmidt@photodex.com
- Tim Wegner, 71320.675@compuserve.com
- Jeremy Wohl, jeremyw@anders.com
the authors wanted to thank the Portable Network Graphics mailing list, the readers of comp.graphics, and the members of the World Wide Web Consortium (W3C) ๐ซถ
the Adam7 interlacing scheme is not patented, and the originator never meant to patent it. every PNG implementation can freely use it, and the name "Adam7" can be freely used to describe interlace method 1 of the PNG spec ๐
Trademarks
GIF is a service mark of CompuServe Incorporated. IBM PC is a trademark of International Business Machines Corporation. Macintosh is a trademark of Apple Computer, Inc. Microsoft and MS-DOS are trademarks of Microsoft Corporation. PhotoCD is a trademark of Eastman Kodak Company. PostScript and TIFF are trademarks of Adobe Systems Incorporated. SGI is a trademark of Silicon Graphics, Inc. X Window System is a trademark of the Massachusetts Institute of Technology. ๐
Copyright Notice โจ
okay bestie, the legal bit ๐ this one is from Copyright (c) 1996 by: Massachusetts Institute of Technology (MIT) ๐
The copyright holders are giving this W3C specification to us under the following license. By getting it, using it, and/or copying it, you agree that you've read and understood the terms and that you'll follow them ๐ซถ
Here's the deal โจ You're allowed to use, copy, and distribute this specification for any purpose, and it's totally free with no fee or royalty. The one catch is that the full text of this NOTICE has to show up on ALL copies of the specification, or portions of it, including any modifications you make ๐ธ
Now the disclaimer, and it's giving serious energy ๐ The specification is provided "AS IS," and the copyright holders don't make any representations or warranties, express or implied. For example, but not limited to, they don't promise it's merchantable or fit for any particular purpose, and they don't promise that using it won't infringe any third party patents, copyrights, trademarks, or other rights. The copyright holders won't be liable for any use of this specification ๐ โโ๏ธ
Last thing, and it matters ๐ฅ The name and trademarks of the copyright holders may NOT be used in advertising or publicity about the specification without specific, written prior permission. Title to the copyright in this specification and any associated documentation always stays with the copyright holders ๐
Security Considerations ๐
okay, security, but make it a pointer ๐ Security issues are discussed in Security considerations (Section 8.5) ๐ซถ so go check that section, it's the real tea โ
Author's Address ๐ฆ
this is where you find the author, Thomas Boutell ๐
Thomas Boutell
PO Box 20837
Seattle, WA 98102
Phone: (206) 329-4969
EMail: boutell@boutell.com
And that's the whole RFC, bestie ๐โจ Thanks for sticking with me through all 22 parts ๐
๐ฆ where this page comes from
- ietf: rfc2083 1997-03
- ๐ girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms