1
1
mirror of https://gitlab.gnome.org/GNOME/gimp.git synced 2025-10-06 05:22:40 +02:00

Compare commits

...

3 Commits

Author SHA1 Message Date
Ell
3a10fa0b23 app: add GIMP_XCF_NO_DELTA_COMPRESSION environment flag
When set, avoid using delta-encoded compression, and use ordinary
zlib compression instead.  Useful for comparing file sizes using
different compression modes.
2018-02-09 13:58:47 -05:00
Ell
0bea993691 devel-docs: document the new compression mode in xcf.txt
Also, add documentation for the existing (non delta-encoded) zlib
compression mode, which hasn't been documented yet, and amend a
bunch of stuff that's no longer relevant in the neighborhood.
2018-02-09 13:56:59 -05:00
Ell
abf24a4b49 app: add delta-encoded zlib compression mode for XCF tiles
This mode delta-encodes the tile data before compressing it with
zlib, as described in devel-docs/xcf.txt in the next commit.  This
usually provides a ~10%-30% decrease in size for some classes of
images, especially photographs.  While delta encoding might also
result in larger files for other classes of images, we apply it
adaptively, looking for the differentiation order that minimizes
the compressed tile size, so that we generally never pessimize
the file size, sans some negligible fixed overhard.

Note that delta encoding is always lossless: both for integer
formats, and for floating point formats, whose raw
reperesenatation is manipulated using integer arithmetic.

Add XCF version 14, which enables the new compression mode.  Use
version 14, and the new mode, for all zlib-compressed XCFs.
2018-02-09 13:56:48 -05:00
8 changed files with 491 additions and 56 deletions

View File

@@ -2468,9 +2468,12 @@ gimp_image_get_xcf_version (GimpImage *image,
if (gimp_image_get_precision (image) > GIMP_PRECISION_U8_GAMMA)
version = MAX (12, version);
/* need version 8 for zlib compression */
/* need version 8 for zlib compression, and version 14 for delta-encoded
* zlib compression; we always use delta encoding when zlib compression is
* requested.
*/
if (zlib_compression)
version = MAX (8, version);
version = MAX (14, version);
/* if version is 10 (lots of new layer modes), go to version 11 with
* 64 bit offsets right away
@@ -2509,6 +2512,7 @@ gimp_image_get_xcf_version (GimpImage *image,
case 11:
case 12:
case 13:
case 14:
if (gimp_version) *gimp_version = 210;
if (version_string) *version_string = "GIMP 2.10";
break;

View File

@@ -685,10 +685,11 @@ xcf_load_image_props (XcfInfo *info,
xcf_read_int8 (info, (guint8 *) &compression, 1);
if ((compression != COMPRESS_NONE) &&
(compression != COMPRESS_RLE) &&
(compression != COMPRESS_ZLIB) &&
(compression != COMPRESS_FRACTAL))
if ((compression != COMPRESS_NONE) &&
(compression != COMPRESS_RLE) &&
(compression != COMPRESS_ZLIB) &&
(compression != COMPRESS_FRACTAL) &&
(compression != COMPRESS_ZLIB_DELTA))
{
gimp_message (info->gimp, G_OBJECT (info->progress),
GIMP_MESSAGE_ERROR,
@@ -2004,6 +2005,7 @@ xcf_load_level (XcfInfo *info,
fail = TRUE;
break;
case COMPRESS_ZLIB:
case COMPRESS_ZLIB_DELTA:
if (!xcf_load_tile_zlib (info, buffer, &rect, format,
offset2 - offset))
fail = TRUE;
@@ -2244,6 +2246,7 @@ xcf_load_tile_zlib (XcfInfo *info,
guchar *tile_data = g_alloca (tile_size);
gsize bytes_read;
guchar *xcfdata;
guint32 order;
/* Workaround for bug #357809: avoid crashing on g_malloc() and skip
* this tile (return TRUE without storing data) as if it did not
@@ -2251,8 +2254,24 @@ xcf_load_tile_zlib (XcfInfo *info,
* skip the whole hierarchy while there may still be some valid
* tiles in the file.
*/
if (data_length <= 0)
return TRUE;
if (data_length <= 0 ||
(info->compression == COMPRESS_ZLIB_DELTA && data_length <= 4))
{
gimp_message_literal (info->gimp, G_OBJECT (info->progress),
GIMP_MESSAGE_WARNING,
_("Invalid tile data length in XCF file.\n"
"Skipping tile."));
return TRUE;
}
if (info->compression == COMPRESS_ZLIB_DELTA)
{
/* read the derivative order of a delta-encoded tile. */
xcf_read_int32 (info, &order, 1);
data_length -= 4;
}
xcfdata = g_alloca (data_length);
@@ -2298,13 +2317,13 @@ xcf_load_tile_zlib (XcfInfo *info,
}
else if (status == Z_BUF_ERROR)
{
g_printerr ("xcf: decompressed tile bigger than the expected size.");
g_printerr ("xcf: decompressed tile bigger than the expected size.\n");
inflateEnd (&strm);
return FALSE;
}
else if (status != Z_OK)
{
g_printerr ("xcf: tile decompression failed: %s", zError (status));
g_printerr ("xcf: tile decompression failed: %s\n", zError (status));
inflateEnd (&strm);
return FALSE;
}
@@ -2320,6 +2339,17 @@ xcf_load_tile_zlib (XcfInfo *info,
tile_size / bpp * n_components);
}
if (info->compression == COMPRESS_ZLIB_DELTA)
{
/* integrate the data, to get the original back. */
if (! xcf_data_integrate (tile_data, tile_data, tile_size / bpp, format,
order, 0))
{
g_printerr ("xcf: tile integration failed\n");
return FALSE;
}
}
gegl_buffer_set (buffer, tile_rect, 0, format, tile_data,
GEGL_AUTO_ROWSTRIDE);
}

View File

@@ -70,8 +70,9 @@ typedef enum
{
COMPRESS_NONE = 0,
COMPRESS_RLE = 1,
COMPRESS_ZLIB = 2, /* unused */
COMPRESS_FRACTAL = 3 /* unused */
COMPRESS_ZLIB = 2,
COMPRESS_FRACTAL = 3, /* unused */
COMPRESS_ZLIB_DELTA = 4
} XcfCompressionType;
typedef enum

View File

@@ -66,11 +66,25 @@
#include "xcf-read.h"
#include "xcf-save.h"
#include "xcf-seek.h"
#include "xcf-utils.h"
#include "xcf-write.h"
#include "gimp-log.h"
#include "gimp-intl.h"
/* the ratio between the test data size and the tile data size, for delta-
* encoded zlib compressed tiles. bigger values result in better and slower
* compression. see xcf_save_tile_zlib().
*/
#define XCF_SAVE_ZLIB_DELTA_TEST_SIZE_RATIO (1.0 / 2.0)
/* the maximal derivative order that may be used for delta-encoded zlib
* compressed tiles. see xcf_save_tile_zlib().
*/
#define XCF_SAVE_ZLIB_DELTA_MAX_ORDER 2
static gboolean xcf_save_image_props (XcfInfo *info,
GimpImage *image,
GError **error);
@@ -112,6 +126,14 @@ static gboolean xcf_save_tile_rle (XcfInfo *info,
const Babl *format,
guchar *rlebuf,
GError **error);
static gboolean xcf_save_data_zlib (XcfInfo *info,
const Babl *format,
const guchar *data,
gint size,
gboolean write,
gint max_compressed_size,
gint *compressed_size,
GError **error);
static gboolean xcf_save_tile_zlib (XcfInfo *info,
GeglBuffer *buffer,
GeglRectangle *tile_rect,
@@ -1590,6 +1612,7 @@ xcf_save_level (XcfInfo *info,
rlebuf, error));
break;
case COMPRESS_ZLIB:
case COMPRESS_ZLIB_DELTA:
xcf_check_error (xcf_save_tile_zlib (info, buffer, &rect, format,
error));
break;
@@ -1787,31 +1810,36 @@ xcf_save_tile_rle (XcfInfo *info,
}
static gboolean
xcf_save_tile_zlib (XcfInfo *info,
GeglBuffer *buffer,
GeglRectangle *tile_rect,
const Babl *format,
GError **error)
xcf_save_data_zlib (XcfInfo *info,
const Babl *format,
const guchar *data,
gint size,
gboolean write,
gint max_compressed_size,
gint *compressed_size,
GError **error)
{
gint bpp = babl_format_get_bytes_per_pixel (format);
gint tile_size = bpp * tile_rect->width * tile_rect->height;
guchar *tile_data = g_alloca (tile_size);
/* The buffer for compressed data. */
guchar *buf = g_alloca (tile_size);
GError *tmp_error = NULL;
guchar *buf = g_alloca (size);
z_stream strm;
int action;
int status;
gegl_buffer_get (buffer, tile_rect, 1.0, format, tile_data,
GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
gint remaining_size = max_compressed_size;
GError *tmp_error = NULL;
if (info->file_version >= 12)
{
gint n_components = babl_format_get_n_components (format);
gint bpp = babl_format_get_bytes_per_pixel (format);
gint n_components = babl_format_get_n_components (format);
guchar *be_data = g_alloca (size);
xcf_write_to_be (bpp / n_components, tile_data,
tile_size / bpp * n_components);
/* don't overwrite 'data'; the caller expects it not to change */
memcpy (be_data, data, size);
xcf_write_to_be (bpp / n_components, be_data,
size / bpp * n_components);
data = be_data;
}
/* allocate deflate state */
@@ -1823,14 +1851,17 @@ xcf_save_tile_zlib (XcfInfo *info,
if (status != Z_OK)
return FALSE;
strm.next_in = tile_data;
strm.avail_in = tile_size;
strm.next_in = (guchar *) data;
strm.avail_in = size;
strm.next_out = buf;
strm.avail_out = tile_size;
strm.avail_out = size;
action = Z_NO_FLUSH;
while (status == Z_OK || status == Z_BUF_ERROR)
if (compressed_size)
*compressed_size = 0;
while ((status == Z_OK || status == Z_BUF_ERROR) && remaining_size > 0)
{
if (strm.avail_in == 0)
{
@@ -1842,26 +1873,136 @@ xcf_save_tile_zlib (XcfInfo *info,
if (status == Z_STREAM_END || status == Z_BUF_ERROR)
{
size_t write_size = tile_size - strm.avail_out;
gint write_size = size - strm.avail_out;
xcf_write_int8_check_error (info, buf, write_size);
if (compressed_size)
*compressed_size += write_size;
if (write)
xcf_write_int8_check_error (info, buf, write_size);
/* Reset next_out and avail_out. */
strm.next_out = buf;
strm.avail_out = tile_size;
strm.avail_out = size;
remaining_size -= write_size;
}
else if (status != Z_OK)
{
g_printerr ("xcf: tile compression failed: %s", zError (status));
g_printerr ("xcf: tile compression failed: %s\n",
zError (status));
deflateEnd (&strm);
return FALSE;
}
}
deflateEnd (&strm);
return TRUE;
}
static gboolean
xcf_save_tile_zlib (XcfInfo *info,
GeglBuffer *buffer,
GeglRectangle *tile_rect,
const Babl *format,
GError **error)
{
gint bpp = babl_format_get_bytes_per_pixel (format);
gint tile_size = bpp * tile_rect->width * tile_rect->height;
guchar *tile_data = g_alloca (tile_size);
GError *tmp_error = NULL;
gegl_buffer_get (buffer, tile_rect, 1.0, format, tile_data,
GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
if (info->compression == COMPRESS_ZLIB_DELTA)
{
GeglRectangle test_rect;
gint test_size;
guint32 order;
gint prev_compressed_size = G_MAXINT;
/* we repeatedly differentiate a small portion of the tile data,
* referred to as the test data, and compress it, with the intention of
* finding the derivative order which minimizes the compressed data size.
*/
test_rect = *tile_rect;
test_rect.height *= XCF_SAVE_ZLIB_DELTA_TEST_SIZE_RATIO;
test_rect.height = MAX (test_rect.height, 1);
test_size = bpp * test_rect.width * test_rect.height;
g_assert (test_size <= tile_size);
for (order = 0; order <= XCF_SAVE_ZLIB_DELTA_MAX_ORDER; order++)
{
gint compressed_size;
/* do a dummy compression, that isn't actually written anywhere, to
* find the size of the compressed test data.
*/
if (! xcf_save_data_zlib (info, format, tile_data, test_size, FALSE,
prev_compressed_size, &compressed_size,
error))
{
return FALSE;
}
/* if the compressed data size has increased, compared to the
* previous iteration, stop looking and use the derivative order of
* the previous iteration.
*/
if (compressed_size >= prev_compressed_size)
{
order--;
break;
}
/* otherwise, try to differentiate the data, so that we compress the
* next-order derivative on the next iteration. if this fails, or if
* we reached the max order, break and use the current order.
*/
if (order == XCF_SAVE_ZLIB_DELTA_MAX_ORDER ||
! xcf_data_differentiate (tile_data, tile_data, test_size / bpp,
format, order, order + 1))
{
break;
}
prev_compressed_size = compressed_size;
}
/* we've overwritten the first test_size bytes of the tile data; reread
* them.
*/
gegl_buffer_get (buffer, &test_rect, 1.0, format, tile_data,
GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
/* calculate the derivative of the determined order of the tile data; if
* this fails, use the 0-th derivative (i.e., the original data).
*/
if (! xcf_data_differentiate (tile_data, tile_data, tile_size / bpp,
format, 0, order))
{
order = 0;
gegl_buffer_get (buffer, tile_rect, 1.0, format, tile_data,
GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
}
/* write the derivative order before the actual data */
xcf_write_int32_check_error (info, &order, 1);
GIMP_LOG (XCF, "delta encoding order: %d", order);
}
return xcf_save_data_zlib (info, format, tile_data, tile_size, TRUE,
G_MAXINT, NULL, error);
}
static gboolean
xcf_save_parasite (XcfInfo *info,
GimpParasite *parasite,

View File

@@ -17,10 +17,19 @@
#include "config.h"
#include <string.h>
#include <cairo.h>
#include <gegl.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include "libgimpbase/gimpbase.h"
#include "libgimpmath/gimpmath.h"
#include "core/core-types.h"
#include "gegl/gimp-babl.h"
#include "xcf-utils.h"
@@ -51,3 +60,205 @@ xcf_data_is_zero (const void *data,
return TRUE;
}
/* calculates the (discrete) derivative of a sequence of pixels. the input
* sequence, given by `src`, shall be a derivative of order `from_order` of
* some original sequence; the output sequence, written to `dest`, is the
* derivative of order `to_order`, which shall be greater than or equal to
* `from_order`, of the said original sequence.
*
* `dest` and `src` may be equal, but may not otherwise partially overlap.
*
* returns TRUE if successful, or FALSE otherwise. upon failure, the contents
* of `dest` are unspecified.
*/
gboolean
xcf_data_differentiate (void *dest,
const void *src,
gint n_pixels,
const Babl *format,
gint from_order,
gint to_order)
{
gint bpp;
gint n_components;
gint o;
gint i;
gint c;
g_return_val_if_fail (dest != NULL, FALSE);
g_return_val_if_fail (src != NULL, FALSE);
g_return_val_if_fail (n_pixels >= 0, FALSE);
g_return_val_if_fail (format != NULL, FALSE);
g_return_val_if_fail (from_order >= 0 && from_order <= to_order, FALSE);
if (to_order == 0 && n_pixels == 0)
return TRUE;
else if (to_order >= n_pixels)
return FALSE;
bpp = babl_format_get_bytes_per_pixel (format);
n_components = babl_format_get_n_components (format);
if (from_order == to_order)
{
if (dest != src)
memcpy (dest, src, n_pixels * bpp);
return TRUE;
}
#define XCF_DATA_DIFFERENTIATE(type) \
do \
{ \
type *d = dest; \
const type *s = src; \
\
if (dest != src) \
memcpy (dest, src, (from_order + 1) * bpp); \
\
for (o = from_order; o < to_order; o++) \
{ \
for (i = n_pixels - 1; i > o; i--) \
{ \
for (c = 0; c < n_components; c++) \
{ \
d[n_components * i + c] = s[n_components * i + c] - \
s[n_components * (i - 1) + c]; \
} \
} \
\
s = d; \
} \
} \
while (0)
switch (gimp_babl_format_get_component_type (format))
{
case GIMP_COMPONENT_TYPE_U8:
XCF_DATA_DIFFERENTIATE (guint8);
break;
case GIMP_COMPONENT_TYPE_U16:
case GIMP_COMPONENT_TYPE_HALF:
XCF_DATA_DIFFERENTIATE (guint16);
break;
case GIMP_COMPONENT_TYPE_U32:
case GIMP_COMPONENT_TYPE_FLOAT:
XCF_DATA_DIFFERENTIATE (guint32);
break;
case GIMP_COMPONENT_TYPE_DOUBLE:
XCF_DATA_DIFFERENTIATE (guint64);
break;
default:
return FALSE;
}
#undef XCF_DATA_DIFFERENTIATE
return TRUE;
}
/* calculates the (discrete) integral of a sequence of pixels. the input
* sequence, given by `src`, shall be a derivative of order `from_order` of
* some original sequence; the output sequence, written to `dest`, is the
* derivative of order `to_order`, which shall be less than or equal to
* `from_order`, of the said original sequence.
*
* `dest` and `src` may be equal, but may not otherwise partially overlap.
*
* returns TRUE if successful, or FALSE otherwise. upon failure, the contents
* of `dest` are unspecified.
*/
gboolean
xcf_data_integrate (void *dest,
const void *src,
gint n_pixels,
const Babl *format,
gint from_order,
gint to_order)
{
gint bpp;
gint n_components;
gint o;
gint i;
gint c;
g_return_val_if_fail (dest != NULL, FALSE);
g_return_val_if_fail (src != NULL, FALSE);
g_return_val_if_fail (n_pixels >= 0, FALSE);
g_return_val_if_fail (format != NULL, FALSE);
g_return_val_if_fail (to_order >= 0 && to_order <= from_order, FALSE);
if (from_order == 0 && n_pixels == 0)
return TRUE;
else if (from_order >= n_pixels)
return FALSE;
bpp = babl_format_get_bytes_per_pixel (format);
n_components = babl_format_get_n_components (format);
if (from_order == to_order)
{
if (dest != src)
memcpy (dest, src, n_pixels * bpp);
return TRUE;
}
#define XCF_DATA_INTEGRATE(type) \
do \
{ \
type *d = dest; \
const type *s = src; \
\
if (dest != src) \
memcpy (dest, src, from_order * bpp); \
\
for (o = from_order; o > to_order; o--) \
{ \
for (i = o; i < n_pixels; i++) \
{ \
for (c = 0; c < n_components; c++) \
{ \
d[n_components * i + c] = s[n_components * i + c] + \
d[n_components * (i - 1) + c]; \
} \
} \
\
s = d; \
} \
} \
while (0)
switch (gimp_babl_format_get_component_type (format))
{
case GIMP_COMPONENT_TYPE_U8:
XCF_DATA_INTEGRATE (guint8);
break;
case GIMP_COMPONENT_TYPE_U16:
case GIMP_COMPONENT_TYPE_HALF:
XCF_DATA_INTEGRATE (guint16);
break;
case GIMP_COMPONENT_TYPE_U32:
case GIMP_COMPONENT_TYPE_FLOAT:
XCF_DATA_INTEGRATE (guint32);
break;
case GIMP_COMPONENT_TYPE_DOUBLE:
XCF_DATA_INTEGRATE (guint64);
break;
default:
return FALSE;
}
#undef XCF_DATA_INTEGRATE
return TRUE;
}

View File

@@ -19,8 +19,21 @@
#define __XCF_UTILS_H__
gboolean xcf_data_is_zero (const void *data,
gint size);
gboolean xcf_data_is_zero (const void *data,
gint size);
gboolean xcf_data_differentiate (void *dest,
const void *src,
gint n_pixels,
const Babl *format,
gint from_order,
gint to_order);
gboolean xcf_data_integrate (void *dest,
const void *src,
gint n_pixels,
const Babl *format,
gint from_order,
gint to_order);
#endif /* __XCF_UTILS_H__ */

View File

@@ -80,7 +80,8 @@ static GimpXcfLoaderFunc * const xcf_loaders[] =
xcf_load_image, /* version 10 */
xcf_load_image, /* version 11 */
xcf_load_image, /* version 12 */
xcf_load_image /* version 13 */
xcf_load_image, /* version 13 */
xcf_load_image /* version 14 */
};
@@ -346,6 +347,7 @@ xcf_save_stream (Gimp *gimp,
const gchar *filename;
gboolean success = FALSE;
GError *my_error = NULL;
gboolean zlib_compression;
g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE);
g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
@@ -366,14 +368,21 @@ xcf_save_stream (Gimp *gimp,
info.progress = progress;
info.file = output_file;
if (gimp_image_get_xcf_compression (image))
info.compression = COMPRESS_ZLIB;
else
info.compression = COMPRESS_RLE;
zlib_compression = gimp_image_get_xcf_compression (image);
info.file_version = gimp_image_get_xcf_version (image,
info.compression ==
COMPRESS_ZLIB,
if (zlib_compression)
{
if (g_getenv ("GIMP_XCF_NO_DELTA_COMPRESSION"))
info.compression = COMPRESS_ZLIB;
else
info.compression = COMPRESS_ZLIB_DELTA;
}
else
{
info.compression = COMPRESS_RLE;
}
info.file_version = gimp_image_get_xcf_version (image, zlib_compression,
NULL, NULL);
if (info.file_version >= 11)

View File

@@ -714,8 +714,9 @@ PROP_COMPRESSION (essential)
byte comp Compression indicator; one of
0: No compression
1: RLE encoding
2: (Never used, but reserved for zlib compression)
2: zlib compression
3: (Never used, but reserved for some fractal compression)
4: Delta-encoded zlib compression
PROP_COMPRESSION defines the encoding of pixels in tile data blocks in the
entire XCF file. See chapter 7 for details.
@@ -724,11 +725,9 @@ PROP_COMPRESSION (essential)
small integer, PROP_COMPRESSION does _not_ pad the value to a full
32-bit integer.
Contemporary GIMP versions always write files with comp=1. It is unknown to
Contemporary GIMP versions always write files with comp>=1. It is unknown to
the author of this document whether versions that wrote completely
uncompressed (comp=0) files ever existed.
TODO: Why do we use zlib compression to compress only the whole file (.xcf.gz)
if it is a built-in feature?
PROP_GUIDES (editing state)
uint32 18 Type identification
@@ -1229,8 +1228,8 @@ Ceil(x) is the smallest integer not smaller than x.
The format of the data blocks pointed to by the tile pointers in the
level structure of hierarchy differs according to the value of the
PROP_COMPRESSION property of the main image structure. Current
GIMP versions always use RLE compression, but readers should nevertheless
be prepared to meet the older uncompressed format.
GIMP versions always use at least RLE compression, but readers should
nevertheless be prepared to meet the older uncompressed format.
Both formats assume the width, height and byte depth of the tile are
known from the context (namely, they are stored explicitly in the
@@ -1289,8 +1288,7 @@ The RLE encoding can cause degenerated encodings in which the original
data stream may double in size (or grow to arbitrarily large sizes if
(128,0,0) operations are inserted). Such encodings must be avoided, as
GIMP's XCF reader expects that the size of an encoded tile is
never more than 24 KB, which is only 1.5 times the unencoded size of a
64x64 RGBA tile.
never more than 1.5 times the unencoded size of a 64x64 RGBA tile.
A simple way for an XCF creator to avoid overflow is
a) never using opcode 0 (but instead opcode 255)
@@ -1298,9 +1296,37 @@ A simple way for an XCF creator to avoid overflow is
c) never emitting two "different bytes" opcodes next to each other
in the encoding of a single stream.
TODO: If each tile has a maximum of 64 pixels (resulting in a maximum of 64
bytes for each color in this tile), do values>64 and long runs apply at all?
zlib compressed tile data
-------------------------
In the zlib compressed format the raw tile data is compressed using the
DEFLATE algorithm.
Delta-encoded zlib compressed tile data
---------------------------------------
In the delta-encoded zlib compressed format the tile data is delta-encoded,
and the result is compressed using the DEFLATE algorithm.
The tile data is a sequence of pixels, manipulated arithmetically
componentwise, as unsigned integers; in particular, for floating-point
formats, the raw value representation is manipulated using integer
arithmeic. Delta encoding stores the sequence using its n-th-order
(discrete) derivative, rather than the original sequence (unless n = 0).
The n-th derivative of a sequence A of length m is defined as follows:
- if n = 0, the derivative is A;
- if n > 0, the derivative is [B[0], ..., B[n-1], B[n] - B[n-1], ...,
B[m-1] - B[m-2]], where B is the (n-1)-th derivative of A.
A delta-encoded zlib compressed tile begins with a (noncompressed) field
specifying the order of the derivative:
uint32 order Order of the tile-data derivative
Following this field is the actual derivative of the tile data. The
derivative is compressed using the DEFLATE algorithm.
8. MISCELLANEOUS
================