📜 ⬆️ ⬇️

One pixel instead of a thousand words



A couple of months ago, resting on the implementation of new features like q_auto and g_auto, I was joking in our team chat about how different image storage formats will compress a single-pixel image. In response, Orly, the blog editor, asked me to write a post about it. I said, “Of course, why not. But it will be a very short post. After all, what can you say about one pixel. "

Looks like I was badly wrong.
')

What can be done with a single pixel?


In the early years of the web, single-pixel images were often used as crutches for things that are now done through CSS. Creating indents, lines, rectangles, translucent backgrounds - a lot of things can be done by simply scaling a pixel to the desired size. Another use of pixels that has survived to our days is beacons, tracking tools and analytics.

In responsive web design, single-pixel images are used as temporary stubs while waiting for the page to load. Most browsers do not support the HTTP Client Hints , so some options with responsive images wait for the page to load to calculate the actual size of the images, and then replace single-pixel images with the desired images using JavaScript.


Broken picture

There is one more application of single-pixel pictures: they can be used as “default” pictures. If for some reason it is impossible to find the desired image, in some cases it is better to show one transparent pixel than to give out “404 - Not Found”, which will be visible in browsers as a “broken picture”. In any case, you will not see the desired image, but it will be more professional not to focus attention on this, producing the icon of a “broken picture”.

Well, then, single-pixel images are useful. And what is the best way to encode a 1x1 image?

Obviously, for image compression formats this is a borderline case. If the image consists of one pixel, there is nothing to compress here. Uncompressed data here will contain from one bit to four bytes - depending on the interpretation: black and white (1 bit), shades of gray (1 byte), shades of gray with alpha (2 bytes), RGB (3 bytes), RGBA (4 bytes).

But you can not encode only the data - in any image format you need to set the interpretation of the data. At the very least, you need to know the height and width of the image and the number of bits per pixel.

Headlines


Typically, four bytes are used to encode the height and width: two by a number (if it were one byte, the maximum image dimension would be 255x255). Suppose you need another byte to specify the type of color (grayscale, RGB or RGBA). In such a minimalist format, a single-pixel picture would occupy at least 6 bytes (for a white pixel), and a maximum of 9 bytes (for a semi-transparent pixel of an arbitrary color).

But the headers of real formats usually contain much more information. The first few bytes of any format contain a unique identifier only needed to report that “Hey! I am the file of this particular format! ” This byte sequence is also known as the “magic number.” For example, GIF always starts with GIF87a or GIF89a, depending on the specification version, PNG starts with an 8-byte sequence that includes PNG, JPEG has a header that contains the string JFIF or Exif, etc.

Headers may contain meta-information. This is the format-specific data required for decoding, which determines which format subspecies are used. Some of the meta-data are not necessarily needed for decoding, but are nevertheless used to determine how to show it on the screen: color profile, orientation, gamma, number of points per pixel. It can also be derived data - comments, time stamps, copyright notes, GPS coordinates. This may be optional or required data, depending on the specification. Of course, this data increases the file size. Let us therefore dwell on the minimum files from which all optional information has been removed - or we will spend precious bytes on nonsense.

In addition to headers, other additional information may also be found in the files — tokens, checksums (used to verify the correctness of the transfer or the result of other processes that may damage the file). It happens that you need to include indentation in the file to align all the data.

Single-pixel, minimally possible pictures show how much “extra” information is contained in the file format. We look.

Here is a hex dump of a 67-byte PNG file with one white pixel.

00000000 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 |.PNG........IHDR| 00000010 00 00 00 01 00 00 00 01 01 00 00 00 00 37 6e f9 |.............7n.| 00000020 24 00 00 00 0a 49 44 41 54 78 01 63 68 00 00 00 |$....IDATx.ch...| 00000030 82 00 81 4c 17 d7 df 00 00 00 00 49 45 4e 44 ae |...L.......IEND.| 00000040 42 60 82 |B`.| 


The file consists of an 8-byte “magic number” PNG, followed by a 13-byte IHDR header segment, an IDAT image data with 10 bytes of “compressed” data, and an IEND ending mark. Each data segment begins with a 4-byte segment with a length and a 4-byte segment identifier, and ends with a checksum of 4 bytes. These three pieces of data are required, so that in any case they eat 36 bytes from a 67-byte file.

The black pixel also occupies 67 bytes, the transparent one - 68, and an arbitrary RGBA color will take from 67 to 70 bytes.

JPEG header is longer. The minimum single-pixel JPEG is 141 bytes, and it is not transparent, because JPEG does not support alpha channel.

In terms of headers, GIF is the most compact of the three universal formats. White pixel can be encoded in GIF with 35 bytes:

 00000000 47 49 46 38 37 61 01 00 01 00 80 01 00 00 00 00 |GIF87a..........| 00000010 ff ff ff 2c 00 00 00 00 01 00 01 00 00 02 02 4c |...,...........L| 00000020 01 00 3b |..;| 


and transparent - 43:

 00000000 47 49 46 38 39 61 01 00 01 00 80 01 00 00 00 00 |GIF89a..........| 00000010 ff ff ff 21 f9 04 01 0a 00 01 00 2c 00 00 00 00 |...!.......,....| 00000020 01 00 01 00 00 02 02 4c 01 00 3b |.......L..;| 


For all the above formats, you can make smaller files that will be shown in most browsers, but they will be made in violation of the specifications, so the image decoder can complain at any time that the file is broken (and will be right) and show the “ a broken picture ”- and we are trying to avoid it.

So what is the best single pixel image format for the web? There are options. If the pixel is opaque, then GIF. If transparent - also GIF. If translucent, then PNG, since GIF transparency is set only as "yes" or "no."

All this means little. Any of these files will fit into one network packet, so there will be no difference in speed, and the difference for the storage is generally negligible. But nevertheless, it is fun to deal with this - at least, format lovers.

What about more exotic formats?


Using the format of WebP, choose its version without loss of quality. One-pixel image without loss of quality in the format of WebP takes from 34 to 38 bytes. With a loss - from 44 to 104 bytes, depending on the availability of the alpha channel. For example, here is a fully transparent pixel in a 34-byte WebP without loss of quality:

 00000000 52 49 46 46 1a 00 00 00 57 45 42 50 56 50 38 4c |RIFF....WEBPVP8L| 00000010 0d 00 00 00 2f 00 00 00 10 07 10 11 11 88 88 fe |..../...........| 00000020 07 00 |..| 


but the same pixel with loss of quality (by default) WebP, which occupies 82 bytes:

 00000000 52 49 46 46 4a 00 00 00 57 45 42 50 56 50 38 58 |RIFFJ...WEBPVP8X| 00000010 0a 00 00 00 10 00 00 00 00 00 00 00 00 00 41 4c |..............AL| 00000020 50 48 0b 00 00 00 01 07 10 11 11 88 88 fe 07 00 |PH..............| 00000030 00 00 56 50 38 20 18 00 00 00 30 01 00 9d 01 2a |..VP8 ....0....*| 00000040 01 00 01 00 02 00 34 25 a4 00 03 70 00 fe fb fd |......4%...p....| 00000050 50 00 |P.| 


The difference is that WebP with loss of quality and transparency is stored as two pictures in one container file: one lossy picture that stores data for RGB, and the other, without loss, with alpha channel data.

BPG


The BPG format also has lossy modes with no loss of quality, and the reverse is true for it. Loss BPG stores 1 pixel in 31 bytes - the smallest figure of all:

 00000000 42 50 47 fb 00 00 01 01 00 03 92 47 40 44 01 c1 |BPG........G@D..| 00000010 71 81 12 00 00 01 26 01 af c0 b6 20 bc b6 fc |q.....&.... ...| 


BPG lossless quality takes 59 bytes. Transparent pixel will take 57 bytes in BPG
lossy and 113 bytes in lossless BPG. Interestingly, in the case of one white pixel, BPG wins against WebP (31 bytes against 38), and with one transparent pixel, WebP wins against BPG (34 bytes against 57).

FLIF



And there is FLIF. Of course, I can not forget about it, being the main author of the free image format without loss of quality (Free Lossless Image Format). Here is a 15-byte FLIF for one white pixel:

 00000000 46 4c 49 46 31 31 00 01 00 01 18 44 c6 19 c3 |FLIF11.....D...| 


But 14-byte for black:

 00000000 46 4c 49 46 31 31 00 01 00 01 1e 18 b7 ff |FLIF11........| 


The black pixel turned out to be less, because zero is compressed better than 255. The header is simple: the first 4 bytes are always “FLIF”, the next is a human-readable color and interlacing designation. In our case it is “1”, which means one channel for color (shades of gray). The next byte is the color depth. “1” means one byte per channel. The next four bytes are the dimension of the image, 0x0001 to 0x0001. The following 4 or 5 are compressed data.

A fully transparent pixel also occupies 14 bytes in FLIF:

 00000000 46 4c 49 46 34 31 00 01 00 01 4f fd 72 80 |FLIF41....Or| 


In this case, we have 4 color channels (RGBA) instead of one. One would expect the data section to be longer (after all, the channels are four times larger), but this is not the case: since the alpha value is zero (the pixel is transparent), the RGB values ​​are considered unimportant and simply are not included in the file.

For an arbitrary RGBA color, the FLIF file can take up to 20 bytes.

Well, it means FLIF is the leader in the “one pixel” category in the image encoding competition. If it were some other important competition :)

But nevertheless, FLIF will not be a leader. Remember the minimalistic format I mentioned? One that encodes one pixel in size from 6 to 9 bytes? There is no such format, so it does not count. But there is an existing format that comes pretty close to that.

It is called Portable Bitmap format (PBM), and is an uncompressed image format from the 1980s. Here is how one white pixel in PBM could be encoded with just 8 bytes:

 00000000 50 31 0a 31 20 31 0a 30 |P1.1 1.0| 


Yes, there is no need for a hexadecimal dump, this format is human-readable. It can be opened in a text editor.

 P1 1 1 0 


The first line (P1) indicates that the picture is two-color. Not shades of gray, but only two colors — black (number 1) and white (0). The second line is the dimension of the picture. And then there is a list of numbers separated by spaces, one number per pixel. In our case, 0.

If you need something other than black and white, you can use the PGM format to represent a single pixel of any color with just 12 bytes, or a 14-byte PPM. It is always smaller than the corresponding FLIF (or any other format with compression).

Transparency is not supported in the traditional PNM format family (PBM, PGM and PPM). There is a PNM add-on called Portable Arbitrary Map (PAM) where there is transparency. But for us it is not suitable because of verbosity. The smallest of PAM files, representing a transparent pixel, is:

 P7 WIDTH 1 HEIGHT 1 DEPTH 4 MAXVAL 1 TUPLTYPE RGB_ALPHA ENDHDR \0\0\0\0 


The last line contains four null bytes. Total 67 bytes are obtained. One could use grayscale with alpha channel instead of RGBA, it would save two bytes in the data section. But it will turn out a file of 71 bytes, since you need to change the TUPLTYPE from RGB_ALPHA to GRAYSCALE_ALPHA. In addition, the processing program may not like MAXVAL 1, and will have to change it to MAXVAL 255 (two more bytes).

In general, for single-pixel images without transparency, the smallest is PNM (from 8 to 14 bytes for PNM against 14 to 18 for FLIF), and with transparency the smallest will be FLIF (from 14 to 20 bytes for FLIF against 67 to 69 bytes for PAM).

Here is a comparative label with optimal file sizes for different single-pixel images:



It may seem strange that a format without compression wins over formats with compression. But if you think about single-pixel images - this is the worst option for compressing images. The entire file consists of a header and additional information, and there is very little data in it. But very little data cannot be compressed, because compression is based on predictability, and how can a single pixel be predicted?

Source: https://habr.com/ru/post/306210/


All Articles