Happy New Year: GIF decoder in SQL
Comments enabled. I *really* need your comment
As readers of my blog know, SQL is a wonderful tool for graphics processing. You can use it to draw Mandelbrot sets, table game boards and even snowflakes, all in a single query.
As I was preparing this year's entry, I found myself all out of ideas. What image shall we be generating this year? A cat? A pig? A winter scenery? It's all doable in SQL (or course), however I couldn't make myself pick anything in particular. And frankly speaking, even with all the power of SQL at my hands, I'm a lousy artist.
Then an idea struck me. Why try and create art when there's so many excellent artists out there on the Internet, whose work I could just steal put to fair use? And my phone camera makes better pictures than I could ever aspire to create myself.
Images that come out of the camera or from the Internet are all digital and compressed. Digital is of course a good thing when it comes to computer processing, but compressed is a challenge. And challenges is something I like.
So this year, we will be creating a GIF decoder in SQL.
GIF is one of the earliest compressed image storage formats, famous for its early adoption by the World Wide Web and for being named with an acronym no one can agree how to pronounce correctly. At its core lies LZW, a lossless compression algorithm which uses dictionary tables to encode repeating patterns of data. GIF is not the best format out there, of course, and it has fallen out of use in the last years (or even decades). Its algorithm, however, is not particularly memory or CPU intensive and as the image compression algorithms go, its implementation is quite simple. In other words, it makes a perfect SQL exercise.
To become familiar with GIF, I used an excellent GIF tutorial called What's in a GIF? by Eric. S Raymond and Mike Flickinger. This tutorial is built around explanation of contents of a tiny sample GIF file:
and this file is what we will be using during the first part of our journey.
Let's get started!
Feeding a binary file to the database
As usual, we will be using the latest version of PostgreSQL for this, because of its excellent support of recursive queries, rowset generators, lateral joins and complex types. The first is an absolute must for anything beyond the most simple tasks, the rest make even the complex tasks easier and more readable.
Unfortunately, we cannot easily use binary files with psql directly. We will have to recourse to a little bit of shell magic: we will hex-encode the file and pipe it to the \copy
command, which will insert its contents into a temporary table.
We will need two scripts for that:
pre.sql
CREATE TEMPORARY TABLE input ( data TEXT );
01.bin.sql
WITH RECURSIVE bin AS ( SELECT DECODE(data, 'HEX') AS buffer FROM input ) SELECT ENCODE(buffer, 'HEX') FROM bin;
, which we will be calling in a single shell command:
cat sample_1.gif | od -A n -vt x1 | tr -d '\n ' | psql -f pre.sql -c "COPY input FROM stdin" -f 01.bin.sql
encode |
---|
4749463839610a000a00910000ffffffff00000000ff00000021f90400000000002c000000000a000a000002168c2d99872a1cdc33a00275ec95faa8de608c04914c01003b |
Here we see the hex-encoded content of the sample GIF file in all its entirety. Note that we have put it into a BYTEA field in a temporary table. This will allow working with the parts of the file more easily than if it would be a hex-encoded string.
Let's try and parse the header of the file. To do this, we will be using BIT
operators and BYTEA
functions:
02.header.sql
WITH RECURSIVE bin AS ( SELECT DECODE(data, 'HEX') AS buffer FROM input ), header AS ( SELECT h.*, io.* FROM bin CROSS JOIN LATERAL ( SELECT GET_BYTE(buffer, 10)::BIT(8) AS flags ) q CROSS JOIN LATERAL ( SELECT CONVERT_FROM(SUBSTR(buffer, 1, 6), 'LATIN1') AS version, (GET_BYTE(buffer, 7)::BIT(8) || GET_BYTE(buffer, 6)::BIT(8))::BIT(16)::INT AS width, (GET_BYTE(buffer, 9)::BIT(8) || GET_BYTE(buffer, 8)::BIT(8))::BIT(16)::INT AS height, flags::BIT(1) = B'1' AS gtc, (flags << 1)::BIT(3)::INT + 1 AS depth, (flags << 4) = B'1' AS color_sort, 2 << (flags << 5)::BIT(3)::INT AS gct_size, GET_BYTE(buffer, 11)::INT AS bci, GET_BYTE(buffer, 12)::INT AS aspect ) h CROSS JOIN LATERAL ( SELECT 13 + 3 * gct_size AS blocks_offset ) io ) SELECT * FROM header;
version | width | height | gtc | depth | color_sort | gct_size | bci | aspect | blocks_offset |
---|---|---|---|---|---|---|---|---|---|
GIF89a | 10 | 10 | True | 2 | False | 4 | 0 | 0 | 25 |
This is some basic information about the GIF file.
version
, width
and height
are self-explanatory.
gct
,depth
, color_sort
and gct_size
are related to the image palette. The GIF standard allows encoding any colors from 24-bit RGB color space, but no more than 256 of them at the same time. The set of 24-bit colors used within a single image is called a palette. Palettes are the reason GIF pictures look grainy on the screen: there are just not enough colors available in the palette to encode the smooth transitions. It was enough for most screens back in the day, though, and this is more than enough for the sample file which only has four colors.
To define a palette, we need to know how many different colors are used in the file and what are their values in the color space. This information is stored in a color table. Static GIF files mostly use a Global Color Table for that. There can also be local color tables, but we won't be dealing with them in this post. So, gct
is a flag showing if a Global Color Table is being used (spoiler: it is). depth
is the number of colors in the palette (as a base-2 logarithm, so 2 means 22 = 4 colors). color_sort
is a legacy flag which, if set, means that the colors are sorted from the "most important" to the "least important", so if a decoder or a rendering device is really short on memory, it can choose to only display a selected subset of colors. Finally, gct_size
is the size of the Global Color Table in 3-byte chunks, one per color.
The rest of header fields deals with interlacing, pixel aspect ratio and canvas color. Those are of no importance to us.
The Global Color Table immediately follows the header, and we will get back to it later. Now we have to find where the real fun begins. And blocks_offset
is a calculated field which brings us past the GCT into the most interesting parts of the file.
Blocks
Data in a GIF file is composed of blocks. Those can be images, captions, comments and the designers have even left room for other extensions, although the chances are low we will see new versions of the GIF standard any time soon. We are interested in image blocks, and we have to local the first image block to work with it.
An image block always goes after a so called Graphic Extension block. All extension blocks start with a value 0x21 (called extension introducer), followed by a byte-long extension type and a byte-long block length. Every extension block ends with a value of 0x00. The image block itself starts with a value 0x2C.
To track the beginning of an image block, we need to recursively read through all the blocks, skipping over those which start with 0x21, and stopping when we find a 0x2C.
To do that, we will use a recursive CTE, which would track the offsets, starting from the first one past the Global Color Table, look at the value at the offset, and if this value is 0x21, skip to the next block (block length at offset + 2
, plus 3-byte header plus 1-byte terminator). This stops when the value at the offset is 0x2C (image data).
03.image.sql
WITH RECURSIVE bin AS ( SELECT DECODE(data, 'HEX') AS buffer FROM input ), header AS ( SELECT h.*, io.* FROM bin CROSS JOIN LATERAL ( SELECT GET_BYTE(buffer, 10)::BIT(8) AS flags ) q CROSS JOIN LATERAL ( SELECT CONVERT_FROM(SUBSTR(buffer, 1, 6), 'LATIN1') AS version, (GET_BYTE(buffer, 7)::BIT(8) || GET_BYTE(buffer, 6)::BIT(8))::BIT(16)::INT AS width, (GET_BYTE(buffer, 9)::BIT(8) || GET_BYTE(buffer, 8)::BIT(8))::BIT(16)::INT AS height, flags::BIT(1) = B'1' AS gct, (flags << 1)::BIT(3)::INT + 1 AS depth, (flags << 4) = B'1' AS color_sort, 2 << (flags << 5)::BIT(3)::INT AS gct_size, GET_BYTE(buffer, 11)::INT AS bci, GET_BYTE(buffer, 12)::INT AS aspect ) h CROSS JOIN LATERAL ( SELECT 13 + 3 * gct_size AS blocks_offset ) io ), blocks AS ( SELECT blocks_offset AS current, GET_BYTE(buffer, blocks_offset) AS intro FROM bin CROSS JOIN header UNION ALL SELECT current, GET_BYTE(buffer, current) AS intro FROM ( SELECT current AS previous, intro AS previous_intro FROM blocks ) b CROSS JOIN bin CROSS JOIN LATERAL ( SELECT previous + GET_BYTE(buffer, previous + 2) + 4 AS current ) q WHERE previous_intro = x'21'::INT ), image_offset AS ( SELECT current AS image_offset FROM blocks WHERE intro = x'2C'::INT ) SELECT * FROM image_offset;
image_offset |
---|
33 |
This means the image block starts 33 bytes into the file.
Image header
The image header also has a fixed format easily retrievable using a little bit of BIT
and BYTEA
functionality:
04.image-header.sql
WITH RECURSIVE bin AS ( SELECT DECODE(data, 'HEX') AS buffer FROM input ), header AS ( SELECT h.*, io.* FROM bin CROSS JOIN LATERAL ( SELECT GET_BYTE(buffer, 10)::BIT(8) AS flags ) q CROSS JOIN LATERAL ( SELECT CONVERT_FROM(SUBSTR(buffer, 1, 6), 'LATIN1') AS version, (GET_BYTE(buffer, 7)::BIT(8) || GET_BYTE(buffer, 6)::BIT(8))::BIT(16)::INT AS width, (GET_BYTE(buffer, 9)::BIT(8) || GET_BYTE(buffer, 8)::BIT(8))::BIT(16)::INT AS height, flags::BIT(1) = B'1' AS gct, (flags << 1)::BIT(3)::INT + 1 AS depth, (flags << 4) = B'1' AS color_sort, 2 << (flags << 5)::BIT(3)::INT AS gct_size, GET_BYTE(buffer, 11)::INT AS bci, GET_BYTE(buffer, 12)::INT AS aspect ) h CROSS JOIN LATERAL ( SELECT 13 + 3 * gct_size AS blocks_offset ) io ), blocks AS ( SELECT blocks_offset AS current, GET_BYTE(buffer, blocks_offset) AS intro FROM bin CROSS JOIN header UNION ALL SELECT current, GET_BYTE(buffer, current) AS intro FROM ( SELECT current AS previous, intro AS previous_intro FROM blocks ) b CROSS JOIN bin CROSS JOIN LATERAL ( SELECT previous + GET_BYTE(buffer, previous + 2) + 4 AS current ) q WHERE previous_intro = x'21'::INT ), image_offset AS ( SELECT current AS image_offset FROM blocks WHERE intro = x'2C'::INT ), image_header AS ( SELECT q.*, l.*, l2.* FROM image_offset CROSS JOIN bin CROSS JOIN header h CROSS JOIN LATERAL ( SELECT GET_BYTE(buffer, image_offset + 9)::BIT(8) AS flags ) f CROSS JOIN LATERAL ( SELECT (GET_BYTE(buffer, image_offset + 2)::BIT(8) || GET_BYTE(buffer, image_offset + 1)::BIT(8))::BIT(16)::INT AS left, (GET_BYTE(buffer, image_offset + 4)::BIT(8) || GET_BYTE(buffer, image_offset + 3)::BIT(8))::BIT(16)::INT AS top, (GET_BYTE(buffer, image_offset + 6)::BIT(8) || GET_BYTE(buffer, image_offset + 5)::BIT(8))::BIT(16)::INT AS width, (GET_BYTE(buffer, image_offset + 8)::BIT(8) || GET_BYTE(buffer, image_offset + 7)::BIT(8))::BIT(16)::INT AS height, flags::BIT(1) = b'1' AS has_lct, (flags << 1)::BIT(1) = b'1' AS interlace, (flags << 2)::BIT(1) = b'1' AS sort ) q CROSS JOIN LATERAL ( SELECT CASE WHEN has_lct THEN 2 << (flags << 5)::BIT(3)::INT ELSE 0 END AS lct_size ) l CROSS JOIN LATERAL ( SELECT image_offset + 10 + lct_size AS image_data_offset ) l2 ) SELECT * FROM image_header;
left | top | width | height | has_lct | interlace | sort | lct_size | image_data_offset |
---|---|---|---|---|---|---|---|---|
0 | 0 | 10 | 10 | False | False | False | 0 | 43 |
We have width
and height
here, the same ones as in the file header (not surprising as this is the only image in the file). The rest of the header fields is also of no immediate use to us (please refer to the tutorial if you're interested in them). They, however, let us calculate image_data_offset
, the offset to the actual image data.
Image data
Compressed image data is composed of multiple data sub-blocks within the image block. Every image data sub-block is 1 to 255 bytes long, and it starts with the block length and ends with 0x00, the block terminator. Before the first sub-block, there is one more value, the minimum code size. This is something that LZW compression uses, and we will need this field a little bit later. We need to obtain the compressed image data in a single BYTEA
field. To do this, we extract the data from the sub-blocks recursively, ending the recursion when we come across a zero-length sub-block, then concatenated all the extracted chunks of data together:
05.lzw-data.sql
WITH RECURSIVE bin AS ( SELECT DECODE(data, 'HEX') AS buffer FROM input ), header AS ( SELECT h.*, io.* FROM bin CROSS JOIN LATERAL ( SELECT GET_BYTE(buffer, 10)::BIT(8) AS flags ) q CROSS JOIN LATERAL ( SELECT CONVERT_FROM(SUBSTR(buffer, 1, 6), 'LATIN1') AS version, (GET_BYTE(buffer, 7)::BIT(8) || GET_BYTE(buffer, 6)::BIT(8))::BIT(16)::INT AS width, (GET_BYTE(buffer, 9)::BIT(8) || GET_BYTE(buffer, 8)::BIT(8))::BIT(16)::INT AS height, flags::BIT(1) = B'1' AS gct, (flags << 1)::BIT(3)::INT + 1 AS depth, (flags << 4) = B'1' AS color_sort, 2 << (flags << 5)::BIT(3)::INT AS gct_size, GET_BYTE(buffer, 11)::INT AS bci, GET_BYTE(buffer, 12)::INT AS aspect ) h CROSS JOIN LATERAL ( SELECT 13 + 3 * gct_size AS blocks_offset ) io ), blocks AS ( SELECT blocks_offset AS current, GET_BYTE(buffer, blocks_offset) AS intro FROM bin CROSS JOIN header UNION ALL SELECT current, GET_BYTE(buffer, current) AS intro FROM ( SELECT current AS previous, intro AS previous_intro FROM blocks ) b CROSS JOIN bin CROSS JOIN LATERAL ( SELECT previous + GET_BYTE(buffer, previous + 2) + 4 AS current ) q WHERE previous_intro = x'21'::INT ), image_offset AS ( SELECT current AS image_offset FROM blocks WHERE intro = x'2C'::INT ), image_header AS ( SELECT q.*, l.*, l2.* FROM image_offset CROSS JOIN bin CROSS JOIN header h CROSS JOIN LATERAL ( SELECT GET_BYTE(buffer, image_offset + 9)::BIT(8) AS flags ) f CROSS JOIN LATERAL ( SELECT (GET_BYTE(buffer, image_offset + 2)::BIT(8) || GET_BYTE(buffer, image_offset + 1)::BIT(8))::BIT(16)::INT AS left, (GET_BYTE(buffer, image_offset + 4)::BIT(8) || GET_BYTE(buffer, image_offset + 3)::BIT(8))::BIT(16)::INT AS top, (GET_BYTE(buffer, image_offset + 6)::BIT(8) || GET_BYTE(buffer, image_offset + 5)::BIT(8))::BIT(16)::INT AS width, (GET_BYTE(buffer, image_offset + 8)::BIT(8) || GET_BYTE(buffer, image_offset + 7)::BIT(8))::BIT(16)::INT AS height, flags::BIT(1) = b'1' AS has_lct, (flags << 1)::BIT(1) = b'1' AS interlace, (flags << 2)::BIT(1) = b'1' AS sort ) q CROSS JOIN LATERAL ( SELECT CASE WHEN has_lct THEN 2 << (flags << 5)::BIT(3)::INT ELSE 0 END AS lct_size ) l CROSS JOIN LATERAL ( SELECT image_offset + 10 + lct_size AS image_data_offset ) l2 ), image_data AS ( SELECT GET_BYTE(buffer, image_data_offset) AS code_size, image_data_offset + 1 AS image_first_block_offset FROM image_header CROSS JOIN bin ), image_blocks AS ( SELECT image_first_block_offset AS block_offset, block_size, SUBSTR(buffer, image_first_block_offset + 2, block_size) AS block_data FROM image_data CROSS JOIN bin CROSS JOIN LATERAL ( SELECT GET_BYTE(buffer, image_first_block_offset) AS block_size ) l UNION ALL SELECT new_offset AS block_offset, new_block_size AS block_size, SUBSTR(buffer, new_offset + 2, new_block_size) AS block_data FROM image_blocks CROSS JOIN LATERAL ( SELECT block_offset + block_size + 1 AS new_offset ) no CROSS JOIN bin CROSS JOIN LATERAL ( SELECT GET_BYTE(buffer, new_offset) AS new_block_size ) nbs WHERE new_block_size > 0 ), lzw_data AS ( SELECT code_size, (1 << code_size) AS clear_code, (1 << code_size) + 1 AS eof_code, compressed FROM ( SELECT STRING_AGG(block_data, '' ORDER BY block_offset) compressed FROM image_blocks ) i CROSS JOIN image_data ) SELECT code_size, ENCODE(compressed, 'HEX') FROM lzw_data;
code_size | encode |
---|---|
2 | 8c2d99872a1cdc33a00275ec95faa8de608c04914c01 |
There are always at least two sub-blocks in an image block, the last one having zero length. We now see the compressed data and a cryptic number called code_size
. We will need it later to do the decompression.
LZW decompression
Here comes the most interesting part of the post, the actual decompression.
LZW is a dictionary algorithm. It tries to find the repeating patterns of colors and assign them a code in a lookup table. Here's what the lookup table for our sample file looks like (I only list first several entries here):
Code | Pattern |
6 | 1, 1 |
7 | 1, 1, 1 |
8 | 1, 1, 2 |
9 | 2, 2 |
10 | 2, 2, 2 |
, etc.
Note how this table starts from 6. There is a reason for that.
The sample file only uses four colors, so the first four entries in the lookup table will correspond to those colors directly. This is common for all implementations of LZW.
Now, the first four entries (0 to 3) correspond to the colors. What do 4 and 5 do then? Those are specific to the flavor of LZW used by GIF.
4 (or, generally speaking, 2palette_size) means "clear code". It does not correspond to any color or a pattern. It acts like a soft-reset switch: when the algorithm comes across this code, it resets its state and clears the lookup table.
5 (or, generally speaking, 2palette_size + 1) means "EOF code". It's a signal to stop the conversion.
One of the most amazing properties of LZW is that although we need to look up every pattern in the code table for decompression, the code table itself is not anywhere in the file. Instead, it is reconstructed in real time. The compression algorithm always adds codes to the table successively, and does so for every (yet) unknown pattern it comes across. So does the decompression algorithm. The first entry it adds to the table is always
2palette_size + 2, the second one is 2palette_size + 2 and so on. The patterns for those entries are reconstructed from patterns for the previous codes. The lookup table grows with every new code.
Since LZW is a compression algorithm after all, it tries to pack codes to bits in the most efficient manner. Since there are at least
2palette_size + 2 size codes, every code is encoded with palette_size + 1
bits, at first at least. Since the lookup table grows with every new pattern, at some point codes will stop to fit into this number of bits. When this happens , the algorithm increases the number of bits it packs each code into by one (but no more than 12).
This means that the code stream has variable bit length. For a 4-color image, codes start coming in 3 bits, then the bit length switches to 4, then 5 and so on, up to 12 bits per code if the image is large enough.
This is one of the challenges we have to deal with while implementing the decoder in SQL.
Another challenge is that the bit stream deals with bits, but the file storage format deals with bytes. Within each byte, the bits are stored LSB first. This means that three 3-bit codes would span two bytes. The first two codes would take up the least significant bits of the first byte, and the third code would take up one most significant bit from the first byte and two least significant ones from the second one.
This does not play well with the way PostgreSQL deals with the BYTEA
to BIT
conversion and BIT
arithmetic in general. That's why we have to shuffle the bytes around a little bit and do so for every code we extract from the stream.
To extract a code from the stream, on each step we need to know the offset to the last code (in bits) and current bit length. We then take three next bytes from the stream and concatenate them in reverse. Once we have done that, we can convert them to a BIT(24)
value and just use PostgreSQL's native SUBSTRING
to extract as many bits as we need to get the next code.
Once we know the next code, we should look at its value and decide what to do with that. If it's a eof_code
, we just end the processing. If it's a clear_code
, we reset our lookup table and the current bit length to their initial values. Finally, if it's a valid code, we reconstruct the original pattern either from the lookup table (by looking up either current code or the one we retrieved from the previous iteration), and add the new pattern to the lookup table.
To do this we need a shortcut (an analog of a return
statement in imperative languages). We would implement it as a series of a sourceless queries (those without a FROM
clause), combined with a UNION ALL
in an inline view, which is then selected from with LIMIT 1
. The first query to return a value will shortcut and the other ones won't be executed.
Finally, we need to store the lookup table between the iterations. We will employ PostgreSQL's HSTORE
type for that, as it's more efficient for key lookups than an array. It only can store text keys and text values, so we will have to convert INT
keys (codes) and INT[]
values (patterns) to text.
06.lzw-stream.sql
WITH RECURSIVE bin AS ( SELECT DECODE(data, 'HEX') AS buffer FROM input ), header AS ( SELECT h.*, io.* FROM bin CROSS JOIN LATERAL ( SELECT GET_BYTE(buffer, 10)::BIT(8) AS flags ) q CROSS JOIN LATERAL ( SELECT CONVERT_FROM(SUBSTR(buffer, 1, 6), 'LATIN1') AS version, (GET_BYTE(buffer, 7)::BIT(8) || GET_BYTE(buffer, 6)::BIT(8))::BIT(16)::INT AS width, (GET_BYTE(buffer, 9)::BIT(8) || GET_BYTE(buffer, 8)::BIT(8))::BIT(16)::INT AS height, flags::BIT(1) = B'1' AS gct, (flags << 1)::BIT(3)::INT + 1 AS depth, (flags << 4) = B'1' AS color_sort, 2 << (flags << 5)::BIT(3)::INT AS gct_size, GET_BYTE(buffer, 11)::INT AS bci, GET_BYTE(buffer, 12)::INT AS aspect ) h CROSS JOIN LATERAL ( SELECT 13 + 3 * gct_size AS blocks_offset ) io ), blocks AS ( SELECT blocks_offset AS current, GET_BYTE(buffer, blocks_offset) AS intro FROM bin CROSS JOIN header UNION ALL SELECT current, GET_BYTE(buffer, current) AS intro FROM ( SELECT current AS previous, intro AS previous_intro FROM blocks ) b CROSS JOIN bin CROSS JOIN LATERAL ( SELECT previous + GET_BYTE(buffer, previous + 2) + 4 AS current ) q WHERE previous_intro = x'21'::INT ), image_offset AS ( SELECT current AS image_offset FROM blocks WHERE intro = x'2C'::INT ), image_header AS ( SELECT q.*, l.*, l2.* FROM image_offset CROSS JOIN bin CROSS JOIN header h CROSS JOIN LATERAL ( SELECT GET_BYTE(buffer, image_offset + 9)::BIT(8) AS flags ) f CROSS JOIN LATERAL ( SELECT (GET_BYTE(buffer, image_offset + 2)::BIT(8) || GET_BYTE(buffer, image_offset + 1)::BIT(8))::BIT(16)::INT AS left, (GET_BYTE(buffer, image_offset + 4)::BIT(8) || GET_BYTE(buffer, image_offset + 3)::BIT(8))::BIT(16)::INT AS top, (GET_BYTE(buffer, image_offset + 6)::BIT(8) || GET_BYTE(buffer, image_offset + 5)::BIT(8))::BIT(16)::INT AS width, (GET_BYTE(buffer, image_offset + 8)::BIT(8) || GET_BYTE(buffer, image_offset + 7)::BIT(8))::BIT(16)::INT AS height, flags::BIT(1) = b'1' AS has_lct, (flags << 1)::BIT(1) = b'1' AS interlace, (flags << 2)::BIT(1) = b'1' AS sort ) q CROSS JOIN LATERAL ( SELECT CASE WHEN has_lct THEN 2 << (flags << 5)::BIT(3)::INT ELSE 0 END AS lct_size ) l CROSS JOIN LATERAL ( SELECT image_offset + 10 + lct_size AS image_data_offset ) l2 ), image_data AS ( SELECT GET_BYTE(buffer, image_data_offset) AS code_size, image_data_offset + 1 AS image_first_block_offset FROM image_header CROSS JOIN bin ), image_blocks AS ( SELECT image_first_block_offset AS block_offset, block_size, SUBSTR(buffer, image_first_block_offset + 2, block_size) AS block_data FROM image_data CROSS JOIN bin CROSS JOIN LATERAL ( SELECT GET_BYTE(buffer, image_first_block_offset) AS block_size ) l UNION ALL SELECT new_offset AS block_offset, new_block_size AS block_size, SUBSTR(buffer, new_offset + 2, new_block_size) AS block_data FROM image_blocks CROSS JOIN LATERAL ( SELECT block_offset + block_size + 1 AS new_offset ) no CROSS JOIN bin CROSS JOIN LATERAL ( SELECT GET_BYTE(buffer, new_offset) AS new_block_size ) nbs WHERE new_block_size > 0 ), lzw_data AS ( SELECT code_size, (1 << code_size) AS clear_code, (1 << code_size) + 1 AS eof_code, compressed FROM ( SELECT STRING_AGG(block_data, '' ORDER BY block_offset) compressed FROM image_blocks ) i CROSS JOIN image_data ), lzw_bits AS ( SELECT current_code_size, clear_code AS code, ARRAY[]::INT[] AS output_chunk, 0 AS next_bit_offset, NULL::HSTORE AS codes, 0 AS next_table_key, 0 AS next_index FROM lzw_data CROSS JOIN LATERAL ( SELECT code_size + 1 AS current_code_size ) cc UNION ALL SELECT next_code_size, code, output_chunk, bit_offset + current_code_size, new_codes AS codes, next_table_key, next_index FROM ( SELECT code AS previous_code, current_code_size, next_bit_offset AS bit_offset, codes, next_table_key AS current_table_key, next_index + COALESCE(ARRAY_UPPER(output_chunk, 1), 0) AS next_index, ld.* FROM lzw_bits CROSS JOIN ( SELECT code_size AS initial_code_size, clear_code, eof_code, compressed FROM lzw_data ) ld ) lb CROSS JOIN LATERAL ( SELECT bit_offset / 8 AS byte_offset ) bo CROSS JOIN LATERAL ( SELECT ( CASE WHEN byte_offset < LENGTH(compressed) - 2 THEN GET_BYTE(compressed, byte_offset + 2) ELSE 0 END::BIT(8) || CASE WHEN byte_offset < LENGTH(compressed) - 1 THEN GET_BYTE(compressed, byte_offset + 1) ELSE 0 END::BIT(8) || GET_BYTE(compressed, byte_offset)::BIT(8) )::BIT(24) AS cut ) cc CROSS JOIN LATERAL ( SELECT SUBSTRING(cut, 25 - current_code_size - bit_offset % 8, current_code_size)::INT AS code ) l CROSS JOIN LATERAL ( SELECT * FROM ( SELECT ARRAY[]::INT[] AS output_chunk, HSTORE(ARRAY[]::TEXT[][]) AS new_codes, eof_code + 1 AS next_table_key, initial_code_size + 1 AS next_code_size WHERE code = clear_code UNION ALL SELECT CASE WHEN code < clear_code THEN ARRAY ELSE (codes->(code::TEXT))::INT[] END AS output_chunk, codes AS new_codes, current_table_key AS next_table_key, current_code_size AS next_code_size WHERE previous_code = clear_code UNION ALL SELECT output_chunk, CASE current_table_key WHEN 4095 THEN codes ELSE codes || HSTORE(current_table_key::TEXT, next_table_chunk::TEXT) END AS new_codes, next_table_key, CASE next_table_key WHEN (1 << current_code_size) THEN current_code_size + 1 ELSE current_code_size END AS next_code_size FROM ( SELECT CASE WHEN previous_code < clear_code THEN ARRAY[previous_code] ELSE (codes->(previous_code::TEXT))::INT[] END AS previous_chunk, LEAST(current_table_key + 1, 4095) AS next_table_key, code < clear_code OR codes ? (code::TEXT) AS code_in_table ) pc CROSS JOIN LATERAL ( SELECT output_chunk, previous_chunk || output_chunk[1] AS next_table_chunk FROM ( SELECT CASE WHEN code < clear_code THEN ARRAY ELSE (codes->(code::TEXT))::INT[] END AS output_chunk ) q WHERE code_in_table UNION ALL SELECT output_chunk, output_chunk AS next_table_chunk FROM ( SELECT previous_chunk || previous_chunk[1] AS output_chunk ) q WHERE NOT code_in_table ) q WHERE code <> eof_code ) q LIMIT 1 ) ns WHERE bit_offset < LENGTH(compressed) * 8 ) SELECT * FROM lzw_bits;
current_code_size | code | output_chunk | next_bit_offset | codes | next_table_key | next_index |
---|---|---|---|---|---|---|
3 | 4 | [] | 0 | None | 0 | 0 |
3 | 4 | [] | 3 | 6 | 0 | |
3 | 1 | [1] | 6 | 6 | 0 | |
3 | 6 | [1, 1] | 9 | "6"=>"{1,1}" | 7 | 1 |
4 | 6 | [1, 1] | 12 | "6"=>"{1,1}", "7"=>"{1,1,1}" | 8 | 3 |
4 | 2 | [2] | 16 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}" | 9 | 5 |
4 | 9 | [2, 2] | 20 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}" | 10 | 6 |
4 | 9 | [2, 2] | 24 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}" | 11 | 8 |
4 | 7 | [1, 1, 1] | 28 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}" | 12 | 10 |
4 | 8 | [1, 1, 2] | 32 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}" | 13 | 13 |
4 | 10 | [2, 2, 2] | 36 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}" | 14 | 16 |
4 | 2 | [2] | 40 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}", "14"=>"{2,2,2,2}" | 15 | 19 |
5 | 12 | [1, 1, 1, 1] | 44 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}", "14"=>"{2,2,2,2}", "15"=>"{2,1}" | 16 | 20 |
5 | 1 | [1] | 49 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}", "14"=>"{2,2,2,2}", "15"=>"{2,1}", "16"=>"{1,1,1,1,1}" | 17 | 24 |
5 | 14 | [2, 2, 2, 2] | 54 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}", "14"=>"{2,2,2,2}", "15"=>"{2,1}", "16"=>"{1,1,1,1,1}", "17"=>"{1,2}" | 18 | 25 |
5 | 15 | [2, 1] | 59 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}", "14"=>"{2,2,2,2}", "15"=>"{2,1}", "16"=>"{1,1,1,1,1}", "17"=>"{1,2}", "18"=>"{2,2,2,2,2}" | 19 | 29 |
5 | 6 | [1, 1] | 64 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}", "14"=>"{2,2,2,2}", "15"=>"{2,1}", "16"=>"{1,1,1,1,1}", "17"=>"{1,2}", "18"=>"{2,2,2,2,2}", "19"=>"{2,1,1}" | 20 | 31 |
5 | 0 | [0] | 69 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}", "14"=>"{2,2,2,2}", "15"=>"{2,1}", "16"=>"{1,1,1,1,1}", "17"=>"{1,2}", "18"=>"{2,2,2,2,2}", "19"=>"{2,1,1}", "20"=>"{1,1,0}" | 21 | 33 |
5 | 21 | [0, 0] | 74 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}", "14"=>"{2,2,2,2}", "15"=>"{2,1}", "16"=>"{1,1,1,1,1}", "17"=>"{1,2}", "18"=>"{2,2,2,2,2}", "19"=>"{2,1,1}", "20"=>"{1,1,0}", "21"=>"{0,0}" | 22 | 34 |
5 | 0 | [0] | 79 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}", "14"=>"{2,2,2,2}", "15"=>"{2,1}", "16"=>"{1,1,1,1,1}", "17"=>"{1,2}", "18"=>"{2,2,2,2,2}", "19"=>"{2,1,1}", "20"=>"{1,1,0}", "21"=>"{0,0}", "22"=>"{0,0,0}" | 23 | 36 |
5 | 10 | [2, 2, 2] | 84 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}", "14"=>"{2,2,2,2}", "15"=>"{2,1}", "16"=>"{1,1,1,1,1}", "17"=>"{1,2}", "18"=>"{2,2,2,2,2}", "19"=>"{2,1,1}", "20"=>"{1,1,0}", "21"=>"{0,0}", "22"=>"{0,0,0}", "23"=>"{0,2}" | 24 | 37 |
5 | 7 | [1, 1, 1] | 89 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}", "14"=>"{2,2,2,2}", "15"=>"{2,1}", "16"=>"{1,1,1,1,1}", "17"=>"{1,2}", "18"=>"{2,2,2,2,2}", "19"=>"{2,1,1}", "20"=>"{1,1,0}", "21"=>"{0,0}", "22"=>"{0,0,0}", "23"=>"{0,2}", "24"=>"{2,2,2,1}" | 25 | 40 |
5 | 22 | [0, 0, 0] | 94 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}", "14"=>"{2,2,2,2}", "15"=>"{2,1}", "16"=>"{1,1,1,1,1}", "17"=>"{1,2}", "18"=>"{2,2,2,2,2}", "19"=>"{2,1,1}", "20"=>"{1,1,0}", "21"=>"{0,0}", "22"=>"{0,0,0}", "23"=>"{0,2}", "24"=>"{2,2,2,1}", "25"=>"{1,1,1,0}" | 26 | 43 |
5 | 23 | [0, 2] | 99 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}", "14"=>"{2,2,2,2}", "15"=>"{2,1}", "16"=>"{1,1,1,1,1}", "17"=>"{1,2}", "18"=>"{2,2,2,2,2}", "19"=>"{2,1,1}", "20"=>"{1,1,0}", "21"=>"{0,0}", "22"=>"{0,0,0}", "23"=>"{0,2}", "24"=>"{2,2,2,1}", "25"=>"{1,1,1,0}", "26"=>"{0,0,0,0}" | 27 | 46 |
5 | 18 | [2, 2, 2, 2, 2] | 104 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}", "14"=>"{2,2,2,2}", "15"=>"{2,1}", "16"=>"{1,1,1,1,1}", "17"=>"{1,2}", "18"=>"{2,2,2,2,2}", "19"=>"{2,1,1}", "20"=>"{1,1,0}", "21"=>"{0,0}", "22"=>"{0,0,0}", "23"=>"{0,2}", "24"=>"{2,2,2,1}", "25"=>"{1,1,1,0}", "26"=>"{0,0,0,0}", "27"=>"{0,2,2}" | 28 | 48 |
5 | 26 | [0, 0, 0, 0] | 109 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}", "14"=>"{2,2,2,2}", "15"=>"{2,1}", "16"=>"{1,1,1,1,1}", "17"=>"{1,2}", "18"=>"{2,2,2,2,2}", "19"=>"{2,1,1}", "20"=>"{1,1,0}", "21"=>"{0,0}", "22"=>"{0,0,0}", "23"=>"{0,2}", "24"=>"{2,2,2,1}", "25"=>"{1,1,1,0}", "26"=>"{0,0,0,0}", "27"=>"{0,2,2}", "28"=>"{2,2,2,2,2,0}" | 29 | 53 |
5 | 7 | [1, 1, 1] | 114 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}", "14"=>"{2,2,2,2}", "15"=>"{2,1}", "16"=>"{1,1,1,1,1}", "17"=>"{1,2}", "18"=>"{2,2,2,2,2}", "19"=>"{2,1,1}", "20"=>"{1,1,0}", "21"=>"{0,0}", "22"=>"{0,0,0}", "23"=>"{0,2}", "24"=>"{2,2,2,1}", "25"=>"{1,1,1,0}", "26"=>"{0,0,0,0}", "27"=>"{0,2,2}", "28"=>"{2,2,2,2,2,0}", "29"=>"{0,0,0,0,1}" | 30 | 57 |
5 | 10 | [2, 2, 2] | 119 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}", "14"=>"{2,2,2,2}", "15"=>"{2,1}", "16"=>"{1,1,1,1,1}", "17"=>"{1,2}", "18"=>"{2,2,2,2,2}", "19"=>"{2,1,1}", "20"=>"{1,1,0}", "21"=>"{0,0}", "22"=>"{0,0,0}", "23"=>"{0,2}", "24"=>"{2,2,2,1}", "25"=>"{1,1,1,0}", "26"=>"{0,0,0,0}", "27"=>"{0,2,2}", "28"=>"{2,2,2,2,2,0}", "29"=>"{0,0,0,0,1}", "30"=>"{1,1,1,2}" | 31 | 60 |
6 | 29 | [0, 0, 0, 0, 1] | 124 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}", "14"=>"{2,2,2,2}", "15"=>"{2,1}", "16"=>"{1,1,1,1,1}", "17"=>"{1,2}", "18"=>"{2,2,2,2,2}", "19"=>"{2,1,1}", "20"=>"{1,1,0}", "21"=>"{0,0}", "22"=>"{0,0,0}", "23"=>"{0,2}", "24"=>"{2,2,2,1}", "25"=>"{1,1,1,0}", "26"=>"{0,0,0,0}", "27"=>"{0,2,2}", "28"=>"{2,2,2,2,2,0}", "29"=>"{0,0,0,0,1}", "30"=>"{1,1,1,2}", "31"=>"{2,2,2,0}" | 32 | 63 |
6 | 13 | [1, 1, 2, 2] | 130 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}", "14"=>"{2,2,2,2}", "15"=>"{2,1}", "16"=>"{1,1,1,1,1}", "17"=>"{1,2}", "18"=>"{2,2,2,2,2}", "19"=>"{2,1,1}", "20"=>"{1,1,0}", "21"=>"{0,0}", "22"=>"{0,0,0}", "23"=>"{0,2}", "24"=>"{2,2,2,1}", "25"=>"{1,1,1,0}", "26"=>"{0,0,0,0}", "27"=>"{0,2,2}", "28"=>"{2,2,2,2,2,0}", "29"=>"{0,0,0,0,1}", "30"=>"{1,1,1,2}", "31"=>"{2,2,2,0}", "32"=>"{0,0,0,0,1,1}" | 33 | 68 |
6 | 24 | [2, 2, 2, 1] | 136 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}", "14"=>"{2,2,2,2}", "15"=>"{2,1}", "16"=>"{1,1,1,1,1}", "17"=>"{1,2}", "18"=>"{2,2,2,2,2}", "19"=>"{2,1,1}", "20"=>"{1,1,0}", "21"=>"{0,0}", "22"=>"{0,0,0}", "23"=>"{0,2}", "24"=>"{2,2,2,1}", "25"=>"{1,1,1,0}", "26"=>"{0,0,0,0}", "27"=>"{0,2,2}", "28"=>"{2,2,2,2,2,0}", "29"=>"{0,0,0,0,1}", "30"=>"{1,1,1,2}", "31"=>"{2,2,2,0}", "32"=>"{0,0,0,0,1,1}", "33"=>"{1,1,2,2,2}" | 34 | 72 |
6 | 12 | [1, 1, 1, 1] | 142 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}", "14"=>"{2,2,2,2}", "15"=>"{2,1}", "16"=>"{1,1,1,1,1}", "17"=>"{1,2}", "18"=>"{2,2,2,2,2}", "19"=>"{2,1,1}", "20"=>"{1,1,0}", "21"=>"{0,0}", "22"=>"{0,0,0}", "23"=>"{0,2}", "24"=>"{2,2,2,1}", "25"=>"{1,1,1,0}", "26"=>"{0,0,0,0}", "27"=>"{0,2,2}", "28"=>"{2,2,2,2,2,0}", "29"=>"{0,0,0,0,1}", "30"=>"{1,1,1,2}", "31"=>"{2,2,2,0}", "32"=>"{0,0,0,0,1,1}", "33"=>"{1,1,2,2,2}", "34"=>"{2,2,2,1,1}" | 35 | 76 |
6 | 18 | [2, 2, 2, 2, 2] | 148 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}", "14"=>"{2,2,2,2}", "15"=>"{2,1}", "16"=>"{1,1,1,1,1}", "17"=>"{1,2}", "18"=>"{2,2,2,2,2}", "19"=>"{2,1,1}", "20"=>"{1,1,0}", "21"=>"{0,0}", "22"=>"{0,0,0}", "23"=>"{0,2}", "24"=>"{2,2,2,1}", "25"=>"{1,1,1,0}", "26"=>"{0,0,0,0}", "27"=>"{0,2,2}", "28"=>"{2,2,2,2,2,0}", "29"=>"{0,0,0,0,1}", "30"=>"{1,1,1,2}", "31"=>"{2,2,2,0}", "32"=>"{0,0,0,0,1,1}", "33"=>"{1,1,2,2,2}", "34"=>"{2,2,2,1,1}", "35"=>"{1,1,1,1,2}" | 36 | 80 |
6 | 16 | [1, 1, 1, 1, 1] | 154 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}", "14"=>"{2,2,2,2}", "15"=>"{2,1}", "16"=>"{1,1,1,1,1}", "17"=>"{1,2}", "18"=>"{2,2,2,2,2}", "19"=>"{2,1,1}", "20"=>"{1,1,0}", "21"=>"{0,0}", "22"=>"{0,0,0}", "23"=>"{0,2}", "24"=>"{2,2,2,1}", "25"=>"{1,1,1,0}", "26"=>"{0,0,0,0}", "27"=>"{0,2,2}", "28"=>"{2,2,2,2,2,0}", "29"=>"{0,0,0,0,1}", "30"=>"{1,1,1,2}", "31"=>"{2,2,2,0}", "32"=>"{0,0,0,0,1,1}", "33"=>"{1,1,2,2,2}", "34"=>"{2,2,2,1,1}", "35"=>"{1,1,1,1,2}", "36"=>"{2,2,2,2,2,1}" | 37 | 85 |
6 | 36 | [2, 2, 2, 2, 2, 1] | 160 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}", "14"=>"{2,2,2,2}", "15"=>"{2,1}", "16"=>"{1,1,1,1,1}", "17"=>"{1,2}", "18"=>"{2,2,2,2,2}", "19"=>"{2,1,1}", "20"=>"{1,1,0}", "21"=>"{0,0}", "22"=>"{0,0,0}", "23"=>"{0,2}", "24"=>"{2,2,2,1}", "25"=>"{1,1,1,0}", "26"=>"{0,0,0,0}", "27"=>"{0,2,2}", "28"=>"{2,2,2,2,2,0}", "29"=>"{0,0,0,0,1}", "30"=>"{1,1,1,2}", "31"=>"{2,2,2,0}", "32"=>"{0,0,0,0,1,1}", "33"=>"{1,1,2,2,2}", "34"=>"{2,2,2,1,1}", "35"=>"{1,1,1,1,2}", "36"=>"{2,2,2,2,2,1}", "37"=>"{1,1,1,1,1,2}" | 38 | 90 |
6 | 12 | [1, 1, 1, 1] | 166 | "6"=>"{1,1}", "7"=>"{1,1,1}", "8"=>"{1,1,2}", "9"=>"{2,2}", "10"=>"{2,2,2}", "11"=>"{2,2,1}", "12"=>"{1,1,1,1}", "13"=>"{1,1,2,2}", "14"=>"{2,2,2,2}", "15"=>"{2,1}", "16"=>"{1,1,1,1,1}", "17"=>"{1,2}", "18"=>"{2,2,2,2,2}", "19"=>"{2,1,1}", "20"=>"{1,1,0}", "21"=>"{0,0}", "22"=>"{0,0,0}", "23"=>"{0,2}", "24"=>"{2,2,2,1}", "25"=>"{1,1,1,0}", "26"=>"{0,0,0,0}", "27"=>"{0,2,2}", "28"=>"{2,2,2,2,2,0}", "29"=>"{0,0,0,0,1}", "30"=>"{1,1,1,2}", "31"=>"{2,2,2,0}", "32"=>"{0,0,0,0,1,1}", "33"=>"{1,1,2,2,2}", "34"=>"{2,2,2,1,1}", "35"=>"{1,1,1,1,2}", "36"=>"{2,2,2,2,2,1}", "37"=>"{1,1,1,1,1,2}", "38"=>"{2,2,2,2,2,1,1}" | 39 | 96 |
Here's what we have here:
- In
lb
andbo
, we select the values from the previous recursion step and give them more meaningful names (so they become "previous" and "current" instead of "current" and "next" they used to be on the previous step). We are also doing some calculations we will need later and give their results aliases. - In
cc
, we shuffle the bytes for better representation ofBIT
so that we could cut our codes from the bit stream more easily. - In
l
, we cut the next code from the bit stream and give it a numeric value we will be working with. - In
ns
, the actual decompression happens.- The first two queries in
ns.q
do the special processing for the CLEAR code and the one right after it (the first code after CLEAR does not update the lookup table, as it's not a pattern yet). - The third query does the table lookup and the table update, as described in the tutorial.
- Note that there is another
UNION ALL
within the third query: the table lookup and update rules are different, depending on whether the code is in the lookup table already or not. - The third query also handles the EOF code (which just ends the recursion as no results would be returned from either query) and the possible 12-bit overflow.
- The first two queries in
- Note that we also explicitly select two additional fields in
lzw_bits
:next_table_key
(the next code which we will be putting in the lookup table) andnext_index
(the running count of decoded color indexes so far). Those are not strictly necessary as they can be reconstructed, respectively, from the lookup table and previous query records (using window functions). However, they improve the query performance greatly, so it makes sense to drag them through the recursion, even though it makes it a little bit more complex.
Mapping color indexes to actual colors
Now that we have a recordset of patterns (arrays), we can make a recordset of individual color indexes by unnesting every array with ordinality and adding the ordinality to the next_index
(which is, I will remind, the total length of patterns selected so far). This will give us a rowset of color indexes, each with its ordinal position in the original bitmap:
07.indexes.sql
WITH RECURSIVE bin AS ( SELECT DECODE(data, 'HEX') AS buffer FROM input ), header AS ( SELECT h.*, io.* FROM bin CROSS JOIN LATERAL ( SELECT GET_BYTE(buffer, 10)::BIT(8) AS flags ) q CROSS JOIN LATERAL ( SELECT CONVERT_FROM(SUBSTR(buffer, 1, 6), 'LATIN1') AS version, (GET_BYTE(buffer, 7)::BIT(8) || GET_BYTE(buffer, 6)::BIT(8))::BIT(16)::INT AS width, (GET_BYTE(buffer, 9)::BIT(8) || GET_BYTE(buffer, 8)::BIT(8))::BIT(16)::INT AS height, flags::BIT(1) = B'1' AS gct, (flags << 1)::BIT(3)::INT + 1 AS depth, (flags << 4) = B'1' AS color_sort, 2 << (flags << 5)::BIT(3)::INT AS gct_size, GET_BYTE(buffer, 11)::INT AS bci, GET_BYTE(buffer, 12)::INT AS aspect ) h CROSS JOIN LATERAL ( SELECT 13 + 3 * gct_size AS blocks_offset ) io ), blocks AS ( SELECT blocks_offset AS current, GET_BYTE(buffer, blocks_offset) AS intro FROM bin CROSS JOIN header UNION ALL SELECT current, GET_BYTE(buffer, current) AS intro FROM ( SELECT current AS previous, intro AS previous_intro FROM blocks ) b CROSS JOIN bin CROSS JOIN LATERAL ( SELECT previous + GET_BYTE(buffer, previous + 2) + 4 AS current ) q WHERE previous_intro = x'21'::INT ), image_offset AS ( SELECT current AS image_offset FROM blocks WHERE intro = x'2C'::INT ), image_header AS ( SELECT q.*, l.*, l2.* FROM image_offset CROSS JOIN bin CROSS JOIN header h CROSS JOIN LATERAL ( SELECT GET_BYTE(buffer, image_offset + 9)::BIT(8) AS flags ) f CROSS JOIN LATERAL ( SELECT (GET_BYTE(buffer, image_offset + 2)::BIT(8) || GET_BYTE(buffer, image_offset + 1)::BIT(8))::BIT(16)::INT AS left, (GET_BYTE(buffer, image_offset + 4)::BIT(8) || GET_BYTE(buffer, image_offset + 3)::BIT(8))::BIT(16)::INT AS top, (GET_BYTE(buffer, image_offset + 6)::BIT(8) || GET_BYTE(buffer, image_offset + 5)::BIT(8))::BIT(16)::INT AS width, (GET_BYTE(buffer, image_offset + 8)::BIT(8) || GET_BYTE(buffer, image_offset + 7)::BIT(8))::BIT(16)::INT AS height, flags::BIT(1) = b'1' AS has_lct, (flags << 1)::BIT(1) = b'1' AS interlace, (flags << 2)::BIT(1) = b'1' AS sort ) q CROSS JOIN LATERAL ( SELECT CASE WHEN has_lct THEN 2 << (flags << 5)::BIT(3)::INT ELSE 0 END AS lct_size ) l CROSS JOIN LATERAL ( SELECT image_offset + 10 + lct_size AS image_data_offset ) l2 ), image_data AS ( SELECT GET_BYTE(buffer, image_data_offset) AS code_size, image_data_offset + 1 AS image_first_block_offset FROM image_header CROSS JOIN bin ), image_blocks AS ( SELECT image_first_block_offset AS block_offset, block_size, SUBSTR(buffer, image_first_block_offset + 2, block_size) AS block_data FROM image_data CROSS JOIN bin CROSS JOIN LATERAL ( SELECT GET_BYTE(buffer, image_first_block_offset) AS block_size ) l UNION ALL SELECT new_offset AS block_offset, new_block_size AS block_size, SUBSTR(buffer, new_offset + 2, new_block_size) AS block_data FROM image_blocks CROSS JOIN LATERAL ( SELECT block_offset + block_size + 1 AS new_offset ) no CROSS JOIN bin CROSS JOIN LATERAL ( SELECT GET_BYTE(buffer, new_offset) AS new_block_size ) nbs WHERE new_block_size > 0 ), lzw_data AS ( SELECT code_size, (1 << code_size) AS clear_code, (1 << code_size) + 1 AS eof_code, compressed FROM ( SELECT STRING_AGG(block_data, '' ORDER BY block_offset) compressed FROM image_blocks ) i CROSS JOIN image_data ), lzw_bits AS ( SELECT current_code_size, clear_code AS code, ARRAY[]::INT[] AS output_chunk, 0 AS next_bit_offset, NULL::HSTORE AS codes, 0 AS next_table_key, 0 AS next_index FROM lzw_data CROSS JOIN LATERAL ( SELECT code_size + 1 AS current_code_size ) cc UNION ALL SELECT next_code_size, code, output_chunk, bit_offset + current_code_size, new_codes AS codes, next_table_key, next_index FROM ( SELECT code AS previous_code, current_code_size, next_bit_offset AS bit_offset, codes, next_table_key AS current_table_key, next_index + COALESCE(ARRAY_UPPER(output_chunk, 1), 0) AS next_index, ld.* FROM lzw_bits CROSS JOIN ( SELECT code_size AS initial_code_size, clear_code, eof_code, compressed FROM lzw_data ) ld ) lb CROSS JOIN LATERAL ( SELECT bit_offset / 8 AS byte_offset ) bo CROSS JOIN LATERAL ( SELECT ( CASE WHEN byte_offset < LENGTH(compressed) - 2 THEN GET_BYTE(compressed, byte_offset + 2) ELSE 0 END::BIT(8) || CASE WHEN byte_offset < LENGTH(compressed) - 1 THEN GET_BYTE(compressed, byte_offset + 1) ELSE 0 END::BIT(8) || GET_BYTE(compressed, byte_offset)::BIT(8) )::BIT(24) AS cut ) cc CROSS JOIN LATERAL ( SELECT SUBSTRING(cut, 25 - current_code_size - bit_offset % 8, current_code_size)::INT AS code ) l CROSS JOIN LATERAL ( SELECT * FROM ( SELECT ARRAY[]::INT[] AS output_chunk, HSTORE(ARRAY[]::TEXT[][]) AS new_codes, eof_code + 1 AS next_table_key, initial_code_size + 1 AS next_code_size WHERE code = clear_code UNION ALL SELECT CASE WHEN code < clear_code THEN ARRAY ELSE (codes->(code::TEXT))::INT[] END AS output_chunk, codes AS new_codes, current_table_key AS next_table_key, current_code_size AS next_code_size WHERE previous_code = clear_code UNION ALL SELECT output_chunk, CASE current_table_key WHEN 4095 THEN codes ELSE codes || HSTORE(current_table_key::TEXT, next_table_chunk::TEXT) END AS new_codes, next_table_key, CASE next_table_key WHEN (1 << current_code_size) THEN current_code_size + 1 ELSE current_code_size END AS next_code_size FROM ( SELECT CASE WHEN previous_code < clear_code THEN ARRAY[previous_code] ELSE (codes->(previous_code::TEXT))::INT[] END AS previous_chunk, LEAST(current_table_key + 1, 4095) AS next_table_key, code < clear_code OR codes ? (code::TEXT) AS code_in_table ) pc CROSS JOIN LATERAL ( SELECT output_chunk, previous_chunk || output_chunk[1] AS next_table_chunk FROM ( SELECT CASE WHEN code < clear_code THEN ARRAY ELSE (codes->(code::TEXT))::INT[] END AS output_chunk ) q WHERE code_in_table UNION ALL SELECT output_chunk, output_chunk AS next_table_chunk FROM ( SELECT previous_chunk || previous_chunk[1] AS output_chunk ) q WHERE NOT code_in_table AND code <> eof_code ) q ) q LIMIT 1 ) ns WHERE previous_code IS DISTINCT FROM eof_code AND bit_offset < LENGTH(compressed) * 8 ), indices AS ( SELECT idx, next_index + position - 1 AS rn FROM lzw_bits CROSS JOIN LATERAL UNNEST(output_chunk) WITH ORDINALITY q (idx, position) ) SELECT * FROM indices;
idx | rn | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 | 2 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 | 3 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 | 4 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
2 | 5 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
2 | 94 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 | 95 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 | 96 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 | 97 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 | 98 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 | 99 |
(results cut for readability).
We have exactly 100 records here, which corresponds to our 10×10 image. Those are not actual colors though yet, just the indexes.
To retrieve the actual colors we have to look them up by index in the Global Color Table (remember that thing)? This is just a region in our original binary stream, and the color indexes are nothing but offsets from the beginning of this region. And while we're at that, we could just as well convert our image to grayscale: there is a formula for that.
08.pixels.sql
WITH RECURSIVE bin AS ( SELECT DECODE(data, 'HEX') AS buffer FROM input ), header AS ( SELECT h.*, io.* FROM bin CROSS JOIN LATERAL ( SELECT GET_BYTE(buffer, 10)::BIT(8) AS flags ) q CROSS JOIN LATERAL ( SELECT CONVERT_FROM(SUBSTR(buffer, 1, 6), 'LATIN1') AS version, (GET_BYTE(buffer, 7)::BIT(8) || GET_BYTE(buffer, 6)::BIT(8))::BIT(16)::INT AS width, (GET_BYTE(buffer, 9)::BIT(8) || GET_BYTE(buffer, 8)::BIT(8))::BIT(16)::INT AS height, flags::BIT(1) = B'1' AS gct, (flags << 1)::BIT(3)::INT + 1 AS depth, (flags << 4) = B'1' AS color_sort, 2 << (flags << 5)::BIT(3)::INT AS gct_size, GET_BYTE(buffer, 11)::INT AS bci, GET_BYTE(buffer, 12)::INT AS aspect ) h CROSS JOIN LATERAL ( SELECT 13 + 3 * gct_size AS blocks_offset ) io ), blocks AS ( SELECT blocks_offset AS current, GET_BYTE(buffer, blocks_offset) AS intro FROM bin CROSS JOIN header UNION ALL SELECT current, GET_BYTE(buffer, current) AS intro FROM ( SELECT current AS previous, intro AS previous_intro FROM blocks ) b CROSS JOIN bin CROSS JOIN LATERAL ( SELECT previous + GET_BYTE(buffer, previous + 2) + 4 AS current ) q WHERE previous_intro = x'21'::INT ), image_offset AS ( SELECT current AS image_offset FROM blocks WHERE intro = x'2C'::INT ), image_header AS ( SELECT q.*, l.*, l2.* FROM image_offset CROSS JOIN bin CROSS JOIN header h CROSS JOIN LATERAL ( SELECT GET_BYTE(buffer, image_offset + 9)::BIT(8) AS flags ) f CROSS JOIN LATERAL ( SELECT (GET_BYTE(buffer, image_offset + 2)::BIT(8) || GET_BYTE(buffer, image_offset + 1)::BIT(8))::BIT(16)::INT AS left, (GET_BYTE(buffer, image_offset + 4)::BIT(8) || GET_BYTE(buffer, image_offset + 3)::BIT(8))::BIT(16)::INT AS top, (GET_BYTE(buffer, image_offset + 6)::BIT(8) || GET_BYTE(buffer, image_offset + 5)::BIT(8))::BIT(16)::INT AS width, (GET_BYTE(buffer, image_offset + 8)::BIT(8) || GET_BYTE(buffer, image_offset + 7)::BIT(8))::BIT(16)::INT AS height, flags::BIT(1) = b'1' AS has_lct, (flags << 1)::BIT(1) = b'1' AS interlace, (flags << 2)::BIT(1) = b'1' AS sort ) q CROSS JOIN LATERAL ( SELECT CASE WHEN has_lct THEN 2 << (flags << 5)::BIT(3)::INT ELSE 0 END AS lct_size ) l CROSS JOIN LATERAL ( SELECT image_offset + 10 + lct_size AS image_data_offset ) l2 ), image_data AS ( SELECT GET_BYTE(buffer, image_data_offset) AS code_size, image_data_offset + 1 AS image_first_block_offset FROM image_header CROSS JOIN bin ), image_blocks AS ( SELECT image_first_block_offset AS block_offset, block_size, SUBSTR(buffer, image_first_block_offset + 2, block_size) AS block_data FROM image_data CROSS JOIN bin CROSS JOIN LATERAL ( SELECT GET_BYTE(buffer, image_first_block_offset) AS block_size ) l UNION ALL SELECT new_offset AS block_offset, new_block_size AS block_size, SUBSTR(buffer, new_offset + 2, new_block_size) AS block_data FROM image_blocks CROSS JOIN LATERAL ( SELECT block_offset + block_size + 1 AS new_offset ) no CROSS JOIN bin CROSS JOIN LATERAL ( SELECT GET_BYTE(buffer, new_offset) AS new_block_size ) nbs WHERE new_block_size > 0 ), lzw_data AS ( SELECT code_size, (1 << code_size) AS clear_code, (1 << code_size) + 1 AS eof_code, compressed FROM ( SELECT STRING_AGG(block_data, '' ORDER BY block_offset) compressed FROM image_blocks ) i CROSS JOIN image_data ), lzw_bits AS ( SELECT current_code_size, clear_code AS code, ARRAY[]::INT[] AS output_chunk, 0 AS next_bit_offset, NULL::HSTORE AS codes, 0 AS next_table_key, 0 AS next_index FROM lzw_data CROSS JOIN LATERAL ( SELECT code_size + 1 AS current_code_size ) cc UNION ALL SELECT next_code_size, code, output_chunk, bit_offset + current_code_size, new_codes AS codes, next_table_key, next_index FROM ( SELECT code AS previous_code, current_code_size, next_bit_offset AS bit_offset, codes, next_table_key AS current_table_key, next_index + COALESCE(ARRAY_UPPER(output_chunk, 1), 0) AS next_index, ld.* FROM lzw_bits CROSS JOIN ( SELECT code_size AS initial_code_size, clear_code, eof_code, compressed FROM lzw_data ) ld ) lb CROSS JOIN LATERAL ( SELECT bit_offset / 8 AS byte_offset ) bo CROSS JOIN LATERAL ( SELECT ( CASE WHEN byte_offset < LENGTH(compressed) - 2 THEN GET_BYTE(compressed, byte_offset + 2) ELSE 0 END::BIT(8) || CASE WHEN byte_offset < LENGTH(compressed) - 1 THEN GET_BYTE(compressed, byte_offset + 1) ELSE 0 END::BIT(8) || GET_BYTE(compressed, byte_offset)::BIT(8) )::BIT(24) AS cut ) cc CROSS JOIN LATERAL ( SELECT SUBSTRING(cut, 25 - current_code_size - bit_offset % 8, current_code_size)::INT AS code ) l CROSS JOIN LATERAL ( SELECT * FROM ( SELECT ARRAY[]::INT[] AS output_chunk, HSTORE(ARRAY[]::TEXT[][]) AS new_codes, eof_code + 1 AS next_table_key, initial_code_size + 1 AS next_code_size WHERE code = clear_code UNION ALL SELECT CASE WHEN code < clear_code THEN ARRAY ELSE (codes->(code::TEXT))::INT[] END AS output_chunk, codes AS new_codes, current_table_key AS next_table_key, current_code_size AS next_code_size WHERE previous_code = clear_code UNION ALL SELECT output_chunk, CASE current_table_key WHEN 4095 THEN codes ELSE codes || HSTORE(current_table_key::TEXT, next_table_chunk::TEXT) END AS new_codes, next_table_key, CASE next_table_key WHEN (1 << current_code_size) THEN current_code_size + 1 ELSE current_code_size END AS next_code_size FROM ( SELECT CASE WHEN previous_code < clear_code THEN ARRAY[previous_code] ELSE (codes->(previous_code::TEXT))::INT[] END AS previous_chunk, LEAST(current_table_key + 1, 4095) AS next_table_key, code < clear_code OR codes ? (code::TEXT) AS code_in_table ) pc CROSS JOIN LATERAL ( SELECT output_chunk, previous_chunk || output_chunk[1] AS next_table_chunk FROM ( SELECT CASE WHEN code < clear_code THEN ARRAY ELSE (codes->(code::TEXT))::INT[] END AS output_chunk ) q WHERE code_in_table UNION ALL SELECT output_chunk, output_chunk AS next_table_chunk FROM ( SELECT previous_chunk || previous_chunk[1] AS output_chunk ) q WHERE NOT code_in_table AND code <> eof_code ) q ) q LIMIT 1 ) ns WHERE previous_code IS DISTINCT FROM eof_code AND bit_offset < LENGTH(compressed) * 8 ), indices AS ( SELECT idx, next_index + position - 1 AS rn FROM lzw_bits CROSS JOIN LATERAL UNNEST(output_chunk) WITH ORDINALITY q (idx, position) ), pixels AS ( SELECT idx, rn % width AS x, rn / width AS y, luma FROM indices CROSS JOIN bin CROSS JOIN header CROSS JOIN LATERAL ( SELECT 255 max_value, 2.2 gamma, GET_BYTE(buffer, 13 + idx * 3 + 0) r, GET_BYTE(buffer, 13 + idx * 3 + 1) g, GET_BYTE(buffer, 13 + idx * 3 + 2) b, .2126 rw, .7152 gw, .0722 bw ) c CROSS JOIN LATERAL ( SELECT ((r::FLOAT / max_value) ^ gamma) * rw + ((g::FLOAT / max_value) ^ gamma) * gw + ((b::FLOAT / max_value) ^ gamma) * bw AS luma ) l ) SELECT * FROM pixels;
idx | x | y | luma | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 0 | 0 | 0.2126 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 | 1 | 0 | 0.2126 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 | 2 | 0 | 0.2126 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 | 3 | 0 | 0.2126 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 | 4 | 0 | 0.2126 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
2 | 5 | 0 | 0.0722 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
2 | 4 | 9 | 0.0722 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 | 5 | 9 | 0.2126 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 | 6 | 9 | 0.2126 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 | 7 | 9 | 0.2126 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 | 8 | 9 | 0.2126 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 | 9 | 9 | 0.2126 |
Rendering the result
Finally we need to render our picture on the screen. We do that as usual:
- Cross join two
GENERATE_SERIES
for width and height - Left join it with the rowset of pixels on
(x, y)
- Look up the ASCII art character depending on the pixel luminance
- Group by
y
andSTRING_AGG
the ASCII art characters without the separator, ordering byx
gif.sql
WITH RECURSIVE bin AS ( SELECT DECODE(data, 'HEX') AS buffer FROM input ), header AS ( SELECT h.*, io.* FROM bin CROSS JOIN LATERAL ( SELECT GET_BYTE(buffer, 10)::BIT(8) AS flags ) q CROSS JOIN LATERAL ( SELECT CONVERT_FROM(SUBSTR(buffer, 1, 6), 'LATIN1') AS version, (GET_BYTE(buffer, 7)::BIT(8) || GET_BYTE(buffer, 6)::BIT(8))::BIT(16)::INT AS width, (GET_BYTE(buffer, 9)::BIT(8) || GET_BYTE(buffer, 8)::BIT(8))::BIT(16)::INT AS height, flags::BIT(1) = B'1' AS gct, (flags << 1)::BIT(3)::INT + 1 AS depth, (flags << 4) = B'1' AS color_sort, 2 << (flags << 5)::BIT(3)::INT AS gct_size, GET_BYTE(buffer, 11)::INT AS bci, GET_BYTE(buffer, 12)::INT AS aspect ) h CROSS JOIN LATERAL ( SELECT 13 + 3 * gct_size AS blocks_offset ) io ), blocks AS ( SELECT blocks_offset AS current, GET_BYTE(buffer, blocks_offset) AS intro FROM bin CROSS JOIN header UNION ALL SELECT current, GET_BYTE(buffer, current) AS intro FROM ( SELECT current AS previous, intro AS previous_intro FROM blocks ) b CROSS JOIN bin CROSS JOIN LATERAL ( SELECT previous + GET_BYTE(buffer, previous + 2) + 4 AS current ) q WHERE previous_intro = x'21'::INT ), image_offset AS ( SELECT current AS image_offset FROM blocks WHERE intro = x'2C'::INT ), image_header AS ( SELECT q.*, l.*, l2.* FROM image_offset CROSS JOIN bin CROSS JOIN header h CROSS JOIN LATERAL ( SELECT GET_BYTE(buffer, image_offset + 9)::BIT(8) AS flags ) f CROSS JOIN LATERAL ( SELECT (GET_BYTE(buffer, image_offset + 2)::BIT(8) || GET_BYTE(buffer, image_offset + 1)::BIT(8))::BIT(16)::INT AS left, (GET_BYTE(buffer, image_offset + 4)::BIT(8) || GET_BYTE(buffer, image_offset + 3)::BIT(8))::BIT(16)::INT AS top, (GET_BYTE(buffer, image_offset + 6)::BIT(8) || GET_BYTE(buffer, image_offset + 5)::BIT(8))::BIT(16)::INT AS width, (GET_BYTE(buffer, image_offset + 8)::BIT(8) || GET_BYTE(buffer, image_offset + 7)::BIT(8))::BIT(16)::INT AS height, flags::BIT(1) = b'1' AS has_lct, (flags << 1)::BIT(1) = b'1' AS interlace, (flags << 2)::BIT(1) = b'1' AS sort ) q CROSS JOIN LATERAL ( SELECT CASE WHEN has_lct THEN 2 << (flags << 5)::BIT(3)::INT ELSE 0 END AS lct_size ) l CROSS JOIN LATERAL ( SELECT image_offset + 10 + lct_size AS image_data_offset ) l2 ), image_data AS ( SELECT GET_BYTE(buffer, image_data_offset) AS code_size, image_data_offset + 1 AS image_first_block_offset FROM image_header CROSS JOIN bin ), image_blocks AS ( SELECT image_first_block_offset AS block_offset, block_size, SUBSTR(buffer, image_first_block_offset + 2, block_size) AS block_data FROM image_data CROSS JOIN bin CROSS JOIN LATERAL ( SELECT GET_BYTE(buffer, image_first_block_offset) AS block_size ) l UNION ALL SELECT new_offset AS block_offset, new_block_size AS block_size, SUBSTR(buffer, new_offset + 2, new_block_size) AS block_data FROM image_blocks CROSS JOIN LATERAL ( SELECT block_offset + block_size + 1 AS new_offset ) no CROSS JOIN bin CROSS JOIN LATERAL ( SELECT GET_BYTE(buffer, new_offset) AS new_block_size ) nbs WHERE new_block_size > 0 ), lzw_data AS ( SELECT code_size, (1 << code_size) AS clear_code, (1 << code_size) + 1 AS eof_code, compressed FROM ( SELECT STRING_AGG(block_data, '' ORDER BY block_offset) compressed FROM image_blocks ) i CROSS JOIN image_data ), lzw_bits AS ( SELECT current_code_size, clear_code AS code, ARRAY[]::INT[] AS output_chunk, 0 AS next_bit_offset, NULL::HSTORE AS codes, 0 AS next_table_key, 0 AS next_index FROM lzw_data CROSS JOIN LATERAL ( SELECT code_size + 1 AS current_code_size ) cc UNION ALL SELECT next_code_size, code, output_chunk, bit_offset + current_code_size, new_codes AS codes, next_table_key, next_index FROM ( SELECT code AS previous_code, current_code_size, next_bit_offset AS bit_offset, codes, next_table_key AS current_table_key, next_index + COALESCE(ARRAY_UPPER(output_chunk, 1), 0) AS next_index, ld.* FROM lzw_bits CROSS JOIN ( SELECT code_size AS initial_code_size, clear_code, eof_code, compressed FROM lzw_data ) ld ) lb CROSS JOIN LATERAL ( SELECT bit_offset / 8 AS byte_offset ) bo CROSS JOIN LATERAL ( SELECT ( CASE WHEN byte_offset < LENGTH(compressed) - 2 THEN GET_BYTE(compressed, byte_offset + 2) ELSE 0 END::BIT(8) || CASE WHEN byte_offset < LENGTH(compressed) - 1 THEN GET_BYTE(compressed, byte_offset + 1) ELSE 0 END::BIT(8) || GET_BYTE(compressed, byte_offset)::BIT(8) )::BIT(24) AS cut ) cc CROSS JOIN LATERAL ( SELECT SUBSTRING(cut, 25 - current_code_size - bit_offset % 8, current_code_size)::INT AS code ) l CROSS JOIN LATERAL ( SELECT * FROM ( SELECT ARRAY[]::INT[] AS output_chunk, HSTORE(ARRAY[]::TEXT[][]) AS new_codes, eof_code + 1 AS next_table_key, initial_code_size + 1 AS next_code_size WHERE code = clear_code UNION ALL SELECT CASE WHEN code < clear_code THEN ARRAY ELSE (codes->(code::TEXT))::INT[] END AS output_chunk, codes AS new_codes, current_table_key AS next_table_key, current_code_size AS next_code_size WHERE previous_code = clear_code UNION ALL SELECT output_chunk, CASE current_table_key WHEN 4095 THEN codes ELSE codes || HSTORE(current_table_key::TEXT, next_table_chunk::TEXT) END AS new_codes, next_table_key, CASE next_table_key WHEN (1 << current_code_size) THEN current_code_size + 1 ELSE current_code_size END AS next_code_size FROM ( SELECT CASE WHEN previous_code < clear_code THEN ARRAY[previous_code] ELSE (codes->(previous_code::TEXT))::INT[] END AS previous_chunk, LEAST(current_table_key + 1, 4095) AS next_table_key, code < clear_code OR codes ? (code::TEXT) AS code_in_table ) pc CROSS JOIN LATERAL ( SELECT output_chunk, previous_chunk || output_chunk[1] AS next_table_chunk FROM ( SELECT CASE WHEN code < clear_code THEN ARRAY ELSE (codes->(code::TEXT))::INT[] END AS output_chunk ) q WHERE code_in_table UNION ALL SELECT output_chunk, output_chunk AS next_table_chunk FROM ( SELECT previous_chunk || previous_chunk[1] AS output_chunk ) q WHERE NOT code_in_table AND code <> eof_code ) q ) q LIMIT 1 ) ns WHERE previous_code IS DISTINCT FROM eof_code AND bit_offset < LENGTH(compressed) * 8 ), indices AS ( SELECT idx, next_index + position - 1 AS rn FROM lzw_bits CROSS JOIN LATERAL UNNEST(output_chunk) WITH ORDINALITY q (idx, position) ), pixels AS ( SELECT idx, rn % width AS x, rn / width AS y, luma FROM indices CROSS JOIN bin CROSS JOIN header CROSS JOIN LATERAL ( SELECT 255 max_value, 2.2 gamma, GET_BYTE(buffer, 13 + idx * 3 + 0) r, GET_BYTE(buffer, 13 + idx * 3 + 1) g, GET_BYTE(buffer, 13 + idx * 3 + 2) b, .2126 rw, .7152 gw, .0722 bw ) c CROSS JOIN LATERAL ( SELECT ((r::FLOAT / max_value) ^ gamma) * rw + ((g::FLOAT / max_value) ^ gamma) * gw + ((b::FLOAT / max_value) ^ gamma) * bw AS luma ) l ), picture AS ( SELECT STRING_AGG(SUBSTRING(palette, LEAST(FLOOR(COALESCE(luma, 0) * LENGTH(palette))::INT + 1, LENGTH(palette)), 1), '' ORDER BY x) FROM ( SELECT *, ' .*:o&8#' AS palette FROM header ) h CROSS JOIN LATERAL GENERATE_SERIES(0, width - 1) x CROSS JOIN LATERAL GENERATE_SERIES(0, height - 1) y LEFT JOIN pixels USING (x, y) GROUP BY y ORDER BY y ) SELECT * FROM picture;
string_agg |
---|
..... |
..... |
..... |
...#### |
...#### |
####... |
####... |
..... |
..... |
..... |
Here we are!
More pictures
So let's do a cat:
string_agg |
---|
.. . *#&&: ::: |
.. . :#&:: |
. . .. .. &#&:* . . |
.. . . . . &&&:. . .. . |
.. : . &#::. . . |
. . .. .&&:: . * |
. . * . . *&&&: |
* . . .. .. :&&:: . . .. |
. : . . . . . &&&:: . . .... ....*... .* . |
. . . . &#&:: . ..* ..... . ....* |
. . ...*. * .* * * ..&o:: ...... .. ... ... . ..... |
. . : . .. ***.*.*:*: .*... * . . .. . ... . .* * |
. . .. ...**.*.*.*..:: .... ..... ..... . |
.*..:. . .*... *.. . . . .. . . . .. |
. ... ......* .*. . .. . . ... . |
. . * .. *. .. . * .*.. .. |
. . . :* * .. . . |
* . . . . * . . . |
. . . .. . . * .. . |
. . .. *.. . . |
. . . . .* . * . . . . . . . . . |
.* . ...* . . . . . .. . . *......* ... *.. . . |
. * . . . . . . . . ... .*. . . . ... . . . . . |
. . . . * . . . .. . ** * ..* . .. . . . . . . . |
. .. . . . . . ....* .*... . .* . . . . . ...* . . |
. . * .. * . . . .. *.*..* . . .. .. .. . . |
.. .. *. .. .* *: **...*. .. . .* .:... . .. .... |
. . . . . . .* .. . . .. :. . . . . . |
. . . . . . ..* .... :* . .. . |
* . *. .. . .* . ....... . . . * |
. . . . . . ...* . . .....*. . . . |
. . .. . . ...* ..*.:*.*** ... |
. ** * * . . .o****.**. . .. |
. . . . . .. ..*... . ...***.*.*. .. .* |
* .. . ...*o*....** ... . |
. * . .**..***. .. * . |
.* ....*..... . *. |
. . . ......... . |
. . . .....* |
: . . . . .... . |
: . ... . . . .... |
.. . * . . . . |
.o ... *. . . . . |
..:: ... * .. . |
...*. * *. .. . . .* |
..:: ..... . |
.:.:: . ... .*. . . |
:..: . . .. .. |
.. .. . * .* . |
::... : ... . . |
::::.: * .: . .. .. . . |
.::::::*.* :&: . ... |
. :::::::*:: :&:& . . . . |
&::::&::*.* :::&& . .. . . |
. ::::::::&.*.: &:&&& . . . . |
&::::::::&.:* :&&&:& . . . . |
. &:::::*::&: :&&:&&& . |
. #::::*:*::& #:&&&:: . . . |
&::::::::&:# &&&&&&&: . |
&:.:.::&::::& .:&#&&: .&&&&:&&: |
::.:*::::::&:#&&&&&&:&##&:&&&::: . |
::....::.::::::&:&&&&&&&&&&&:&:: . . |
::...*.:.:::::::::&&&&&&&#&&&&:# . |
.:. .:.::.::::::::&&&:&&#&&&&::& |
.:. .:..::.::::&:.:&&&&&&#&#&&::. . |
.: .:...*.*.:::&*:&&&&&#&&8&&::: |
::. ..:.*.:.*:&&&#&:&#####&o: . |
.:. .:..:...::. :&&:&&&##:::#&&& |
.:. ...:.::::. &::&&#& :&#&# |
:.*.:.:.:::::: . :&::&#& .:#&# . |
...:.:.....::#:. .:::&&:.:.#&# |
*.:.:..:....::#:::. .:::&#&&:&#&# . |
...:.*...::::::&&:.*...:::&#&#&&# |
.:......:.:::::::&::. .o&:#&#&&# |
*...:...:::.::::::::&:.*:&&&&#&# . . |
.... ....*.. ....... ..:....:::.:....:::::&&::&&&&&&# . |
......* ..*... :.*.*. ...*... ..... . . ..... . :.....*....*.::::::&::*:&&&#&&# . . |
..**.... . ..... .*: .:**.....*.. ....... ... . . .... ...*......:.::::::&::::.:&&&&&: . |
. ....... . ... . .*...................... ... . .:.........*.::::&::&:&.&&&&# . |
.....***. ...*.* . . ... . .. . :...*.**.**.*..**....... :.... .:.*:*:.:.:::.:::::&&#:&&&#. . |
.... ..*.* ........ . .. :.. ..*.. ..* .............*.... ****. . .:.:..*.*::::::::&:#&&#&8#& : . |
***.**.*. ....... ...*...*. .. ..:.. ......*.......... . ..::::.:*::::::.:::&#&&#&& . |
*.* ..*:**.. .....*. . . ....:o&&&#o....:. . o:: ................ ....::::::*:::::::&&&#&&# . |
***.**:*. **::***. ...... .. ....::&&&&&o. ** . :::. ............. . ..... :*:*::::::::::::&&&&&&# . |
...*.. *.**:**. ....... **:::&&#&#. .. :..: .*......... .*:::: ...:::::::::::&&&&8#&# :.*.... . .. |
*:.*.**... *:.*:o&&&: ...* *..o ..:& * . .... .:..*:.. .. *...**::::&:&&&&&&&#&&: . :.*:. .*.. |
***.. o:..::&: ..*:*:.. *..*:*:&o&. .*. . . ...*.:**. *::::. ...:.::::&:&&&##8#&#: :. .. . |
. . ...*.*...&#..::..*:*:. *:*o ...*.:&&&: *:o:o:o::o::..*::o::::*:::*. * :....:::::&&&&##8##&&#&##. ....*:*::*... ..... |
. * ..... ..*.**::.*:::..*:*:o&&: .*::*.:*:*::*. .*..:& o .:*..:o:o*oo:o:o:*..*:::*::*..* ::..:::::&&:&#&8##&##&#&&# .:::. . .:.:.. ::::*::..:. ... |
.**. .*.*:*:*. .***:.****:*.:&.*# **:*.:*:**.:*o. **::&:.: *o:oo:o..::o::*::*:::*. .::::: :::::::&&&&:&&&##&##&&#&&&#& :::: :::: *::. ..... ..... |
.*.*.*.*. *.:*:*:. .*:*..*:o::&#. .*:::*:*..:*:. *..##&#. o:..::::o::*..::::.::::::*. : :::::&&&&&::&&#&#&#&##&&&&&#. .:&:. ::::. .::: .: . . .. . .....:*. |
*..**..***.*:*.. .*::*:*...:.:#&&&o.:*:. .::*:*:*: ::&&&#. :::o:o* .:::::o:. ::::::::*::: ::::&&&&&&&&&##&#&##&#&&&&###8 *#&. &&:: ::* . :. .. . .. |
* ***. .***..*:.:***. .*:*::*:&:..#...*::o*:* .*::. *.#&::: .oo:o:::o:oo&oo&o&ooooo:oo:o::: .:.:::&&:&:&&&&&&##&&&&&&&#&&&#: .&&:.::&:&. .:: ...**. .: . . ...... |
*.:.**:*.:. .:.*..:*:*:*:. .:*&&:&&:.:::*..::o:o:o. *:&:::& . . &o&o&&o&&&:**&o&:o::* .&:::::: ::::::&&&&&&&&#&&&&&&&&#&#&&#&## &&&: &:&* :: :: ..:.. .* . ..... .... |
.....:. .*.::**...****.:*::::*:&#&##*:::* .o:o::o:o.. .....*o##&&&........*. o&o&:::o. ::o:o::. .:::::.. :::.:::::&#&&&&&&&&&&&&&&#&&&&&&8& :&&& :&:* .:. ::: :. . ... .. . ... .*: |
....*.*..*:.. .:*:**:* .::*::::&&&##:oo:&o&:&:o:o&&........*:#&&#:. ........ .:::o&::..::::::::.o****... &:::::::o:&:#&&&&&#&&&&&&&&&&&&&&# .&&&. .8::. .:. : .:: :&.. *...... .... . |
*..*:.**:*:..::::::::o&o:o:o:o::#&:&&.:&::&::o .:o:....... .:8#&#&.........**::::::::::::::::::::***:**. ::.::::::&:&&&&&#&&&&&&&&:&:&&&&&&&& *&::. :::: :: .::. ...:#8&. *.*:....... . |
..*. ::::::::::::*:*:o::::&. ::::#:::& *:::::o*. *::. . .:#&&&:......... ::::::::::*::.:.:.*****..... ::::::::&&:&:&:&:&&:&:::::::&:&&:&:&&: .&&&...&:: .: ...*::: *#&#..:###&.:& ..... |
....:::...:::::::* .::::::: . :::#&&&#.*:::::*::*:::. *&&&&:.:.........:::.*.:.::**: :::*:*:. *. &:.....:::::::&:&::&&&:::&:&:&&&&&&:&& :&::. :::: .. .::..:.:* . *. . .#&.:#...&.... |
..* .*:. :.:*::*...:::::.:*.::*.:#&&&:::::::::*:*::. .:&&&&:.*....... ..:: .:*:*:*. .:**:.:. *..::..*...:.::.::::::::&&&&&::&&&&&#&:&&* ..* .:&: .&:: . ::&...:::. : ..:oo. *.:###&..:& ..*. |
*.. ...* ***.*.*.:.**.:*.:*.**.:*:&&&#:.:..:..:.*:.: *&&##&.:. . .. . .:*. .:**:.::.*:*:**:*:..*.:*............:::.:::&&&&#&&:::*. &&....* :o&. .::. . :::*..&::: ... o&&: .:#8#:.:&:...*.. |
... .......*.:.....*.****.*.**.:.*&&:&&.:: :.:.*.:* .&&&&:.*. .. . ..::*:*:***:*:*:*:*:.:**.*.:.........:*.*....*::&&::*. ........&& .... ..&&:... . ... .::: .:8:: . : .: .:&o.. :&#&. &::... .. |
... *........*..*..**....*.:..**. :&::&... .***:**.. :&&&&:..: . ..*:*:***.:*:*:*:.****.*................:...... . ............#*. .... .:&:. . .: .:::...:. . :. .8 .:8*.* :&o.:&&..*.*:* |
.. ......... ...*...* *..**.* o::*&..:*.:.:.*.:*. ..&&&8& :.*:.... ....*:*:*..**:*:*:* ::**.*.. ... ....*.... ..................:& .....* ::.#* ..*. .:::. . .* .: :&: ..&..* .:..#::.. . . |
.. ....... ...*.... *.*.****:.:#::&*.*.*.*.**:.:. :&##&: .:.:.*.:* *:.*:*:.. *:*:*:*...:.:*:*.......::. ......................&: . ... .*.* .. .::: .::::. . ....: .8&&. .*:..&: .:::..*.*... |
.. .........* .......:....**.**.**:&&&&:..:**:..*.**. .&&:#o .:*:*.... **:***:..o:*:*:*:o*:*:**.... o&o&o .......................*& .... ...*.*.:.*.:. ***. .:: .#::..:8::. . *..:. &&o&. .:..*&o . ..&:: |
.. .......................*.:.*.**.&&&&: *.**.:. .**. ::&:&: .*.:**:*:.*:.*.:*::*:*:*:*::*o*.. .... o&:&.................... ...o. . . .....*...*... &:* ::&* ::&& .:&::. o...: :8&#... *...:#8..... .::&.. |
. .*.................:.**** *.*.*:&&&& .**.:. .*:. *&::&:.:.****.:.:..****:*::::***... .......... o&:&o . . ..................* .. . .. * :.*.*.* . ::o ..: .:8&:. o&8..:#:&.* .:*... :8&8:..# .:8&8.**.... &::. |
. ..... ........ *...... .:.**:&::&....:.*:..:*. .&:&:: .:..***:*.:.:...::*.. . .................:*:& ....................: . .. . .. **.:... . :&. ::&#:..&&8. .#8& &:&:&. ::... &&8#. :8: .8&&:.....*...::*. .... |
. ..... ........ ...:..*......*.::..:..*..:*.:***. .:&:&: *.** :*****.. .................. .. . &::&... ............ ....:. .... . ....o#&:&* . :*. *.#8&o .&8#::.&&&. :&&&&& **.. .#8#:..#&&. . .#:& ......*...: ......... |
. *.... ........*...*....*..*.:.**::..&. .*.*.:.:.: *&:::& ***. *:*:. ................. . . .. ... *&:&: ... .......... ...:* . . . .. ..*...**. . :. .:.:&&##..:#8: :8&&..&&&&:: ...... :&&&..:8&8* 8&: .o....*...*. ....:: |
.................*....*.**......:&:::. . .::.:.** .::::: **.:.*:*:. *............ . .. ...... ... . . o&&& ...... ..... ....oo ... .. . .. ....... . . 8..*#&8:.&8&&. *8&8..:8&&&&. ...*.*:.&&#:..#&#&.. . .::.&&.....*........*::.. |
.*.................. :...**** *::&:: *.**.:.. *::*:o .*..:**: ..... . . . ..... .. . . .. ......*&&#.. .. ..... ... .:o.. . .. . . ......... .. :.#8#&. :&&: .&&#:..##&&&&. ..***..::&:o..:#&8*.* ...#&... .. ... .**:o& |
.. *....... ..*..... .**...*. ..:.... *.*.******* ::::&:.*.:.:.: ... . .. ... . . . .. ... .. . . ..&:#....... .... . :o*. .. .. .. .. ..*... . .&#&#. *#8&..&8#&. :#8#&8&& ..*****...:&&:..##8&. :o .#&&:. :.:::*.... |
....... .......*..:..:.:.*..*.***.::..:.*****. .::::& **.:.: .. ... . . . ..... ... ... ...... o:&& . . .. . .. oo:. . . . . .. .......... :#8: .#8#&.*&#&: .#8#:#&&&&.**... .o:..&#&#*..#& .#&#: ...::&:&:& |
............*........:.......*:.**.:*..:.*.:...:*. *::::: .:.:*: ... . . . .. . . ..... .. .. . ...*:&& ....... . .&::*... . ... .. . . *... 8& &##: .##8#..&##:.&&&&&o **..&8#&..&# . *:::o: |
................*..*...:...**..:.*. ::...*.. :** *::::&..:.**.:. . .... . ... .. ... .. ... .. .... ..o:#. . .. . .:o.:. . .. . ..*:o&&.. .. . :##&..:###:..###..&#&&&#: ....... ..:::#*..#: #:.. ...... |
*....... ...... ...*.*..* *.**.** ..:...*. ..:. *::.*:.:.*:.:.* ..... .. .. .. .... .. ... .. . . .::#..... . .o&:*.*:&o:. ...::o:&:&:&#:. :# .*##&:.*#&#&..##8::&&&&8*....... *&:&:.:#8 . *:o: . ... |
..... ........ .*...*. .**.**.*.***.***.:.*:.: *::..:.**..*..* ... .. .... ... . ... .. .. . ....::&: . . .o::o:::&:&&&#::&&::&::o:&::&# . ::#& &##&..:###*.:###..&#&&&&&&#. .&:...#. . . ..&. . .. . .::::* |
*.... ....... ...*....:.***.:..:..*.*.**..*.:.:. *.::::.*.:* *: ........ . .. . ... . .. .. ... .. .#:o.. .:8:.:&:&&:::#8#&:::::::&::&:&:&. :.###. ##: :###:..###:.:##&&8&&&&&. .:..#* .:: #:* ... .. . .::&: |
. ....... .......*......**...*.*..:..:.***.*.*.*.*.*. ..::::..*.* ... .... . .... ... . .... . .. . . .. o&& .&&:*::::&&&&:#::.:::::::&:&&o:*:. .&:::. .#..8###.:#..#&#&&&* .:. . .::& . . .... .. ::#: |
. *.................*....**.*.*.. **..:.*. *.:.*.* .**:::.**.*..*.* ... ... . .. ... . . ..... ..... ...#o..o:*....::&&&&:....::&::&:*... ... .::::..# ..:###:.####:.:###:&&&8&...&:. *:#.. . . .. |
............. .......* :...*.*. ..:..*.. ..:..**.: *...*..*..**.*** ... . .... . . .. .. . . . . ..**:o..*.......o*&&:..:::::*:::& .. ....* .::&: .:#: .###&..#..&##:..&##.. &&::. ::8&.:. .. . |
* .. ........ ........ ..*...*. ....***.:.**..*.*:* .:.:.*.*.*..*... ....... . ............ ..*oo::o&&&&:. :oo***.*.*:::o*. ..:&* .... . .:. .::& ..8##. .#8#..:###*.:###..&##:..:#8&: .:. .:*: *:8#:. .. . |
... ...... ..............*....*.:.*.**.**.*.*..*. .:.:.* *.*..**. . .... . ..... .. . ..*:&o::&::o*::::: ..::::::..... .. . .&*. .. .. .... ..:&. &8&&. :#:..###& .###:.:###...###8 .:::: :.*. &8&. . .. . |
. ......................*...*.***.*...*.**.:.**. .*.: .:..:. .*.*..*.. ........ . . ..*o&&&&&::.::o::::o.*: * .:... * ...... .** . . .. .. .. .::. :###... o. ####:&:###..###:.*#8##...:::: *.: .&. . . |
...................................**. ****.** *.: .:.:.:*.::.***.*: ... . .*:o&&&&&&::&:&:*:&:o::o.*:o ** .*. . . ..*:. . . .. .....: .::.:*#8#:..&* .:###:..###:.:#8#...###:.*#&&&: ::#*. . |
.. ..... ......... ........ .:.:.**. ..:.**.* *..* .**.:.*:.:.::**.: . ...:o:&&::&&&::&:**:&:&::o:::::.:&o .o ..::::*.:o . . . .. . . . ..:. .###&. :#& *###:..###&..&##:.:#8#& ..#8#&..&#&. . |
.. ..... ........ .....*.. .*...**.*.:.:.:..:.**.** .*:..:.*:.:...***. *&&:&:&:&:&&&:*::&**::&oo**.. .*::&o *&.. ..:*.*. . . . . . .. .........:##8...#&#: :##..:###:.:###..#...&&##..:: *. |
.. ..... ...........*.*..*.*.****..*:.******..**... .. *:.::*** .**.* .&:&&:::&&&&&&:&.::&::o:. ....::::#* *::.* . . . . . . . .. ........ :: .&##:.. #:..###&..###:.:###:..&###&.. :...# . |
. ...... .................*..*..*..:..:.:...***.**.. . ::.**.*. .*.***&:.&#:*:o&.:::&::*&::. . ........:&:#* .&o *... . .. .. . . . . . . .. ..... .&:##..:#&#.:8. *.&###*.:###..:##&...###: *#&...: : |
. *.. ........ ............**.** :..:..*. ...*..* .*.:*.:.*:.*.*:.:.:&..&&::&&::.:&&:o:. .... .....o.&&&#. .&& ..:o&o.. . . . . . ... . . ..... * :..*###. .#8 .&###:..#8#:..###...#8. ##&...&* .* . |
.. .. ....... .......* *....... .**.*.*. ..**.*..* .:*:.*.:.*****..:..&o*&&:.. .::&o:. .. . .. ...: o:&&# .:& :. :::&:* . . . . .. . . . .. . .... &:#:..#8#&..:##o #..:###.*:##&.:. .*..&8#:.&:&: .. |
.. ...... . ....... .*....**..*...*.**...**.:..: ..*.:...*******: **&o. .... o:. ..... ... .....o &&&&o ..:&*o&.:&:::&... . . . . . ... ..... . ::&#..&##&..*###& ##..*###:..###* .o##:.*#8&...&:* ... |
.. . . ..... .......*.....*........**..*.*.:..*.*.* *:..: .:**.**. .:* .. ... .. . ...... ....*..:#&#*. :#:&:&::&:&:: .. . .. . . .. .. . . .* .*&8*.*###.. 8##&.& .&..&###..:#: .####&.:#8#:..:::. . . |
. ...... ....................*...:.....:..*.... .:.* ...:.. .****.*:..** .... ..... ... . .....& :& . :&::&::&::::&* .. . . . .. . .... ... :.&:.:&##:.:&###...& ..&###:.&: .*.:####..:##&...::: .. . |
* ... ..... .............. *.*..... .***.:.. .** :*..:*.:**.:.*..*.. .. .. . .. . .... ....*& ::&&& . ::&:::o:::&::: .. .. ... ..... . .. *...&*.:##8.::###:..&#* :###&. .8&..#&##:..#8#:..::: |
. ..... ........ *....... ..*.*.*. ..*..*.**.:.** .*.:..:..***.*..*..* .... .. . ... .. .....#: :&&&: . *::&:&o:&:&:&: . ... ... .. ...... .... .&&&###:..&##&..&### .##: :8##.*&###&. &###:..o:. |
* .. . .. ... .........*...*..*.**.***.*.*..*:. .*.**.*..: .:.*... .. . . . . .. . . ...*#*.&:&&.. .:&:&::o:&::::o: ..... .. .... . ... . ...:###&&####:*.###&. . .**:###:..####:..#&#:..::* |
. ... . .. ...................*..*..*..**..*.:.**.. .. .**.*.*. ......* .... .. .. ... . ... &#..:&&& . . &:.o&::::&o&:&o.. ........ ... . . ::&&..:###&#####... *#:..###&..:##8&..&##&...&: . |
. ...... . .... ...........*......*.*.**.. :.....*. ....**.*.......... ... . . .. . . ....:#.::&&: . :: ::&:&::&::::& .. .. . . . . .::: ..&8&..&##&&&#.... *###..&8##:..####:..###:..::. . |
. ... ........ ........ .......* *.....:. ..*.*... *............*...... .... .. . .. . ... oo#.*:&# .. :::.:oo:&o:&. &::. . . : .::. .:::..:&:: .#8 ..***... .&###.*.###&*.#:.:##8&..::: . |
. ... . .. ........ ........ ........ ........: *.....*........ ..* .. . . . . . . ... &&#**:&o . .:..*:&o::&:&.:::. .. :::. .:::. :& .*.* .&::###&..&###:..####..&&##:..::. |
. . . . . ...... ............................. ....* ........ ... ... . . .. . .....#&#*.:#. .. .:.:..&:o:oo:&:&:. . :::... o#&&####:::###&.*##8#:..###&..::: . . ... . |
. . ... .. .................................. ... ..... ..... *... .. . . . .. .#&8:*o: . .. :..: :::&::&:o&:*. .. ...... .& .:8&#:&####&:.####..:###:..::: . .. . . |
. . . . ...... .. .. .. . . . . ....... ... .... . . .. .....#&&&** . . :. . &::&:.:::&:& .... ......: ....... ..*::: .:#8#:&####*.*###&.*::: . . .. ....... . |
. . . . ... . . ... ... . . . . *. . . . . . .. o.. .. . o&::. &&:o:: ........ ......* *.**.......... . ::: .::::..:8&8::####&*.o:: . . ....... .. |
. ................. .... . .. . .. . :::&::::&.:&: .......*......*.*....:*. .*.*............ .::. .::: ..:&::.:&&&::.:: .. . ............ |
And a pig!
string_agg |
---|
.##* |
##&# |
&&o# |
:& |
&# |
###& |
#&&# |
.&&: |
. |
:##: |
#&#& |
&&o# |
:: |
## |
##&# |
#&&# |
&&. |
*. |
:##& |
#&&& |
o&&& |
** |
## |
###& |
#&o# |
&& |
*: |
&##& |
#&&& |
:&&& |
.. |
##. |
##&# |
#&&# |
&& |
:: |
###& |
#&o# |
*#&& |
. |
.##. |
##&# |
&&o# |
&& |
&# |
##&# |
#&o# |
.#&: |
:##: |
##&# |
o&o# |
:o |
## |
###& |
##o# |
&&. |
.. |
&##: |
#&&# |
o&&# |
*: |
## |
#### |
#&:# |
&&* |
.. |
:##: |
#o&# |
o#o# |
*: |
## |
##&# |
#&o# |
&&. |
*. |
&##& |
#&&& |
:&&& |
.. |
##. |
##&# |
#&&# |
#o |
:o |
###& |
#&o# |
:&&& |
. |
.##. |
##&# |
&&o# |
o: |
. |
*o&&&#&#o: |
.&&##&&o&##&#: |
o&##. . *o#&# |
:##* .. .#&#. |
&&&* . ..o#& |
:o#** *.o## |
:#:: :*&&* |
:&#: *:#& |
:#:* .*&#. |
.o#: *o#: |
::&: *:#o |
:o#* .*&& |
*o&* :&o |
*o&* .*#: |
*:#: .*#* |
*.&o *oo: |
*&&. *&:. |
**&o *oo: |
..&&o .*#** |
**o&* .*&o. |
.*&&o *:&&. |
.*...**o*o:oo:o:*****.. |
.:o***:*::o::&oo&o&o&&&&o::::*:* |
***.****:*::o&o&&o&&&&o&&o&o:**.*o |
**.**:.*:*ooo&&&&:&o&&&o&:&*o:***: |
:***.o***:::&:&&::&&&&o&&&::&:*:** |
*.*.*o.**::o&&o&&*#o&&&&&o&*o:***:. |
*****o***::oo&&&::&&&&o&&&::::*:*.* |
.*:...&.*:::o&&&&&:&&&&&o&oo::o*:**: |
*..*:.&*:**::oo&&:*&&&&&&&&o:oo:.*:: |
:**.:.&..:::o&&&&&:&&&&&&&:::::::*.o |
*.:*.*o.***:ooo&&:*&&o&&&o&:*oo***:: |
:..:..&.**:::&o&#:*&&&&&&&::::::*:.* |
.****.&*.*.:oo&o&:.&&&&&&o::*:o****: |
:..:.*..***:::oo&..oo&o&o::*:.::**.: |
.**.** .****:o&&.. .o&&o&:o:. :**.*: |
..**. .....* ... ****... :***. |
. ........ .. . ... |
. : . ................. |
.. 8 ...........*........o:.. |
...: o ....................&**. |
.:..:*: . . . . . |
. o : o .* :&..:. .. . .. ... .. . ... |
:*. o :.8 : ::.o*:.#. .... .... .. . . :*:. .* |
.o:* .:: :.&..o .*:o . ...... ..:..... . .o. .*. |
. .8**... o&::* ..o#&.. ........#:.&# .. ..... . .. . .. |
*. . ..o&: o::&:*:.:#........ ..o:**...... .... . . .. . |
.. .::.***. :o*o:::..................&&&o#8...... ... .. ... . |
**.. . .o8:*. &o.o&....:&&...........##&.&:*....... ..... .... . . . |
*. . .&:. & .::* #:..:ooo&#.............:&.#8............. .. ... . . * |
. .. . . .*.**# & o. .#.# # .o...............&#..::................ . .. . ..o: |
. .* .. . .#*8.:.8.8..:.& #.##..&#................................. ...... .. . |
:* . . ...8* &.&:&.:. o#.o*.:.............................. ....... ... . .. .. |
*. o:. . . .. ... ..&&8&.8#.:.##.:#&#.................................... ... .... * . |
:.. . . . . ........ &.*.*o:#*....... .........oo&#*............................ ....8o. . |
.. . .. . . .. ...*#.o&.*..................& # ##:............................ ....8: *: |
. . .. ..:.... ......:..8....................#o&...o..##o#......................... ...*#. : |
. . .. .. *...... ......##.....................o&....&..&.::#.......................... . .o* :*:o |
.. . ..*..... ..... ............8.....*....:#:..o:..&.#.....#...................... ....*:&o:o:::o. |
. . ....: ...*..*................:#........*o#.oo.*o..#.&:...o#.........................:#&ooo&&.o . |
. .. .. ..*...:.................##.&#........:&.o..#..#.o.#..*.*#..........................*#o:..8..: .# |
. . . *.*..::*..................&&:&.*##o....:&.....#.o:&#.oo##.*................................8..# . .* |
. . . ....::.::...*...............:###o&*.....o.o....*:o...&*.....:............................ ..&& .. # |
. . . . .**:.:..:.::**............*##:#&:.........#.*.#o#&*...:#:#&&:#&...........o...................# ...*&&* |
.. . .. .. .....**.&:...:.............#&*.o*o#o.......o#.:....#...:.:....*#...........o#....&.............#.. .**.&* |
.o:o . . .. ....*:o....*.................&#..##...........o&##.#oo#*#......#.....**......*..&.................. . &.& . |
.::*:8 . .. . .....*...o....*................##............o&&.:..#.&.#.:&:...#&......oo...&&.o.*................. ...&& & : |
:*:*:o . . . ...*.......*.o.:.................&.............#o.....#.&o*#..&*.o*:*......*:*:.o..#&............... ..&#&&&: . . |
.o::o*:..# . . . . ...:....:*..................................:o:...o&.#...#..::#:o.........:#..o.:..*................#8:.:o& . |
o.::.*.o:. . . .. .. ..........................................8*o.*.o*:*o...&..#...............*oo..&.::#..................#.#. . |
.: 88 .8 . . ... .......:........................................&#.#.:..o.....#*.............&:*.&.&o:&.:..*............ .. #*.# # |
.o **o &** . . .. . .. .................*................. ......::o###.*&...oo#...........:o&.&*:*#::..*...... ........... .*8 .#. .. |
*: o&.o . . . :&. :................:..&.................................###.#.o:...........*.*.#..&.**o&o.....................#.. #. o |
.o.&:8* . . . ..#. &# . ..... .........:....................................oo#&#....*............*&o.o..:*.....................#&.. . |
.ooo..8.. . . .:.#8:o&.. .......*...&.*..o*......*..........................&.o....................&.:#.o.#*....................:. .. |
. :o:&88: . ..*#**8&&8#:..........*::.:.8.......................... ..............................::*.&.o....&...................... ... |
8&::8 ** . *. & o#:&&8&#... .........o:......................... *: .............................*o....:....................... .... .. . |
8. &8 ... .*# #.#..#&o#&&...........#.*..&:..........*......... &&8: .............................#....*o.......................... ... . |
. * .o&.8: .&o*.** o#&8#&&:.........*..&.8...*................. &&o. .. ..............*...................:*............:.............. ... |
* *.&o&&8. .:.&8.##:#&8&&..............*..................... &8oo& .......................................*............*..*........ .... . .. |
* .. :::o: . .&: #8&&&&8###8:... .........&...:................. .&oo&o& ... .........*....................................o.o*............ . . . . |
. * &&&& . &* .&&&&8#&...#&.............*.................... &o&:&oo& ....................................................***............... .. . |
* *:. 8:o.8. .#&*#o&&8o .##8&# ......................... ...... &oo:o:o:& ....... ...........................................*:*o*.*.............. .. |
*: 8 ..:8.. .# 8&&&&&8# ##.:#8&......................... ...... #oo:ooo:&o& .............. ...................... .............*.*:........... .. . . |
**o : . .&: #&&&o&#&#*#&*.&............................ ...... #o&*oo&o&ooo# *.......*........ . ................. ........ ...o.:.o.............. . . |
*:***:. .&&:: ..&o&#&..o .:8#8 .... . ................... ... .. &o:o.o&ooo&o:o# ....... o8&&88 ........... ...............*.*.............. ... . . . |
****:*. :.&:&8.#&o&8# &# ..:.##*. .......................... .. &oo& 8o:&:o&o&& &:&&88&: .*. #oooo&o&&& ...... ............................... ... . . . |
**.:*&.&8.#:&.8:#&. &##o8#. . ......*........................ . .o&o# #&&o&oo&. &. .&oo&o&8. . #ooo&o&oooo&&. .............. ....................... .... .. |
.* 8*:::::&:&:8&:.# #o .#.:..... ....:.*..................... .. 8oo: .&:& ::&: .&o&o# &&: &oo&&o&:&&o&:o&&&: .............................. ... . |
. o:&ooo:&&& & #&. #.. . .. ...*.*..**.................... .. &:&o: &ooo # &.o :##.&.. & &..&o&o:o&ooo&oo&o&oo&8&8&&&8# .............................. . . . |
8..: . :::&&:.# . . . *. .... ... ...*o........................ :oo&: ooo:oo :&o ####..###.:o&:o&o&oo&o&o& *.:o&o:oo&oo#:& ................................. . . . |
: . : &::&&:&:#& . .. ... ... ....:.: o....................... &&o: oo::ooo. & ## .# ####.&o&o&:&o&oo&:: .:::. :.&:&8: .............................. . .. . . |
. .&:&:&o&&&.. . .*. . ...... ...*.:..................... .. #o &:oooo:oo: :# & #####:o:o&o&o&o&:&& #ooooo&&oo& ...............*...................... . . |
:::&:&&&&& ## .:: *. . . . ................................ . &.ooooo:oooo& : :## #:&o&:o&o:&oo&..o&&:&&&o&o& ... ..............*..**.............. . . . |
.: &::&:# &&&:**.###8.......... ..:..*........................ :oooo:*ooooo:oooo ## : :.&o&o&o&&o&: &o:o&o:oo&& .... ............&o.**.:.*.........*.*.. ... : |
& o.&:.&::&*.o&&&&8* .&##. . . ................................ &:oo& oo *oo:ooo :.: &:o&o&o&o:&o# #:o&o&o&oo*...................**.&.*.*..........*.*. . . . :. |
.:o.::8.8.8:&#*8:oo&&. &#* . .#.. . ...................... .... &:o:o. &*& oooo:o.. &. oo&:o&o&:&& # &o&o:o&:&& ..................o..**.**:...........****.. . . |
.. .:o: &:.::: .8o&:8. # ##:##.. ..... ....................... o:o:oo o.:o* oo:ooo:.# .#.&&o&:&o&ooo& &o&o&o&o&o& ...................*:.*o..**o..........*..... . . |
o .o:.. &&& #8:o&8o##o#:..#o::.......... ............... . o:o::o:&o:.o& &oo:ooo.&o& #oo&oo&:&o&&& &#oo&:&:oo:..................:o.***::..o*............* . . . . * |
...o:*8 &&. .# .#:ooo&oo.& & .... . ....................... .. &*oo:oo:ooo:oo*o:ooooo.o&o8 &:&:o&:&oo8 # &:&oo&o& ...................&::*o:oo:..:o.......*...... . & |
.**o* . ..&o.* *#.&ooo&# &8# #: ....... ..................... . oo::o:oo:oooo:ooo:o:::.&o&o8::&o&o&o&o&:.o&# &o&:&:& ...................*..o***&o....o.......... . . . :.: |
*:* 8 .&:o*8& . #:. 8o&&o# .8&:o#.. .. .......................... :ooo:ooo:o:ooo:oooo& &&oo&o&oo:&&o&o&: &&oo&o:&oo&:.................*.&...*::..*o...:......... ...... & . |
.** .. ::o: ..*8 :&oo&&o#o:oo&:. ............................... .. .oooo::ooo:oo :oo&:&o&:# oo&:&o:&o&o:&oo&:o& ....................:*:.o&.o:*.:............. . . . |
.**: . *:o:o* & 8&:*8#oo&oooo:o ..... ... ..:.................... . *&. ooooo: oo&o&o&o&o& *&oo&o&&o:&&o&oo&&& ...................::..**o::o:&.o......... .*... . . . |
*..**. 8:::oo8* *.o8 & .#&o:&::o*. . ..... ..#.................... :&&# oo&&:o& ..8&&oo&o&o&& .8:&:&o&:&o&:o&o&oo# ........... ......*.....::**:...:*.........:&:. . . . . |
*. : .*..oo* o:. .:* 8:*.8o&oo::& ... ..:*..:#:................... &ooo& &:oooo&o#o8o&oo:&o&:o# :&oo&o:oo&:&o&:&oo&:....... ..........*...*o:. **o.**......... .#..8* .. .. |
.... : .:8 8 . . 8..8:8..&::::8.... . ..##.*&....................*o:oooo #oo:ooo&o&o&o&o&:o&& 8&o&o&8 #oo&o&o&:&& .. ....................:o..:.o*..:..........**..o.. . .. |
.. :. 8o* :.& . . .# 8#.8:::o.. . .....#8.o*.#8#............. . :o:oo:&. &:&:&o:oo&:o&:&o#o &o:&:&o& :#8&o&&&: ... ......................*...*.:..........:.*. .. 8:.. . |
. * 8:&:*o8:. . . .*oo.8.o::#*. ... .. ...8#::#&.............. . ::o:&o:o #oo:o&o:&o&o&&: :&o&o&o:&& ... ..........*.................o..*&o.o:......::&:.o..&...* . |
*. o .:o:o :o . . :.8:o*o8.. .. .....**o&:o................. .. &:ooo:&o: .&#&&&&&&&:: .. &&o&o&o&o# o8 *................................:o ...........*** o.*.* ...: |
* ::o*oo: . . .o: o::* . .... .. &##.&:&................. .. &o::oo:o8 ........ #oo&o&oo&o ## *...... .....................................*# ..:.:&.. * |
o oo 8 . . . *: .8... ... ... **:..o.*#8............ . .. :oo&ooo:# ....... ..........&o:&o:o&&o& ##o ..... ........................*..............#.......*... *. |
: &**:..:. . . . .. . ... ... .. ...#8..#8............. ..... 8:oo:ooo ...... .......... 8o&o&&oo&o# *### ..............................................8*...:&.:.*:*. . |
:*. o:.o o* . . . . .. . ... ........8&................. .... &o:&oo# . .............. 8o&o&oo&o&o: o###...............................................o.**::. .:. . |
.* :.:o 8 *o* . . . . . . .*.. . . . ...#. ... .................. 8 &ooo:o:. ............. #o&o&:&:&o&8 #### ..............................................o.:..o..*.:*:. |
. *.o:*.*: * . . . . . .... ....... ........................... # &:o&:o: . ....... . &&:&o&o&o&:8 #### .: .... . ..........................................::..*. . * |
*****::.* . . .. . .....: .. .. ..... ... ................... : *# #o:o:o& ....... o&o&oo&o&o&&& *#### :&o: ... ........................................oo:..o**:. .. |
o:.**.**. . . . . *...:. ... .. ......... ............... :&o8 &* &:o&oo&& .:&&o&:oo&o:&8: ##### :ooo&o& ..........................................o ..*..* :. : |
...*.**o . . . ....... . .. .. .. ........... ....... &o:o:& &*:oo:o:oo::&&&o&oo&oo&:&&# *###.## :oo&&ooo&& .........................................::..:.o.*.. .. |
:*:. . . . . . ..* . :. .. ........... ....... ... .. &o:o:oo: & o:oo&:ooooo:oo:o&o:&o& .##:.o&* &o&o:o&o:oo# ..................................... .o. .*.: .. *: |
. . . . .:... .. .. . . .. .... .. ..... .. #:oo:o::oo # 8:ooo:o&oo:&o&o&oo&8 ##*.##8 #o&:o:o&o&o:o&* .......................................#..*.:. * : . |
. . . . . .. . . . ...................... ... ooo:oo:oo:oo # :&:o&:oo:o&o:o:&& ##o.##o 8&oo:oo&oo:o:&ooo* .*................................. ..**:: . . : . |
. . . . ...*. ... . . .. . ............ .. &::o:o:o:oo:o& # #o:o:oo:oo:8&. *##.#:*. :oooo&oo&o:&oo&:o&o&. ..........................................:: *o . |
:.. . . . . .. . . ... .... .. ......... 8o:oo:o:oo:o:o:& o# &&&&&&&#: ### *:* .&o&&:oo&o:&o:o:oo:oo:8 ..........................#........ ..... o.:. . |
: 8. . . .. . . ..... . ... ... ...... ...... :o:o::o:o::o:o:oo& #. o ##: :&oo&oo:&:oooo:&o:&o&ooo:& ... .................#...&*......... .... .:.. . . |
. :: * . . . . . . #*:o . .......... ....... . o:o*o:oo:oo:o:o:o:8. ##########.# .&&oo&ooo:ooo:&:&o:o&o:o::ooo. ... ................:o*::.... ...... . .. . . . : . |
. ..**.#: . . . . . .:.:** .. .. .. .... ... .. oo:o:o:o:o:o:oo:oo:o& ##########. o&ooo:oo:&o&oo&oo:oo&oo:&oo&:o&o& ....................o..*....... ..... . . . . .. |
. . . ..*.. . . .#* . *:.*.. . .. ...... . . .. #:o:o:o*o:o:o:o:o::o:oo ##8&### *#oo:o&oo&ooo:oo&o:&o:ooo:oo:oo:::o# ..*.................*oo*o&*..... . ..... . . .. |
.. .o.** . . ..*... 8*... . .. .. .. . ... .. &:o:::o:o:o:o:oo:ooo:o:& *##:.# o&o::oo:o:o:&oo&& o&oo:o:&oo&oo:ooooo: .................&.o.:.o... ....... . . . :. |
. *. . o. . .8.*.o ....:.. . .. ............ .o:::o:o*o:o:o:o:o::o:oo& 8#&o# # &o:oooo&o:&o:o:oo .o:oo&:oo:oo:o&o&::o& ................&::.:**......... .. ... . ... . . |
. . . .: :. . . ......* .&*. ..:# . . .. .. ... oo::o:::o:o:o:o:o:oooo:o:o& ##** o:o:o:o&oo&&&&&&# #oo:oo:&o:oo::o:o:o:o:.. .. ...........*..*.o& .... ..... .. . . . . |
: . . o..:.:.. .# **.. ... . .... .o::o:*o:o:oo*o:oooo::o:o:o:o# #&&o&&&&*. :&&:oo:o:o:&o:o:ooo& ..................oo.o:... ... . .. .. . . |
. : . . . . *:.:.* *:.:.** ... ...... .&*oo::::o:o.:o::: .:oo:ooo:o:o #. o###########. .&&o:&o:o:o:o&:o:. . ................o:...:.... ...... . . . . |
. . . *..o**.:.*.& .. ..... &*o:o::o*:o:.oo:oo&o::o:o::o:o.o&8o *#o&###&#oo##&##&#######* &oo&:oo&o:oo:o: . ................o*............ . . . . . |
. . . *.o...*:: *:*.:.*.. .... ... &::*:::*o:oo &::oo ...oo:oo:oo 8##### *o&o:o###o#8#o#o#o###o &: :oo:o:oo:& .. ..................... .. .. .. ... . . . |
. *: * :.*.*... .. .. . .. . . ::*o:o:o::o.. oo:o:o* ::&o:o #&##&###&##o#o####&##o#&##### ..o:o:oo:o& .................... ....... .. ... .. . . |
. . . ..*.*.:..::**. . . .. . ... oo:*:*o::::& o&. ...... :&: *#o&8##o8####&:oo:*###o##&8#o#### #o:o:oo::o:: ........................ ...... .. . . . |
*. . . *. .. *:..*: & .. ... . .. o*::*o*::*o::..* ............. .o ##::o##o*. .:*####o#&#&8&# o:o:o:oooooo ...................... .... . .... .. . |
. .* . . o*.....:. *.o..... .. ... o:*o::::*o:*o: ................. : ....... *.#8&##8#o# o:o:o:::. : ................. ...... ..... . . ..8: ... |
. . .. * . . . ....* .: :*. ... . . . o:::*o:*o.o& ...................... :: .......... .. .8&8#o#&#&###.oo.*:ooo.: ................. . .. ..... ... . . o:8o: . |
. . ** . ..*. ...*.& . . . .. :*:*o:.o oo ......................... .......... .. . :##&#&8#8&&8# & &:o:o& ........................ ...... ... :.:.: .o:: |
.. . . . . . . ..o.* . :... . .... . &.o:::..&. ........................................ .... .#&8&8&#&# :.oo:o:& ......................... . . . . .8.**: : . |
. . . .. .: .:... . ..o. . . . ... *:*:*:o: ............................................. *#&##o#88&##&o o:o:o:& ............. ... . .. ........ .. *:..::*.: |
. . . .* . . ****. . . . . .. .. ::.o*o:o ............................................... :##o#o#&#o#o## &o:o:oo . ......................:: ... . .. ..*:.::.:.. |
. . .....: . * . .. . . . .. . & o:**o................................................ .8##&#o#&#o# o:o:o:o .................. ... &o#..8. .. . * # *. *.o |
. . . . . . . . . .. . ... o:*o:o ............................................... *#o8&8&##&8# :o*o:o& ............. .. ......:::o #.. .. . . .:o..: |
..*.. ...*.* . . . . . . . . . .. . o:*:**: ............................................... ###&8&#o#&8&8&#*.o:o:o: ............ . ..... .:::*#.: ... . :. .:o: . |
. . : . .. . . . . . . . . o*:::o ................................................ #&&o#o#&#o#&8&#o o:o:o ........... ........ .*o&.# :#. .. ..# .: o |
.. * .* : . . . . . . . . .. o:*:*:o ............................................... o#8#&8&#o#&8o#&8# oo::& ............... ... &.8.8.*# .. * .. .o#o. o : |
. . .. : * . . . . .. . . .. o:**o*o ............................................ .. ##o&#&#o#o#&8&#&# o:o& ..... ... ... ..... . #..oo &8. .o. . :..::::8 |
. . * . * . . . . .. . . . . .*o**:*o ................................................#o#8&8&#&8&#&8o## &oo ................ ....#:..&:# o .:: . . :o8 o .. |
. . . .... . . . . . . .. o**::*o:* ........................................ .. . #o#o#&#o8&#o#o#&8# o .. ..... .. .. .. .::::#&o.o& .8o ...:. : : .. |
. . . * .. . . . . . . . . :**:***:& ........................................ . .. ##&#o8&#o#o#o#&8&# &: ..... .............#::. o:.::&::o*.& .:88o 8 . . |
.*. . . . . . . . . .::**::**& ....................................... . ...#o8o#&8&#o#&8&8&8# oo ... ......... ... :&::#:.#8:&*:&. .: # o:.8& : * |
.. .* . . . . . . . o*::*::**: .......... ........................... . *##&8&8&8&8&8&8&8&# . o:. .. .... . ... .. .#:.8&.8& #. .:.. *oo * ... . |
* *. . . . . . . .. :**:***:*oo ....... :#&. ... :o. ... . . ... :. . *8&#o#o&#o&8&&#o&8# :oo. .. . . ..... .... .:..#..8 o8.:8 .*88 #. . .... |
... . . :o# . . . . . .. o*:*:::***o. ..... #88##....:####* . ####: . ##8## .. #o#o#o#o##o#o8&&&o o:o. .. ... .. .. ..:**8. .8&8#.#.... .:* *# . **. |
:* : . . . .. . ::**:**o:*:o ....*8& *8# ..#8..8# . .*8#.. #8#.&*o.. #&&o#&o#&o&8&o#o# . o:o ... . . ...... .....8.#* . #*# o*.8* ..o&. o.: * |
.o* # ** *o . . .. . . o:***:*:::o ...:oo .## .#8. ## ....#8 ..##. .:& . o&o#o8&&8&8&# .. o:o ... ..... . ... . .. . .. o8 .#..8oo. .8: .: o* .. |
o .: #o ..:....: . . . .. . .o::o*ooo ....#8.#o& *8& . #8. . #8* .:8* o:o .. #o&o#o&#o&#o&#* . *::o ...... ...... .... .. . .#o*. 8o#.8. #ooo :*o *: |
. * :. .*: *: . . . . . .. ..... .o8& ## ..&8 ...#8 ..:&# *&# ...*&8#o&8o#oo##* .. &oo ... .... .. . . . .. . :&o8. :..8 .8:88 .*8 .:.: |
. . .:..*: o o :. ::8: . . . .. . : .......... 8#& ## . &*& . o:: .. #ooo&* .... 8&o#o#&&o##. ... o:. .... . .. .... .... .. . .8*.*8:8.8: 8.&. :. * |
. .. . : o o : o:. o . . . ... :*o .......... 88& ...o: . #8 .. ## ... :o:8 ...... #&o&oo#8# .... &o. ... .. ..... . .. .... . ...:#. 8:.o8 .8 &: o:o . * |
. . ..o :..... :o. # . . . . .*o ......... #o: .o# #oo ..*oo ..... ## .... .. #&8&o#& .. . .& .. ... ... . .. .. . . .. . .. . #: : *oo8 .o :.. |
. .:.*o.. 8 * 8 #: . . . . . .**o ....... #oo#8## #:##o# .. ## ..... #&: .... . .. #o##. .. .. # . .. ...... .. .. . . ... . .. . .8..oo:: ... : |
. . o : o 8 *o.o . . . :**. ....... ::o&&: ###o .. #8 ..... ## ...... . .. #* .. .. : ... ... . . .. .. . . .. . . . . . ::8oo*:o:8 |
.. :8 *o :* 8: 8 .&&. . . . o**o ....... .. .... ...... . ....... . . ... ... ... .. .. .. . . .... . .. . . # . :. 8o.&:oooo& |
. ... * 8:. *8 o* .o 8. :::*: o* . .. .*: & ................................... .................... ... . .. . ... . ... . . . . &:. .#..8* : o::o ..: . |
...... .* *8. ::.:*o 8 8. o # . . .. o** o ...................................................... ... .. .. . .. . ..... .. 8#& # :.:88 8o8. #.: & . |
.. . * . .o 8:.o.**: :. .*.: . . .*** o .................................................... .oooo ... .. . ... . .. *8#:# ..8*..& 8o.:o &: &..*o*. * |
. . o..*8. .*. o.. .8o::o8 . . . .. :*** * ....................................................o:*::o: ... . .. . .. .. . .o 8 :88o :o&o8 &o:: o. . |
. .. o * *. *::.:*.8&. :8 . . . . *o*:.: ................................................. o*o*o:*:o . . .. .. ... .:&#..: ..8:#. .o8 8 : 8. :o ** |
. . :. .oo o*..:*.8 .88..8. . . . . :**** .............................................. o*:*o oo*oo .. . . . . .#o:*. 8*oo .#*o8 &o& #.o *.. |
. . o* : # o :: .8. .:o . . . . . .ooo ........................................... o::*o o& . ... . ... . #:#8. &8. .8*8 &* 8o o :* * |
. .. .: 8 8 * o .8* . . . . . ......................................... .o*:*:::ooo. .. . . . . ..8. .##o8 #& :8. &. :: 8 : * |
o* 8 *o o* . . . . *: ....................................... .:*:*:****:*:o. .. .. . . . . . 8#8&: .o8:o 8 #:: :8 : oo |
& . o * oo8:*:. . . . . . o:*.o .................................... o****:*:**o*::*:: .. . . . . . * # .:::#: :&. .. o:&. . * . |
*..::* & 8 & .o... . . . . :*****o* ................................. :oo:*.:::*:****o& . . . . . . # .88. 8# .. . .**. . o . |
.8o* :.. ...o : . . . . o.***: oo .............................. .:...:.oo: .. . . . . . 8.#. *#. 8. * &#.:.:o# * * |
: .8 : &.o.:.:: . . . :**** .****o ........................... ..... .. . . . . .. .:8..#o *: : o : .. |
* :.*.: ..*.. o . . . .**: *o*******: ....................... .. . . ............. . . .. . . . ..:&o. :* : : . . |
:* * *8 8: &. & . o..********o. ..................... . . . . . . .. . . . . . . . ...8:o:&* & 88 :. . |
. .. . *&& . . . .. .******.******o* .................. .. . . . . .. . . . .. . . . . . :o& : 8o&8 o:8. |
.8: . . ***********:* ............... . . . . . . .. . . . . . . .: # . #. 8 : . |
:. . o. .* o :. . ............. . .. . . . .. . . . . . . .. . . . :.# |
. .o . ... . . ........... . . . .. . . . . . . . . . . 8:. . |
. * o . . . ....... . . ......... . . . ... . . . . . . . . . . . & #..o .* |
. o *. . . . ........ . . . . . .. . & . . : . . . . . . . o. : .* o |
* * .. . . . . . . . ...... .. . . .. . . .. . *.. . . . . &..8 8o *.. |
. o o . . . . . . . . ..... . . . . . . .... . : . . . . . .* 8 :.o |
:*.:**: . . . ... . .. . . . . .* : . . . . 8** **. |
. *.:** . . . . . . . . . *:. .&.. ... . ::ooo*** |
. * . . . . . .. . . ..&... ..* : :. . *. |
. .* . .. . . . . . . . . .o. . *: . . |
o..: *. . *. .. . . . . . . . *. * *: . . . . &.:* |
. ** ** . . . . . . : * . .. ..*. |
. * .* . . .. . . . . . . .&.. . .#. . * .. |
* .. : o . . . . ... : . . . . . . :. * * * * :.. ..* .*. *. |
* * . o .:* * . ....... . :. *8. ... . .# :* **: * .*. . . |
.* . .*:****: .... .. . *8: *o . . ... * *... *. ..*.* |
.....***..:*. . ... . *:.. .* . . ... : . .*:..*.: : |
..**.:..:. . ... ... &o:.88: . . .. . . &.. ... . |
.. * *& * ... ... :8&.:o*. .. .. . . ..**. . |
.* .. o. : . . *. .... ... .. . . . . :....:.. . . . |
*. *o . .. 8. o#. . . . .. .*:... .* *..* . . |
. 8 . . .o. : .. . * . . .. ... . .. . |
.* . . . . . . ... ..... . . .* . .. .. ... |
*. *....... . * ... |
... ... *... . :.:. .. . |
.*.***o . . ..... . . . |
... .. . . .* . . |
... . . . . * . |
. :. . . . . .. |
*. *.o... . *.* |
.*. .: ..: .. . . : * |
* . * . ..* :. .o ... o |
... ... .: . .. . * . o *.: .*. . |
.. . ... .. .. .. : .. ... **.*.: * o o. . ....o |
.. .. .* . * . o .. * .*. o : . ..*. |
* * .. ..* * .... ... o * * ... ... |
.. .. . * .* ...*. .. ... ** ..... .. .. ..*. |
.. ....* ...... . .. ... .. ...... *... ... |
.... . . .. .... . : * . .....* ..... . |
.. ... : . * .. .. * . ... . .. . |
.. . . . .. . . . .. .. .. ... .. . . |
. ... ...: :. . .. .. .. ... |
*. *. .. . .... . |
. . . .. |
. . |
And a winter scenery!
string_agg |
---|
.::.::::.:::::::::::::::::::::::o::::&::&::o::&::&::&:&::&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&::o:&:o::&::&::&::&::::&:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: |
..:.:::::::::::::o::o:o:o:o:::&:o::&::&:o::&:o:&::&:&::o::&:&:&:&:&:&:&:&:&:&:&:o:&o:&o:o&:o&:o&:o&:&o:&o:o:o&:&o:o:o:o:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&::&:&::&::&:&:o::o::o::&:&::&:&:&:&:&:&:&::o::o::::o::::::::::::::::::::::::::::::::::.:::*::.::.::.: |
... :..::::::::::o::::::::::::&:&:::&::&:::&:o::&::&::&:&:o:&::&:&:&:&:&:&:&:&:&:&&:&:o:&:&:&:&:&:&:&:&:&:&:&&:&:&:&&:&&:o&:&&:&:&:&:&:&:&:&:&:&:&:&::&::&:o:o:&::&:&:::&::o::&:o:::&:::::::::::::::::::::&::::::&::::::::::::::::::::::::.::*:::*:::::::::::: |
:::::: .:::::::o::::::&:&:&:&:&::::&:&::o:o:&:::o::&:&:&:&::&:&:&::o:o:o:o:o:o:&:&:&:&:&:&:&:&:o:&:&:o&:&:&:&o:o:o:&:&:&:o:&:o:o:&o:o&:&:&:&:&:&:&::o:&:&:o:o::&:&:o::o:o::o::o::::o:::o:&:&:&:&::o:o::o:::::::o::::::o:::::::::::::::::*::::::::::::*::*:.::*:: |
:: ...:::::::::::&::o::::::::::&:o::&::o:::&::o:&:&:&::o::&:&:o:o:&:&:&:&:&:&:o:&&:&:&&:&:&&:&&:&:&o:&:&:&o:o:&:&:o:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&::&:o::&:o:&::&:&::&:&::::::::::::::::::::o:::::&:::::::::::::::::::*:::::*::.:.::.::::::::::.: |
: . :::::::::.:::::::&::&:&:::o::o::&:o:o:&:&::&:::o:&:o:&::&:&:&:&:&:&:&:&:&:o:o:o:&:&o:&:&:&&:&:&:&:o:&:&:o&:&&:o:o&:&:&&:&&:&:o:o:o:o:&:o:&:o:o:&:&:&::&:&::o:o::o::o:::o::&::o::::o:o:o::&:&::o::&:::::&::::::&::::::::::::::::::::::::::::::::*:.::.::::: |
:...::::::.: ::::::::::&:::::o:::o::&:::&:::o::o::o:&:&::&::&:o:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&:&o:&:o&:&:&o:&:o:o:&:&:&&:o:&:o:o:&:&:&:&:&:&:o:&:&:o::&::o:&::&:o::o:o::&:o:o::o:::o:&:::::&::::::::::::&::::::::::::::::::::::::::::::*:::*:::*:.::::::::.:*: |
:. ...:::.&:::::&::::::&:&::&:&::&::&::&:&::&:o:&::&:&:&:o:o::o:o:o:o:o:&o:&&:&&:&:&:&:&:&:o:&:&&:&:&o:o:&:&:&:&:o&:o:o:&:o:&:&&:&:&:&:&:&:o:&:&:o:&:&:&:o:&:&::&:&::o::&:::o:::o::::&:&::::&::o::&::o::::::&:::::::o:::::::::::::::::::::::.:::::.::.::::::: |
..:.:..:.:::.::::::::o::&:::::&::::o::&:&::o::&:::&::&:::&:o:&:&:&::&:&:&:&:::::::::::::::::::::::::&:&::&:&:o:&&:o&:&:&:&:o&:&&:o:&:&&:&:o:o:&:&:&::&:&:&:&::&:&:&:&:o:o:&::&::o:&:::&:::::o::::::::::::::::o:::::o::::::::::::::::::.:::.::*::::::::*::::*::.: |
. .... :.::: :::&:::::::::&:o:::&:&::o:::&::o::&:o::o:&:&::&::&:&:&:&:&::::::o:::::o::&::o::&:&::o::::::&::::::::&::&:&:&:o::&:::::::::::::::::::::o:::::::::::::::::::::::o::&::::o:&::o:::::o::&::o::&::o::::::o::::::::::::::::::::::::::::::*:*:::::*:.::::: |
.. :.: .:::::&:&::::::o:::o:::o::&::&:o::o:&::&:o:&:&::o:&:&::::::::::::&::&:::::o::::::::&::&::::&:&:&::o::::::::::::::&:&:&::o:o:o:o::o::::o::&::o:o::&:&::o::&:::::::&::::::::o:::::::::::::::::::::::::::::::::::::::::::::::::*:::::::.:*:::::.:.: |
...:..:::::.:::::::::::::o:o:&::o::&:o::o::&:::&::&:&::&:o:o:&:&:o::::::&:&::o:::::::o:o:::o:o:o::::&::o:::::&::::o:o:o:o:o:o:o:::::::o::::::::::::o:::::::::::o::::::::::o:::&::::::::::::::::::::::::::::::::::::::::::::::::::.::::*::::::*:::.:::::*::::::: |
. .:.:.:::::::o:::::o::&:::::::::o::::o::&::&:o::&:::o:&::&::o:o:&:::::::::::::&::&:::::::::::::::o::::::&:&::::o::::::::::::::::&::o:::::o:::o::o::::&::&::&:::::::&::&:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::.::::::.::::*:::::.::::*::.: |
.:.::.:::::::::::::o::::::&:&:&:&::&:o::o:::&:::&:&:o::o:&::o:&::&::::::::::::::::::::::::::::::::::::::::::::::::::::::::&:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*::*:.:.::::*::.::::.:::*::*::*:*::::: |
.. .:::::::::::::::::::o:&:::::::o::::&:::o::o:&::o::o:&::&:&:&:&:::::::::::::::::::::::::::::::::::::&:::::::::::::::::::::::::&:::::::o::::o::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::.::.:.:*:*:.:.:*::*::*::.:.::.:::::::::::*::::::::*:*: |
:: :::::::::::::&:::o:::::o:o::&::&:&::o::&:::&:o::&::&:o:o::&:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*::.:.:.:::.::::.:::*::.::.::.::.:.:*:.:.:*::.::*::::.:.:*:::::. |
.::. .. ::::::::::o:::::o:::::::o::::&::&::&:&::::&::&::&::&:&::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::.:::.::*:.::.::::.::.::.:.::*:.::*::*:.:.:*::*::*:.:*:.::.::::*:::::::.:*:: |
::::::::::::::::::::::&::::&::o.::o:&:::&::o:::&:&::&:&:o:&::&::.::::::*::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::.::::::::::.::*::.:.:::.::.:::::::.:.:::.::::::*::*:*:.:.:.::.:.:.:.:.::.::*::::.:::.:*:*:::.::* |
::::::::::::::::o:::::::::::o:::::::::o::&::o:o::o:&:&::&::o:&.:::*:.:*::*::.::.:*:*:.::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*:::::::::::::::::::*:.:.::::::::.:..:.::.:::::::*::*:.:*:*::.:.:*:.:*::::*:::::::*::.:: |
:::::::::::::&::::::&:::o::::::.::&:::::&::::&::&::::&:&::&:o:::.::*:::.::.::*::::::::.::.:.::::::::.:*:::::::::.::.::::::::::.::::::::::::::.:::::::.:::::.:::::::*:*::::.:*:.:*:::::::.::::::.:*:.:*:*:::*:*:..:.:..:..::.:.:.:.:.:.:..:.:.::.:::*:*:*:*::::.: |
.:::::::::::::::::::::::::.::::::::::::::&::o::&::&:&:::&:o:&:..::*::.:.:*:.::.:..:..::.::*::.:.:.:::::.::.::.::::.:::::*:::::::::.::::.:::.:::::.:.::::.:.:::.:.:*::::*:.::::.:::.:::*::::::.:::.:.:::.:*:.:::::.:.:*:.:..::.:.:**.*:*:.:..:..::.:::::::::*:*:: |
::::::::::::::o::::::::.::::::.:::: :&:::::&:::o:&:&:&:::o:&:.:.:*.:.:.:.:.:.:::.::.:.:.::*:.::::*:.:::*:::::*:*:.:.:.::.::.:.:.::*:::.:.:.:.:::*::.:::::.:.::::::*:*:::.::.:.:.::*::::*::::.:.:.::*::.:.:.:*..:.:.:.*.::..:...:..*....:.:*:.:::.:*:*:.:.:::.: |
:..::::::::o:::::::..&:.o.:..:.:..:::::.:::o::::o::::o::&:&:o:.:...:.:.:.:....:...:.:*:.:.:.:.:.:*:.::::.::.:.:*::.:.:::::::.:.:::::::.::*:.::::*::.:::.:*::*:....*::::*:.:.:.:.::.:::*:.:::.:.:.:.:.:.:.:.:*:..:.:.**.....:....:....:.:.....:.:.::::::.:::*:.:: |
::... :::::::::::::::.::::.: :.:...: ::::::::&:::o:&::o::&:::o::....*.......: .:.:.:.::.:.:.:.:.:.::..::.:.:.::*::::.:..::::*:*...:.::.::::.:*::*:.:..:.:.:.:.::::.:.::.:.:.:.:..:.:.:.::.:.:.:.:.:*:.*:..:..::...:..*.*....*....:...:..*.*.:::::.:*:::*:::.:.: |
.:: *:.:.:::::::o::.::::.:.& :.&. :: .::::::::::::&::o::&:&:o:&:.. . :::: . .............:..:.::...:.::..::.:.::.::.:*::::::.::.::*:.:::*:::.:.::.::.:::.:*.:.:.:.::.::*:.*.........*:*::.*....::.:..*:....:..:...:.:......... .......:::.:*:::.::.:*:::: |
:o.::.:::.:::::::::*::..:: :::. ...:::: ::.::::::&::&:::o:::& ::::& .. ...........:..:.:......:..:*::.:..:...:..:..:..: ..:.:......... .. . . . .....:..:.......:..........:.......... . . .:::::..... .*:::::*:*::*:::.:.. |
:.:. .::::.:::&:::::..:: ::: .. .: ::::.:::::::&::&::&:o:&:&& ..:.:. . . :.*:*:. . .::*::::*::.:*:.:: |
..: .::.:::::::&::. ...:.: .... & . &:. :..:::::o:::::::&::& :::..:: . :.:..: .:.::*:::*::::::.: |
.. .:.:...:.o::::::: : . : :.. . :: :.:::.:.....:::&::&::&:&:& . . ... .: ::::::*:::.:*:.:: |
.:. ::.&.:.:::::::: ... :. . . .:: : ..:::.::::::::&::&. . : .::*:*::*:*::.::.: |
:: &. :..::::&:...: : . ... . : : ..: : . :....::.:&:::&::& . ::*::::.:::*::.:*: |
::: .:. .::.::o..::.:: . . : .. . . :..: ::..:.:&::::o:. ::::*:*::*::*:::.: |
..: .::. :: : .::.... : .:. ..*: .. .. :... :..:.:::.:&::&&:&:::& .:.. ::::.::::*::.::*:*: |
: :: & . .: :.. . . : :. :... ..... :.. .:. :::&.:.:&:&::o:&:&:. &::::::*::.:*::.::*:.::. |
: :.:..:: :: ....:::.. . :. . : .. :: .:... : ::..:::::::o:&::o::8 .:.::::::*:::::.::.:.::*: |
:.:. ::*.::...:.:...: . . . : : ... ... ..:..::.::&:::o::o::o:: .::::.:.::::*:.::*:::*:*:. |
..::.. :..::: . .:: : . . ::: .: .. .:.: . ..:: ::.::&:::o::o::& ::::::::::*:*:::.::*:.::*:: |
.. . . :. . ::. .: . : . . .:... . : : .: : ::::.::::::::::&::o:o& *::::*:.::*::::.:.::*::*:.:.: |
:. :. *:. . ..... . . . .. .: : :.:. : .::..::.:&:.:&::&:o::& .:::::::::::::*:.::::*::*:.:::. |
.... . .:..... ... .. : . :.. : . . :..:..:..:::::o::&:::o:&. ::::::*::*:*:*::::.:*::*:::*:.:. |
:..:.: &. : . . . . ..: ..: : .. ..: ::.:...:::::::::o:o:::# .::::::::.:::::::.:*:::*::*:*:.:*: |
:: ::.. .: . . .: :: :. .::: . . : :. . . .* &::.::...:::&::::o::& : :::*:*::::.:*::.:::.:.::.:.::.::. |
.: ::.:. :.:..::. .. : . . &::: : :.. : . . :::..::.::::::&:o::&:& ::::::::::::*::::.::::.:::*::*::.::.: |
::: .::: .. .. . ::.:. *.. :.:::: :. ..:. :.::. . :...:&::&:&:::&::&& ::::::.::*:::::.::::*:.::*::*::.:.:.:* |
. . ::::: :::..:. .::.:: : . .. : . . ...:::::::::o::o::& .:::::.::::::*:*:::*:*::::.::.::.:.:.:.: |
. ....:...: .. . . . ::.:.:::. : : :.: :.:*:.:&:::o:::&:: .. .... . . ... . . ....:.:.....:.::.:.... ::::::::.::.:::::.::::*:*::.:.:*:::: .:. |
:. :::: .:. :.. . .. : ... ...:. .: .. ..*::.:&::&::o:o:::&:& .&:&:&&&:&&:&:&&:&&:&&&:&:&&&:&&&:&:&&&:&:&&:&&:&&:&:&&:&:&:&&&:&:&:::&:&:&::::::::::&:&::::::::::::::::::::::: .::::.:::.:::::.::::.::::*::. :...:....: |
::. :. . .: : . .: . . : .. : ..:.... . . :.. . :.::&::::::&:&::& .:&::::::::::::::::::::::::::::::::::::::::::::::::::::::: :. ::::::&:::::::. :.::::::::::::::::::::::::::::::* ::.::::*:::.:.:::*:.::.:*::. . . .:. |
..::: : .:. : . . . . .: : :. : ::. . :. . .: .:.....:::::&:o::::&:& &::o:&:o:o:o:o:&::&:o::&:: .:&::o:&::&::o::&::o::&::o::&:. &:&:::&::::. ....:::::::::::::::::::::::::::: ::::*::::::::::.:::.::*::. . . .. |
.:. ..: . : . :... :: . .: .*.::.: :...:::::::o:&:::& ::o:::::::::::::::::::::. :::o:::::::o:::::::::::o::::: ::::&: :o.:. . :.::::&::& &:::::::::::::::& ::*:::.:.:.:.::::.:::.:.. . . .: |
.: . :.. . .. . : :: .:: ::... .: ..:::::o:o:::::&: .&:::o:o:o:o:o:&:::::::.: ::&:::::&:&::::o::&:&::::::o:& .:&:: .: . ..:::::::: :::::::::::::::: .::::*:::::::::*:.:::.::: . |
.... .:. . : . :. . ::: :: .:....:::::..:::::::&:&::& .:o:::::::o:::::::.::..: :::&::::::::::::::::. ::::::: . : : . ::::::::: ..:::::::::::::::.. . ::::::.:.:.:.::.:.:*::.. . |
. . .. : . ..... .. .. .... .::::::o:&:&:::::& .&::&:&:&::::o:::.::: . &:::&::o::&:&::o:: .::&: .. .:: .::::::: :o:::::::::::::. :: ::.:.:::::::::. ::.:.:. . |
. . . . . : . .:.. . .: . :.:..:::::::::::o:o:& .::::::::&::::::::.::. .::::::::::::&::: . &: :... .: :. .::.:::: .:::::.::.::: . .. ::::::*:*:.:: . . . . |
. . .. . .: : :::...:.. :...::.:..:.*:::&::o:::::: .o:o:&:&:::&::::.:.::: : .:o:o::::.:&. :&. &. : ::: . :..:::: ..::. . :... : ... :.:*::.::::*: . . : |
. . . . ..... ..: . ...:..::::::::::o::&& :::::::::&:::::::. :.: . . &:::: . ::.: :&: ::. . ::::.:: :: :::: ...::.....::: .. |
.. . . . : .. : . .: .: :.::. :.:::::&:.&:::::& .o:&:&:o::::::::.:.:.. : :&:. . ::&:.: :. .. . : : . ::.::: &:.. .. : . ... .:....... . . : |
.:. . :. . .. . .. .: : :.. . ::.:&::::::.:::*::&:::& ::::: &:::o::::::....: : .. :::. ..:.. . .. :...: . :.:.: :: :: . . . ... . . .. . ::: |
. :. . ..: .. : .: ..: :::::..::::.::::o:: :::: .::::::::...:.. . :.:. . ... . . . :.:. . .::. :. . . : . . . |
. . . . : .: : .. .:::. .:::::...: :::&::* .:& ..&::::::*::.. :.. &. . :.. . .:.. : . . . :. .. |
. . . : ..: .&::: .:::::.:: :&:::&. .:. :.:... ::..:.. : : : . ::. .. ... . . . . . |
.. . . . . ..::::. ::::::.. ::::: .: . .:::...::. :: . . . . . . . . . . . |
. ... . .. .::::: .::::::..::&. . .:.:: : :. .:: . . :. :.. . |
.. .::::. ..:::::: ::. .: . : . .. . :.. . . . .: |
. . . . . ::::.. .::::::: :. .: . . . .::. . . . . . . . |
:: . . . . . ..:::. .:::::. . . : . :: . :. :. .. . .. . . . |
: .. . . . . . .::::. .:::: . . . . ..: .: . .. :. .:. |
... . . . . .. .::... .:. . . . . . . ... . . |
. . . . ::*:.. . . .: . : . . : : . |
. . :.:. . . . . :. . . . |
. . . .. . ... :&:: . |
.. . .. . . . . . |
. . . & . . . . |
. . :& . . .. .. . |
. . . .:::. . . . . |
. . ::: . . ... |
. . . ::& : : . : |
. . :: .. . ::.: :. ..: . .. |
. . . :& . . |
. . .&. .. :. :&::: . :. . |
. . . .:. . : .. :. :. . |
. . . :. : . .: |
:::.. ..... :. ::. :.& . . . |
:::::::::::::::::::::&:o::::.::......:..: ::::&:&:&:::.. . ... . .. ..... . . . . ... ... . .. . ::::::&: & |
:..:.:.::.:::::::::::::::::::::::::::::::::::::::::::::::::::. . . . . : |
.::.:.:..:.:.:.::.:.:.:.:*:.:.:*::.::*:::.:.:..:.:.:::::::::::::::. . . ..: : ... |
..:*:..::.:.:.:.::.::.:.:.::*:.::*::.:*:.:::::::.::.:..:.::*:.:::::::::o . . . .. . . : . . . : |
.....*...*.:.:.:..:*.:.:.:*:.:*:.:.:.::*:....:.:*:.::::.:.:::::.:::::::.:::: . . : . . : . : . . : : .:.:. .:.:&:&. . |
.::.:..:..:**.*.::.:.:.:.::.:.:*:.:.:*.:.::.:.:.:.:.*:.:*:.:..:.:.:.:.::::::::::. . . . . .. . . .: ........ . . . ...& ::&. . . |
........:...........*.:*.:.....:..:..::.........:...:.:...:..:..:.:.:..:..:*:..:*: . ::. . . . . .... . .. . :: .::::::. . .::: :::& . . |
. . .. .....:.. .*:....:...:..*....:. ::: :.....:..*.....:.......:..:...:.:.: : : ...: . ..:..::..........::&:. ::::: . :.. . ...::::.::. . .. .*:::.:: . ::&: . |
. . :::. .. .. : :::::::::::::. &::::::::&::&::&:&::&:::&:&::::&&&::::&:&:::::::::::.:.:::::::::::::. .: : ::. ..: . . |
:::.: . . . :::::::::::::..::::::o:::&::::::::::::::::::o::::::&::::::&::&::::::::::::::::::::::::.. ..: :. :: . & : ..:::: :... . . . |
::.: ...:.:: ::::::::::::::::::o::::::::::o::::&::&:&:o:o::o:&::::o:&:::::::&:::o::::::::::::::o:::o:&:&:&*::::. :.... .::::: :*:..::*:.::..:.::*:.:.: :.. . . |
. :..::..:::::::::::&::::::o:::o:::&:&::&:::::::&:::::&:&:::::&:&::::&:::o:o::::::&:&:::&::::::::::::::&::o::o:::::: :::::::::::::::::::::::::::.::::::: |
::::.:: .::.:::.. : ...:. ........ ::.:.::::::::::::::::o::::&:::o:::::o::&:&:o:::o:o::::o:&:&::::o:::o::::::&::o:::::::::::&:::::&:&::::::::::::::& ::::::::::::::::::::::::.::.::::::: |
........ :.......:.:::..:..... .. .... .:.::.::::::::::::o::::o::::&::::&::&:&::::&::o:::&::::&:&::::::&:&::&::o:o:o:::::::::::o::o:::::&::::::&::::o::::::: ::::::::::::::::::::::::::::::::::: |
.:.:.:.::.:.:.:.:.:*.:..*.::.::.::.... . . :.::::::.:.::::::::::::o:::o:&::&:&:::o:::o:&:::&::&::&:o::::&:&:&:::::o::&:::::&:o:o:o:&:&::::&:&::::o::&::::::::::o::& .:::::::::::::::::::::::::::::.:*:. |
.:.:..:.**.**.:.:..:.:.:.:.:..::.:.:::::::::::::::&:..:.::::::&&&&&:::.... ..: &:::::::::::::::::o:o:&::&::::&::o:::o::&::::&:&:::o:o:::o:&::&:::o:o:&:::::&:o::::::::::::::&::::::o::::::::o:o::o:::::. .:::::::::::::::::::.::*::::::::::: |
.:..::.*.::::.::::::*::.::::.::::::::..:.:.::.:::::::::::::::::::::::::::::o::. :::::::&::::o:::&:::::::&::o:::::::&:::o::o::::o:o::::&:o:::::::o::::o::&:&::::o:o:o:o:o:o:::::o::o:::o::o::o::::::::o:: :::::::::::::::::::::::::.::::::.. |
:*::.::.:.:*:::*:.:::.::::::.:::*::.:::..:::::::::::::::::::::::::::::::::::::. :::::o::.:::::o::::&::o:::::&::o:&:::&:::o::&:o:::&:&:&:::&:&:o:::o:o::&::::o::::::::::::::o:o:::::::::o::::::::&::&:::&. &::::::::::::::::::::::::::::::::: |
.:..:.: ::::.::::.:*::::.:.:.:::::::::::::::::::::::::::::::::::::::::::::::.& :.:::::::::&::::o:::&:::&:o:::&:::&:o::&::&::&::&::&::::&::::::&:::&::&:::&:::&:&::&:&::o::::::&::&:o:::::o:::::::::::::. ::::::::::::::.::::::::::::::.:::: |
.:.::: :.:*::.::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: :::::::::o::::::::&:::o::::::&:::&::::::&::&::o::&:::&:o:::o:o:::&::o:::o:::o:::::::::::::&::o::&:::::::o::::&:&::::::::. ::::::::::::::.:::::::::::.::::::. |
:*:.: .:*::.::.::::.:.::::::*::::::::::::::::::::::::::::::::::::::o::o::::& o:::o::&:::&:&::o:::o:::o::&:::&::&:&:o:::&:&:::::&:o::::&:::::&::::::&:::::::&:&::o:o::::::::::::o:::&:::::::::::&::::&: ::::::::::::::*:::::::*:::::::.::: |
:.::: . .::::::::::.:::::::::::::::::::::::::::::::::::::::::::::::::::::::::& .::::::::::::::::::::::&:::::::&::&::::&:&:&::&::o:&::::o::o::&::::o:o::&:&::&::::o::::&::::&:&::&:::&:::::::&:::::::::::: ::::::::::::::.:::::::::::::.::::: |
.....:::..........:.:..:.....:.:...:..:.:.:::.:::*::::::::::::::::o:&::&::&::: .&::o::&::o:o::&:&:o::::&:o:&:&::&::o:::::::&:::o:::&:&::::::o::&::::::::::o::::::::o:::o::&:::::::::::&::o::::&:::::::::: ::::::::::::::::::::::::*::.::.:*: |
:.*.:....:**.:.::.:.:*:*::*:::.:.::.::*:.:..:*:..:.:..:*:*::::::::.:.::.:::::* :::::o:::::::o::::::::o:::::::::::o:::&&:&:&:::::o:&:::::&:&::::::o::&:&:&::::o:::o:::o::::::o::&:&:&::::::::::::&::o::::: :::::::::::::::::::::*:::::::::::: |
...:*.::.:.:*:*:**.:.:.*.::.*:.:.:.:.::.::*:.:*::.:.::*:::*:*:*:*:::.:*::..:.:: . ::o::::&:&:::::&:&:&:::o:o::&:&::::o::o::&:::&:&::::o::::::::&::o::::::::::&:&::::::::::o::::::o::::::::o::&:o:::::::::::: :::::::::::::::::::::::::::::::::: |
.........:.:..:..:..:..:..:.::.*.::*:..::.:.:.:.:.:.:.::.*:.::.::.:*::::.::*:.::: :::o::::::::&::::::::&:::::&:::::&:::&:::::o::::::o::&:o:::o::::::&::o::o:::::::&::::&:::&::o::::::::&::::::::::::::::::o: ::::::::::::::::::.:::::.::*::*:*: |
.*...:..........*..:*.*..:.:*.:.:..:.:...:.:.:..:.:.:...::.:.:.::*:.:...*..:.:.:. :::::&::::o::::&::::::::&::::o:o::::&::&:&:::o:o::::::&::::::&:::::::::::o::&:::::&::::o::::::::::&:::::::&::::::o::o::::: :::::::::::::::::::::::::::::::::: |
.............:......*..:.....:......: ........*:......:..............:.:*.:.:...: o:&:::::&::::::::::o::o::::::::::&:::&:::::&:::::::&:&:::o::::::&:::::&:::::::&:&:::::::::::o::o::::&:::::::::::::::::o::: ::::::::::::::::::::::.::::::*:::: |
. .... ..............:............. ..:........ .::.:. :.*.......... . ::::::::::::&::o:o:::o:::o:o:o:::::o::::&:&:::&:::::::::&:::o:::::&:::::::::::::::::&::o::&:::::::::::::::::o::::::::::::& ::::::::::::::::::::.:::*::.:::*:. |
.:.:. . :. :. ::::o::&::o::&:::::::::::::::::::&::::&:::&:::::::o:::o::::::::o:::::o::::o::o::&:&::::::::::::::o:::&:::::::::::::::::::: :::::::::::::::::::::::::::::::::: |
. &::::::::::::::::::::::o::&::::o:::o::::o::::&::o:::::::::::::::::::::::&:::::::::::::::::::::::::::::&::::::::::::::::::: *:::::::::::::::*::::::::*:::.:::. |
&::::&:::&::::::::o::&::::::::::::::::::::o:::::::::&:::::o::::::::o::::::::::::::o::o::::o::::::::&:::::::::::::::::::::& ::::::::::::::::::::.::*:::::::.:: |
. .... . .. . . :::o:::::::::::::::::::::::&::o:::&:&::o:::::::::::::::&::::&::::::::::::::::o:::::::::::::::::::::::::::::::::::::::::::: ::::::::::::::.:::::::::::*::::::: |
. .....::::::..::::.:: .:::::::&&:&&:&::&::&:&:&:&:&&::&:o:o::&::::&::o::::&:: &:::::::::::&:::o::::::::&:::::::::::::::o::::o::o::::::::::::::::::::::::::::::&::::::::::::::::::::::::::::::::::::::::: :::::::::::::::::::*::::.::::*:*:: |
::::::::.:.:*::::*:::::::::::.:::::::::::::::::::::::::::::::::::::::::::::::. ::::::::&::::::::::o::o::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: :::::::::::::::::::::.::::*:::::.: |
:.:*:*:.:.:::::*::::.::::::::.:::::::::::::::::::::::::::::::::::::::::::::::. &::::::::::::::::::::::::::::o::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::. .:::::::::::.::*::.:::::.:::.:.::: |
:.:*:*::.::.:.::*:::::*::.::.::::::::.:::::::::::::::::::::::::::::::::::.:::. ::::::::::::::::::::::::::::::::::o::::o:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::.:..:.:.::.:.::::.:::. .::.:::::.::::::::::*:::::::::::.: |
:.::*::.::.:.:*:::.:.::::::. .:::::*::::::::.:::::::::::::::::::::::::.::.:::. :::::::::::::::::::::::::::::::::::::::::o:::::::::::::::::&:::::::::::::::::::::::::::::::::::::::::::::::::.:.::::*:::::: .:::::*:::::::*::.::::*:*::*::*::: |
.:.:.:*:.::.:::.:::::.:*:.:::::.::::::*:*:::::*::.::::::::::::::::::::.::.:::. ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::.:*::*::::::::*::::*::. ::::::::::::::::::*:::::::.:::::*: |
::*:*:.:*:*::.::::*:...:::::.:::::.:*::::::*:::::::::.:.::::::::::*: :::: :::. :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::.:::::::::::::::::::*::::::*::.::::*::.:::*::::::::::::.:: .:*:*::::*::*:::*:.:.:*::.::.:.::: |
.*:.:*:*:.:.::..:*:..:.:.::.::.:*:::::.::.:::*::.::*:::::*:::::::: :.:.::.::: ::::::::::::::::::::::::::::::::::::::.::::::::::::::::.:::.:.:::::::::::::::::::::::.:::.::::::::::::::.:.::*:*:*:.:.::::. ..:.:.:.:::.::*:::::.::.:::.::::*: |
.:.:*:.::*::.*::.:.*.:.::.:::::::.:.:.::.::.::.::*::::.::::.::*:::.::..:: ::: :::::::::.::::.:::::::::::::::::::::::::::::.::::::::::::::::::::::.::*::::*::::::::::*:::::*:*:.:.::..*:::.::::::::::::*:: ..:.:..:.:.:::.::*:::.:.:*:.:.:.:: |
:*:*:.:..:.:.:..:.:*:*::.:*:..:*:.:.::.::.::.::.::*:.::.::.:::::.....: :..:.: ::.:*:.:::::::::.::::::::::::::::::::::::::::::::::*::::::::.::::*::::::*:::::*::::*::::::.::::::::.:.::::.::*:*:.:*:.:.::* .::*:::*::.:.:*:::.:::.::.:::.::.: |
..:..:..:*:.*:*:.:.:.:.:.::.::.:.:.::.:.:.:*::.::*:::.:::.::.:.:.:.. :. ::... :.::::::::::*:::::.::*::::::::::::::::::::::*::::.:::.::*:::::.:::::::::::::::::*:::::::::::*:*:*:..:*:*.::.:..:*:*:::.::.: .::::.:::*::::.:.:*:.*:.::.:.:*:*: |
.**.*.*:....:..:..:*:.:.:.:*:.:*::.:.:.:::.:.:.:.:*:.:.:*:....:*:..::. :. . .:::.:.:.:.::::::::::::::::::::::::::.::::::::::*::::::::::::::::::*:.:::.::*:::::::.::*:.:::::::::::*::::.::.*.....:..:..:* ::*:...:.:.*::*::.:::.:.:.::.::.: |
...:*.*..:.:..*..*.:.:..:.:.:.:.:.:.:.:.:*:*:.:.:.:.::.:.:...:.:.:.:..:... . :.::.:::::::.::.::::::::*::::*:::::.:::::::.::::::::*:::::*:::::*:::::*::::::*::*:::.::::::*::.:....:..:.::.:*:.*.:..:..:.: .:.....::*:.:::.:.:.:.:*:*:.:.:.:*: |
:.:..:.*:.*:.:.:.:*.:.:.:*:.:.:*:.:.::.:.:.:.:.:.::.:.:.:.:::*:*:*:..:... .:. :.:::*:.:::.::::.:.::*::::*:::.:::.::::::::::.:::*::::.:::::*::::::::::.:.::::::::*:::*::.:::*:::....:.:::.:::::.::.:.::...: ......:.:.:*:*.:.:.:.:.:.::*:*:.:*:. |
...*:..:..:..*.*..*.:..:.*.:.:.:.::.:.::.:.::.:.::.:*:.::.:.:.::.::. ..: ..: ...:*::.:::::*::::*::::::::.:::::::::::.:::*:::.::::::::::::*::::::*::.::.::::.::*::.:::*::*:::*:.:..::.::::*:.:*:.*:.:..:.......:. :....:..:**:.::.:*:.:*:.:*:.::.:.:*: |
:.:..:..:.:**:..:.:.:.:.:.:.:.:.:.::*:*::.:.:.::.:.::*:*::.:::*::.*:.::....:.: ..::.:::*:*:*:::::::::::*::::::::::.:::::::::::::.::*:.::*::*::::.::*::::::::.:::::::*:::.:::::*:.:.:.::::::*:*:::.:.::.:..:*:.:.:....::.. .....:.*.:*:***:.:*:.:*:.::.::*:.:* |
...:.:.:.:.:...:.:*:*:*:*:.:.::.::.::.::.::*::.::.::*:::.::.:.:*::.:.: ::.:..:.::.:.:.:*:.::::::*:.:.::*::::.::::::::::::::::*:::::*:::::::::::::::::::.:.::.::::*::.:::*::::*:*:::*::::::*::.:::.:*::.::.::.:.:.:..: ..:.: .:.:....:.:.:.:.*.:.:.:.:.:..:.::.: |
:.:.:.:.:..:*::..:.:.:.::*::.:.:.:.:.:.:.:.::.::.:.::*:*::*::.::.::.:.: .:.:..:..:.:*:.:::.:.:*:::::::.::::::::*::*::::::.:::::::::.::::.:*:.:.::*::*:::::*:::.:::::::::::.::::::*:::.:.:.::*:.:*:::.::*::. ::*:.:.:....:.:.:.....:.*.:*::.::.:.*.:*.:*.::*:*:*: |
*:.*..:.:.:.:.*.:.:*:.::*:.::.:::::.::.:::::.::*:.::*:::.:.:*:.:.:*:.:::..:.:*.:.:..:.::.::.::::.:.::.:::::.:::::::::.:::::*::::.::.:.:::::::::*:::.::::*:::*:::.:.:.:.:.:::*.:*:::*:::::::::::.::.::*:*:.:::.:*::.:....:..:..:.:.....*.:.:.:*:*:*.*.:.:.:.:.:.: |
:.*:*:*:.:.:.:.:.:*:.:.::*:*::.:*:.:.::*:.::.:.:.:..:..::.:.:*:*:.:.:.:. :.:.*:.:.::*:.:.:::.:.:::::.::.:.:::::.::::::::*::::.::::::::::::::.::::.:::.::::*:::.::*:::::::.:.:::::.:::*:*:.:*:.:.:.::.::.::*:.::.:.:.:::.:.:.:.:....:.:*:.:.:.:.:.::.:.:.:.:.:.:* |
..:..*..:..:...:..:.::.:.:.:.:.:..:.:.:.::..:..:.:.:.::.*:.:.:.:.:*::. : :.:.:.::.:*:::::.:*::::::*::::::::::.:::::.:::::::.:::::.:.::.:.:*::::.:::.::::.:::::::::.::.:*::.::*:*::::.::::::::::.:.:*::.:.:.:*:*::.::.:*:.:::.:.:.:.:.:..:.:.*.:.:..:.:.:*:.:.:.: |
:.:.:..*..:..:..:.....:...:....:.:*:.:....:.:.:*:*:*:..::.:.:.:.:*:..::.:..:::*:::::.:.:::::..:.:.:::*::.::*:::*:.::::::::.::*:*::::::::::::.::::::::*::::*::*::.:::::::::::.:::*:.:::*:*:*:*:.:.::.:.:::.:::*::*:.::.:::.:.::*:..*...:*:.:.:*:.:*:*.:.:..:.:.:. |
.:..**........*...*.*..:.:.*.::.:.:.:.*:::*:.:*:.::.:*::.:.:.::.::.:::: :::.:.::*:*.::.:*.:*:::*:::::::*:::::::::::.:::.::::::::::..:*:.::*:::*::*:*:::.:::::::::.:*::*:.*:.::*::::::.:::.::::*:::.::::*::.:.::.:::*::*:*::.:.:*::..:.:.:.:*:.:*:.:.:..:.:.:.:.: |
..:.:...*..*...:.:.:.:..:..:.:.:.*.*:.:*:*::*::*:*:*::.:.::.:.::.::.:..::*::::*:.:::.::.:::::.:::.:*:::::.::*:::::::::::::::*::*:.:::::::*::::::.::::::::*::.::.:::::::::.:.:.:::*:.:::*::*:.::.:::*:.::*:::.:.:*:.:*::.:*:*:.:.:*::.:.:.*.:.:..:..:*:.:.:..:.:. |
:.....:..:..:.:...*..:.:.:.:.:..:*.:*.::.::*:.:.:.:.:*::*:*::.:.:..:*:.:.::.:*:.::*::.:::::*:::.:::::::*::::::*::*::::::.:.:::::::.:*::*:::.::*:::::::*::::::::::.:::.:*:*:..::*:::::*::.::::.:::*::*:*::.:.::.::.::.:.:.:.:::.::.:.:.:.:*:*:.:*:.:.:*:..:.:.:.: |
.*.:.:..:..*..:.:..:.:.:.::.*.:.:.:.:.:*:*:.:*::*::.:.:.:.:*:.::*::::::::.:::::::.::::*:.:.:::.::.:.:::::.:::::::::::::.:::::.::.::::::::.:::.:::*::.::::*::*:.::::.:::::.:.:::::*:*::::::.::::.:::.::.:.:.:.::.::*::*:.:*:*:.:.::.::.:.:.:.:.:.:*:.:..:.:*:.:*. |
..:.*.*:.:.:.:.:.:*.:.*.:...:.:.:*::.:::*::*::.:.:*::.:*::.:::*:::.::.:.::*:*:*:.:::*:::::::::::::::*:.:::::*:::::::.:::::.::::::*:.:.:*:::.:::*:::::::*::::::::.::::*:.::*:*:.:.:::*:*:*::.:*:::*:::.:.:::::.::.:::*::::::.:.::.::.:.:*:*:*:.:.:.:.:.:.:.:.*:.: |
:..*.:...:..:*:.*.*:.:.:.::.::.::.:.:*:*:.:.:.:::*:.:::*:::.::::*::*:::.:::.::::::.:::::::::.:::::::::::*::::::.::*:::::::::::::.::.:::::::::::::::::::::.:.::::::::.:::.:::::::::*:::::::::::*:::.:.:::.:.::::*::*:::.:.:.:::*:.:*::.:.:.:.:*:.:*:*:*:.:*:.:*:* |
You can view the queries here: https://github.com/quassnoi/explain-extended-2019
I wish that this New Year brings more bright colors and pretty pictures in your life!