File indexing completed on 2024-12-22 04:10:29

0001 /*
0002  *  SPDX-FileCopyrightText: 2005-2006 Ariya Hidayat <ariya@kde.org>
0003  *  SPDX-FileCopyrightText: 2010 Dmitry Kazakov <dimula73@gmail.com>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "kis_lzf_compression.h"
0009 #include "kis_debug.h"
0010 
0011 
0012 #define HASH_LOG  12
0013 #define HASH_SIZE (1<< HASH_LOG)
0014 #define HASH_MASK  (HASH_SIZE-1)
0015 
0016 #if !defined _MSC_VER
0017 #pragma GCC diagnostic ignored "-Wcast-align"
0018 #endif
0019 #define UPDATE_HASH(v,p) { v = *((quint16*)p); v ^= *((quint16*)(p+1))^(v>>(16-HASH_LOG)); }
0020 
0021 #define MAX_COPY       32
0022 #define MAX_LEN       264  /* 256 + 8 */
0023 #define MAX_DISTANCE 8192
0024 
0025 
0026 // Lossless compression using LZF algorithm, this is faster on modern CPU than
0027 // the original implementation in http://liblzf.plan9.de/
0028 int lzff_compress(const void* input, int length, void* output, int /*maxout*/)
0029 {
0030     const quint8* ip = (const quint8*) input;
0031     const quint8* ip_limit = ip + length - MAX_COPY - 4;
0032     quint8* op = (quint8*) output;
0033 
0034     const quint8* htab[HASH_SIZE];
0035     const quint8** hslot;
0036     quint32 hval;
0037 
0038     quint8* ref;
0039     qint32 copy;
0040     qint32 len;
0041     qint32 distance;
0042     quint8* anchor;
0043 
0044     /* initializes hash table */
0045     for (hslot = htab; hslot < htab + HASH_SIZE; hslot++)
0046         *hslot = ip;
0047 
0048     /* we start with literal copy */
0049     copy = 0;
0050     *op++ = MAX_COPY - 1;
0051 
0052     /* main loop */
0053     while (ip < ip_limit) {
0054         /* find potential match */
0055         UPDATE_HASH(hval, ip);
0056         hslot = htab + (hval & HASH_MASK);
0057         ref = (quint8*) * hslot;
0058 
0059         /* update hash table */
0060         *hslot = ip;
0061 
0062         /* find itself? then it's no match */
0063         if (ip == ref)
0064             goto literal;
0065 
0066         /* is this a match? check the first 2 bytes */
0067         if (*((quint16*)ref) != *((quint16*)ip))
0068             goto literal;
0069 
0070         /* now check the 3rd byte */
0071         if (ref[2] != ip[2])
0072             goto literal;
0073 
0074         /* calculate distance to the match */
0075         distance = ip - ref;
0076 
0077         /* skip if too far away */
0078         if (distance >= MAX_DISTANCE)
0079             goto literal;
0080 
0081         /* here we have 3-byte matches */
0082         anchor = (quint8*)ip;
0083         len = 3;
0084         ref += 3;
0085         ip += 3;
0086 
0087         /* now we have to check how long the match is */
0088         if (ip < ip_limit - MAX_LEN) {
0089             while (len < MAX_LEN - 8) {
0090                 /* unroll 8 times */
0091                 if (*ref++ != *ip++) break;
0092                 if (*ref++ != *ip++) break;
0093                 if (*ref++ != *ip++) break;
0094                 if (*ref++ != *ip++) break;
0095                 if (*ref++ != *ip++) break;
0096                 if (*ref++ != *ip++) break;
0097                 if (*ref++ != *ip++) break;
0098                 if (*ref++ != *ip++) break;
0099                 len += 8;
0100             }
0101             ip--;
0102         }
0103         len = ip - anchor;
0104 
0105         /* just before the last non-matching byte */
0106         ip = anchor + len;
0107 
0108         /* if we have copied something, adjust the copy count */
0109         if (copy) {
0110             /* copy is biased, '0' means 1 byte copy */
0111             anchor = anchor - copy - 1;
0112             *(op - copy - 1) = copy - 1;
0113             copy = 0;
0114         } else
0115             /* back, to overwrite the copy count */
0116             op--;
0117 
0118         /* length is biased, '1' means a match of 3 bytes */
0119         len -= 2;
0120 
0121         /* distance is also biased */
0122         distance--;
0123 
0124         /* encode the match */
0125         if (len < 7)
0126             *op++ = (len << 5) + (distance >> 8);
0127         else {
0128             *op++ = (7 << 5) + (distance >> 8);
0129             *op++ = len - 7;
0130         }
0131         *op++ = (distance & 255);
0132 
0133         /* assuming next will be literal copy */
0134         *op++ = MAX_COPY - 1;
0135 
0136         /* update the hash at match boundary */
0137         --ip;
0138         UPDATE_HASH(hval, ip);
0139         htab[hval & HASH_MASK] = ip;
0140         ip++;
0141 
0142         continue;
0143 
0144     literal:
0145         *op++ = *ip++;
0146         copy++;
0147         if (copy >= MAX_COPY) {
0148             copy = 0;
0149             *op++ = MAX_COPY - 1;
0150         }
0151     }
0152 
0153     /* left-over as literal copy */
0154     ip_limit = (const quint8*)input + length;
0155     while (ip < ip_limit) {
0156         *op++ = *ip++;
0157         copy++;
0158         if (copy == MAX_COPY) {
0159             copy = 0;
0160             *op++ = MAX_COPY - 1;
0161         }
0162     }
0163 
0164     /* if we have copied something, adjust the copy length */
0165     if (copy)
0166         *(op - copy - 1) = copy - 1;
0167     else
0168         op--;
0169 
0170     return op - (quint8*)output;
0171 }
0172 
0173 int lzff_decompress(const void* input, int length, void* output, int maxout)
0174 {
0175     const quint8* ip = (const quint8*) input;
0176     const quint8* ip_limit  = ip + length - 1;
0177     quint8* op = (quint8*) output;
0178     quint8* op_limit = op + maxout;
0179     quint8* ref;
0180 
0181     while (ip < ip_limit) {
0182         quint32 ctrl = (*ip) + 1;
0183         quint32 ofs = ((*ip) & 31) << 8;
0184         quint32 len = (*ip++) >> 5;
0185 
0186         if (ctrl < 33) {
0187             /* literal copy */
0188             if (op + ctrl > op_limit)
0189                 return 0;
0190 
0191             /* crazy unrolling */
0192             if (ctrl) {
0193                 *op++ = *ip++;
0194                 ctrl--;
0195 
0196                 if (ctrl) {
0197                     *op++ = *ip++;
0198                     ctrl--;
0199 
0200                     if (ctrl) {
0201                         *op++ = *ip++;
0202                         ctrl--;
0203 
0204                         for (; ctrl; ctrl--)
0205                             *op++ = *ip++;
0206                     }
0207                 }
0208             }
0209         } else {
0210             /* back reference */
0211             len--;
0212             ref = op - ofs;
0213             ref--;
0214 
0215             if (len == 7 - 1)
0216                 len += *ip++;
0217 
0218             ref -= *ip++;
0219 
0220             if (op + len + 3 > op_limit)
0221                 return 0;
0222 
0223             if (ref < (quint8 *)output)
0224                 return 0;
0225 
0226             *op++ = *ref++;
0227             *op++ = *ref++;
0228             *op++ = *ref++;
0229             if (len)
0230                 for (; len; --len)
0231                     *op++ = *ref++;
0232         }
0233     }
0234 
0235     return op - (quint8*)output;
0236 }
0237 
0238 
0239 
0240 KisLzfCompression::KisLzfCompression()
0241 {
0242     /**
0243      * TODO: make working memory htab[HASH_SIZE] be in heap
0244      * and shared inside the class
0245      */
0246 
0247 }
0248 
0249 KisLzfCompression::~KisLzfCompression()
0250 {
0251 }
0252 
0253 qint32 KisLzfCompression::compress(const quint8* input, qint32 inputLength, quint8* output, qint32 outputLength)
0254 {
0255     return lzff_compress(input, inputLength, output, outputLength);
0256 }
0257 
0258 qint32 KisLzfCompression::decompress(const quint8* input, qint32 inputLength, quint8* output, qint32 outputLength)
0259 {
0260     return lzff_decompress(input, inputLength, output, outputLength);
0261 }
0262 
0263 qint32 KisLzfCompression::outputBufferSize(qint32 dataSize)
0264 {
0265     // WARNING: Copy-pasted from LZO samples, do not know how to prove it
0266     return dataSize + dataSize / 16 + 64 + 3;
0267 }