/**************************************************************
*
* cr3.h (C-Munipack project)
* Copyright (C) 2021 David Motl, dmotl@volny.cz
*
* This code is adaptation of the code include in LibRaw version 0.21
*
* I removed everything that is not needed to decode CR3 files and
* I made the code compatible with ANSI C. The memory allocation uses
* the cmpack* routines in order to track memory leaks.
*
* I release this under the same license as the LibRaw, see below.
*
***************************************************************/

/*
Libraw licence

Copyright (C) 2018-2019 Alexey Danilchenko
Copyright (C) 2019 Alex Tutubalin, LibRaw LLC

LibRaw is free software; you can redistribute it and/or modify
it under the terms of the one of two licenses as you choose:

1. GNU LESSER GENERAL PUBLIC LICENSE version 2.1
   (See file LICENSE.LGPL provided in LibRaw distribution archive for details).

2. COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0
   (See file LICENSE.CDDL provided in LibRaw distribution archive for details).

 */
#include "crx.h"

#include <stdlib.h>
#include <string.h>
#include <limits.h>

#include "comfun.h"

#ifdef _abs
#undef _abs
#endif
#define _abs(x) (((x) ^ ((int32_t)(x) >> 31)) - ((int32_t)(x) >> 31))

/*
 * Search the mask data from most significant bit (MSB) to least significant bit (LSB) for a set bit (1).
 * index of a bit set (starting from 0) and 0 if no set bits were found
 * Replacement for _BitScanReverse
 */

static int BSR(uint32_t Mask)
{
    int j;
    for (j = 31; j > 0; --j) {
        if (Mask & (1 << j))
            return j;
    }
    return 0;
}

/*
 * Byte swap for 32-bit unsigned integer
 */
static uint32_t BSUL(uint32_t val)
{
    return ((val >> 24) & 0xFF) | ((val >> 8) & 0xFF00) | ((val << 8) & 0xFF0000) | (val << 24);
}

/* this should be divisible by 4 */
#define CRX_BUF_SIZE 0x10000

typedef struct 
{
  uint8_t mdatBuf[CRX_BUF_SIZE];
  uint32_t mdatSize;
  uint32_t curBufOffset;
  uint32_t curPos;
  uint32_t curBufSize;
  uint32_t bitData;
  int32_t bitsLeft;
  FILE *input;
  long long input_size;
} CrxBitstream;

typedef struct
{
  CrxBitstream bitStream;
  int16_t subbandWidth;
  int16_t subbandHeight;
  int32_t roundedBitsMask;
  int32_t roundedBits;
  int16_t curLine;
  int32_t *lineBuf0;
  int32_t *lineBuf1;
  int32_t *lineBuf2;
  int32_t sParam;
  int32_t kParam;
  int32_t *paramData;
  int32_t *nonProgrData;
  int supportsPartial;
} CrxBandParam;

typedef struct
{
  int32_t *subband0Buf;
  int32_t *subband1Buf;
  int32_t *subband2Buf;
  int32_t *subband3Buf;
  int32_t *lineBuf[8];
  int16_t curLine;
  int16_t curH;
  int8_t fltTapH;
  int16_t height;
  int16_t width;
} CrxWaveletTransform;

typedef struct
{
  CrxBandParam *bandParam;
  uint32_t mdatOffset;
  uint8_t *bandBuf;
  uint16_t width;
  uint16_t height;
  int32_t qParam;
  int32_t kParam;
  uint32_t qStepBase;
  uint32_t qStepMult;
  int supportsPartial;
  int32_t bandSize;
  uint32_t dataSize;
  int32_t dataOffset;
  short rowStartAddOn;
  short rowEndAddOn;
  short colStartAddOn;
  short colEndAddOn;
  short levelShift;
} CrxSubband;

typedef struct
{
  uint8_t *compBuf;
  CrxSubband *subBands;
  CrxWaveletTransform *wvltTransform;
  int8_t compNumber;
  int32_t dataOffset;
  int32_t compSize;
  int supportsPartial;
  int32_t roundedBitsMask;
  int8_t tileFlag;
} CrxPlaneComp;

typedef struct
{
  uint32_t *qStepTbl;
  int width;
  int height;
} CrxQStep;

typedef struct
{
  CrxPlaneComp *comps;
  int8_t tileFlag;
  int8_t tileNumber;
  int32_t dataOffset;
  int32_t tileSize;
  uint16_t width;
  uint16_t height;
  int hasQPData;
  CrxQStep *qStep;
  uint32_t mdatQPDataSize;
  uint16_t mdatExtraSize;
} CrxTile;

typedef struct
{
  uint8_t nPlanes;
  uint16_t planeWidth;
  uint16_t planeHeight;
  uint8_t samplePrecision;
  uint8_t subbandCount;
  uint8_t levels;
  uint8_t nBits;
  uint8_t encType;
  uint8_t tileCols;
  uint8_t tileRows;
  CrxTile *tiles;
  uint32_t mdatOffset;
  uint32_t mdatSize;
  int16_t *outBufs[4]; /* one per plane */
  int16_t *planeBuf;
  FILE *input;
  uint32_t input_size;
} CrxImage;

enum TileFlags
{
  E_HAS_TILES_ON_THE_RIGHT = 1,
  E_HAS_TILES_ON_THE_LEFT = 2,
  E_HAS_TILES_ON_THE_BOTTOM = 4,
  E_HAS_TILES_ON_THE_TOP = 8
};

int32_t exCoefNumTbl[144] = {1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0,
                             0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0,
                             0, 0, 1, 2, 2, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 2, 2,
                             1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 2, 2, 2, 2, 1, 1, 1,
                             1, 2, 2, 1, 1, 1, 1, 2, 2, 1, 1, 0, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1};

int32_t q_step_tbl[8] = {0x28, 0x2D, 0x33, 0x39, 0x40, 0x48};

uint32_t JS[32] = {1,    1,    1,     1,     2,     2,     2,      2,      4,      4,     4,
                   4,    8,    8,     8,     8,     0x10,  0x10,   0x20,   0x20,   0x40,  0x40,
                   0x80, 0x80, 0x100, 0x200, 0x400, 0x800, 0x1000, 0x2000, 0x4000, 0x8000};

uint32_t J[32] = {0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2,    2,    3,    3,    3,    3,
                  4, 4, 5, 5, 6, 6, 7, 7, 8, 9, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F};

static unsigned sgetn(int n, const unsigned char* s)
{
    unsigned result = 0;
    while (n-- > 0)
        result = (result << 8) | (*s++);
    return result;
}

static int _crxFillBuffer(CrxBitstream* bitStrm)
{
    if (bitStrm->curPos >= bitStrm->curBufSize && bitStrm->mdatSize)
    {
        bitStrm->curPos = 0;
        bitStrm->curBufOffset += bitStrm->curBufSize;
        fseek(bitStrm->input, bitStrm->curBufOffset, SEEK_SET);
        uint32_t bytes = (bitStrm->mdatSize < CRX_BUF_SIZE ? bitStrm->mdatSize : CRX_BUF_SIZE);
        bitStrm->curBufSize = (uint32_t)fread(bitStrm->mdatBuf, 1, bytes, bitStrm->input);
        if (bitStrm->curBufSize != bytes)
            return -1;
        bitStrm->mdatSize -= bitStrm->curBufSize;
    }
    return 0;
}

static int _crxBitstreamGetZeros(CrxBitstream* bitStrm, uint32_t* nbits)
{
    int exitCode = 0;
    uint32_t nextData = 0;

    *nbits = 0;

    if (bitStrm->bitData)
    {
        int nonZeroBit = BSR(bitStrm->bitData);
        *nbits = 31 - nonZeroBit;
        bitStrm->bitData <<= 32 - nonZeroBit;
        bitStrm->bitsLeft -= 32 - nonZeroBit;
    }
    else
    {
        uint32_t bitsLeft = bitStrm->bitsLeft;
        while (1)
        {
            while (bitStrm->curPos + 4 <= bitStrm->curBufSize)
            {
                nextData = BSUL(*(uint32_t*)(bitStrm->mdatBuf + bitStrm->curPos));
                bitStrm->curPos += 4;
                exitCode = _crxFillBuffer(bitStrm);
                if (exitCode != 0)
                    return exitCode;
                if (nextData)
                {
                    int nonZeroBit = BSR(nextData);
                    if (nonZeroBit) {
                        *nbits = bitsLeft + 31 - nonZeroBit;
                        bitStrm->bitData = (nextData << (32 - nonZeroBit));
                        bitStrm->bitsLeft = nonZeroBit;
                    }
                    else {
                        *nbits = bitsLeft + 31;
                        bitStrm->bitData = 0;
                        bitStrm->bitsLeft = 0;
                    }
                    return 0;
                }
                bitsLeft += 32;
            }
            if (bitStrm->curBufSize < bitStrm->curPos + 1)
                break; /* error */
            nextData = bitStrm->mdatBuf[bitStrm->curPos++];
            exitCode = _crxFillBuffer(bitStrm);
            if (exitCode != 0)
                return exitCode;
            if (nextData)
                break;
            bitsLeft += 8;
        }
        int nonZeroBit = BSR(nextData);
        if (nonZeroBit) {
            *nbits = (uint32_t)(bitsLeft + 7 - nonZeroBit);
            bitStrm->bitData = (uint32_t)(nextData << (32 - nonZeroBit));
            bitStrm->bitsLeft = nonZeroBit;
        }
        else {
            *nbits = (uint32_t)(bitsLeft + 7);
            bitStrm->bitData = 0;
            bitStrm->bitsLeft = 0;
        }
    }
    return 0;
}

static int _crxBitstreamGetBits(CrxBitstream* bitStrm, int bits, uint32_t* nbits)
{
    int exitCode = 0, bitsLeft = bitStrm->bitsLeft;
    uint32_t bitData = bitStrm->bitData;
    uint32_t nextWord;
    uint8_t nextByte;

    if (bitsLeft < bits)
    {
        /* get them from stream */
        if (bitStrm->curPos + 4 <= bitStrm->curBufSize)
        {
            nextWord = BSUL(*(uint32_t*)(bitStrm->mdatBuf + bitStrm->curPos));
            bitStrm->curPos += 4;
            exitCode = _crxFillBuffer(bitStrm);
            if (exitCode != 0)
                return exitCode;
            bitStrm->bitsLeft = 32 - (bits - bitsLeft);
            *nbits = ((nextWord >> bitsLeft) | bitData) >> (32 - bits);
            bitStrm->bitData = nextWord << (bits - bitsLeft);
            return 0;
        }
        /* less than a word left - read byte at a time */
        do
        {
            if (bitStrm->curPos >= bitStrm->curBufSize)
                break; /* error */
            bitsLeft += 8;
            nextByte = bitStrm->mdatBuf[bitStrm->curPos++];
            exitCode = _crxFillBuffer(bitStrm);
            if (exitCode != 0)
                return exitCode;
            bitData |= nextByte << (32 - bitsLeft);
        } while (bitsLeft < bits);
    }
    *nbits = bitData >> (32 - bits); /* 32-bits */
    bitStrm->bitData = bitData << bits;
    bitStrm->bitsLeft = bitsLeft - bits;
    return 0;
}

static int32_t _crxPrediction(int32_t left, int32_t top, int32_t deltaH, int32_t deltaV)
{
    switch ((((deltaV < 0) ^ (deltaH < 0)) << 1) + ((left < top) ^ (deltaH < 0)))
    {
    case 0:
        return left + deltaH;
    case 1:
        return left + deltaH;
    case 2:
        return left;
    case 3:
        return top;
    default:
        /* Should not happen */
        return 0;
    }
}

static int32_t _crxPredictKParameter(int32_t prevK, int32_t bitCode, int32_t maxVal)
{
    int32_t newKParam = prevK - (bitCode < (1 << prevK >> 1)) + ((bitCode >> prevK) > 2) + ((bitCode >> prevK) > 5);

    return !maxVal || newKParam < maxVal ? newKParam : maxVal;
}

static int _crxDecodeSymbolL1(CrxBandParam* param, int32_t doMedianPrediction, int32_t notEOL)
{
    uint32_t bitCode = 0;

    if (doMedianPrediction)
    {
        int32_t symb[4];

        int32_t delta = param->lineBuf0[1] - param->lineBuf0[0];
        symb[2] = param->lineBuf1[0];
        symb[0] = symb[1] = delta + symb[2];
        symb[3] = param->lineBuf0[1];

        param->lineBuf1[1] = symb[(((param->lineBuf0[0] < param->lineBuf1[0]) ^ (delta < 0)) << 1) +
            ((param->lineBuf1[0] < param->lineBuf0[1]) ^ (delta < 0))];
    }
    else
        param->lineBuf1[1] = param->lineBuf0[1];

    /* get next error symbol */
    int exitCode = _crxBitstreamGetZeros(&param->bitStream, &bitCode);
    if (exitCode != 0)
        return exitCode;

    if (bitCode >= 41) {
        uint32_t nbits = 0;
        exitCode = _crxBitstreamGetBits(&param->bitStream, 21, &nbits);
        if (exitCode != 0)
            return exitCode;
        bitCode = nbits;
    }
    else if (param->kParam) {
        uint32_t nbits = 0;
        exitCode = _crxBitstreamGetBits(&param->bitStream, param->kParam, &nbits);
        if (exitCode != 0)
            return exitCode;
        bitCode = nbits | (bitCode << param->kParam);
    }

    /* add converted (+/-) error code to predicted value */
    int32_t sVal = ((bitCode >> 1) ^ ((bitCode & 1) ? UINT_MAX : 0));
    param->lineBuf1[1] += sVal;

    /* for not end of the line - use one symbol ahead to estimate next K */
    if (notEOL)
    {
        int32_t nextDelta = (param->lineBuf0[2] - param->lineBuf0[1]) << 1;
        bitCode = (bitCode + _abs(nextDelta)) >> 1;
        ++param->lineBuf0;
    }

    /* update K parameter */
    param->kParam = _crxPredictKParameter(param->kParam, bitCode, 15);

    ++param->lineBuf1;
    return 0;
}

static int _crxDecodeLine(CrxBandParam* param)
{
    int exitCode = 0, length = param->subbandWidth;
    uint32_t nbits = 0;

    param->lineBuf1[0] = param->lineBuf0[1];
    for (; length > 1; --length)
    {
        if (param->lineBuf1[0] != param->lineBuf0[1] || param->lineBuf1[0] != param->lineBuf0[2])
        {
            exitCode = _crxDecodeSymbolL1(param, 1, 1);
            if (exitCode != 0)
                return exitCode;
        }
        else
        {
            int nSyms = 0;
            if (_crxBitstreamGetBits(&param->bitStream, 1, &nbits) == 0 && nbits != 0)
            {
                nSyms = 1;
                while (_crxBitstreamGetBits(&param->bitStream, 1, &nbits) == 0 && nbits != 0)
                {
                    nSyms += JS[param->sParam];
                    if (nSyms > length)
                    {
                        nSyms = length;
                        break;
                    }
                    if (param->sParam < 31)
                        ++param->sParam;
                    if (nSyms == length)
                        break;
                }

                if (nSyms < length)
                {
                    if (J[param->sParam]) {
                        exitCode = _crxBitstreamGetBits(&param->bitStream, J[param->sParam], &nbits);
                        if (exitCode != 0)
                            return exitCode;
                        nSyms += nbits;
                    }
                    if (param->sParam > 0)
                        --param->sParam;
                    if (nSyms > length)
                        return -1;
                }

                length -= nSyms;

                /* copy symbol nSyms times */
                param->lineBuf0 += nSyms;

                /* copy symbol nSyms times */
                while (nSyms-- > 0)
                {
                    param->lineBuf1[1] = param->lineBuf1[0];
                    ++param->lineBuf1;
                }
            }

            if (length > 0) {
                exitCode = _crxDecodeSymbolL1(param, 0, (length > 1));
                if (exitCode != 0)
                    return exitCode;
            }
        }
    }

    if (length == 1) {
        exitCode = _crxDecodeSymbolL1(param, 1, 0);
        if (exitCode != 0)
            return exitCode;
    }

    param->lineBuf1[1] = param->lineBuf1[0] + 1;

    return 0;
}

static int _crxDecodeSymbolL1Rounded(CrxBandParam* param, int32_t doSym, int32_t doCode)
{
    int32_t sym = param->lineBuf0[1];

    if (doSym)
    {
        /* calculate the next symbol gradient */
        int32_t symb[4];
        int32_t deltaH = param->lineBuf0[1] - param->lineBuf0[0];
        symb[2] = param->lineBuf1[0];
        symb[0] = symb[1] = deltaH + symb[2];
        symb[3] = param->lineBuf0[1];
        sym = symb[(((param->lineBuf0[0] < param->lineBuf1[0]) ^ (deltaH < 0)) << 1) +
            ((param->lineBuf1[0] < param->lineBuf0[1]) ^ (deltaH < 0))];
    }

    uint32_t bitCode = 0;
    int exitCode = _crxBitstreamGetZeros(&param->bitStream, &bitCode);
    if (exitCode != 0)
        return exitCode;

    if (bitCode >= 41) {
        uint32_t nbits = 0;
        exitCode = _crxBitstreamGetBits(&param->bitStream, 21, &nbits);
        if (exitCode != 0)
            return exitCode;
        bitCode = nbits;
    }
    else if (param->kParam) {
        uint32_t nbits = 0;
        exitCode = _crxBitstreamGetBits(&param->bitStream, param->kParam, &nbits);
        if (exitCode != 0)
            return exitCode;
        bitCode = nbits | (bitCode << param->kParam);
    }

    int32_t code = ((bitCode >> 1) ^ ((bitCode & 1) ? UINT_MAX : 0));
    param->lineBuf1[1] = param->roundedBitsMask * 2 * code + (code >> 31) + sym;

    if (doCode)
    {
        if (param->lineBuf0[2] > param->lineBuf0[1])
            code = (param->lineBuf0[2] - param->lineBuf0[1] + param->roundedBitsMask - 1) >> param->roundedBits;
        else
            code = -((param->lineBuf0[1] - param->lineBuf0[2] + param->roundedBitsMask) >> param->roundedBits);

        param->kParam = _crxPredictKParameter(param->kParam, (bitCode + 2 * _abs(code)) >> 1, 15);
    }
    else
        param->kParam = _crxPredictKParameter(param->kParam, bitCode, 15);

    ++param->lineBuf1;
    return 0;
}

static int _crxDecodeLineRounded(CrxBandParam* param)
{
    int32_t exitCode = 0, valueReached = 0;

    param->lineBuf0[0] = param->lineBuf0[1];
    param->lineBuf1[0] = param->lineBuf0[1];
    int32_t length = param->subbandWidth;

    for (; length > 1; --length)
    {
        if (_abs(param->lineBuf0[2] - param->lineBuf0[1]) > param->roundedBitsMask)
        {
            exitCode = _crxDecodeSymbolL1Rounded(param, 1, 1);
            if (exitCode != 0)
                return exitCode;
            ++param->lineBuf0;
            valueReached = 1;
        }
        else if (valueReached || _abs(param->lineBuf0[0] - param->lineBuf1[0]) > param->roundedBitsMask)
        {
            exitCode = _crxDecodeSymbolL1Rounded(param, 1, 1);
            if (exitCode != 0)
                return exitCode;
            ++param->lineBuf0;
            valueReached = 0;
        }
        else
        {
            int nSyms = 0;
            uint32_t nbits = 0;
            if (_crxBitstreamGetBits(&param->bitStream, 1, &nbits) == 0 && nbits != 0)
            {
                nSyms = 1;
                while (_crxBitstreamGetBits(&param->bitStream, 1, &nbits) == 0 && nbits != 0)
                {
                    nSyms += JS[param->sParam];
                    if (nSyms > length)
                    {
                        nSyms = length;
                        break;
                    }
                    if (param->sParam < 31)
                        ++param->sParam;
                    if (nSyms == length)
                        break;
                }
                if (nSyms < length)
                {
                    if (J[param->sParam]) {
                        exitCode = _crxBitstreamGetBits(&param->bitStream, J[param->sParam], &nbits);
                        if (exitCode != 0)
                            return exitCode;
                        nSyms += nbits;
                    }
                    if (param->sParam > 0)
                        --param->sParam;
                }
                if (nSyms > length)
                    return -1;
            }
            length -= nSyms;

            /* copy symbol nSyms times */
            param->lineBuf0 += nSyms;

            /* copy symbol nSyms times */
            while (nSyms-- > 0)
            {
                param->lineBuf1[1] = param->lineBuf1[0];
                ++param->lineBuf1;
            }

            if (length > 1)
            {
                exitCode = _crxDecodeSymbolL1Rounded(param, 0, 1);
                if (exitCode != 0)
                    return exitCode;
                ++param->lineBuf0;
                valueReached = _abs(param->lineBuf0[1] - param->lineBuf0[0]) > param->roundedBitsMask;
            }
            else if (length == 1) {
                exitCode = _crxDecodeSymbolL1Rounded(param, 0, 0);
                if (exitCode != 0)
                    return exitCode;
            }
        }
    }

    if (length == 1) {
        exitCode = _crxDecodeSymbolL1Rounded(param, 1, 0);
        if (exitCode != 0)
            return exitCode;
    }

    param->lineBuf1[1] = param->lineBuf1[0] + 1;

    return 0;
}

static int _crxDecodeLineNoRefPrevLine(CrxBandParam* param)
{
    int32_t i = 0;

    for (; i < param->subbandWidth - 1; i++)
    {
        if (param->lineBuf0[i + 2] | param->lineBuf0[i + 1] | param->lineBuf1[i])
        {
            uint32_t bitCode = 0;
            int exitCode = _crxBitstreamGetZeros(&param->bitStream, &bitCode);
            if (exitCode != 0)
                return exitCode;

            if (bitCode >= 41) {
                uint32_t nbits = 0;
                exitCode = _crxBitstreamGetBits(&param->bitStream, 21, &nbits);
                if (exitCode != 0)
                    return exitCode;
                bitCode = nbits;
            }
            else if (param->kParam) {
                uint32_t nbits = 0;
                exitCode = _crxBitstreamGetBits(&param->bitStream, param->kParam, &nbits);
                if (exitCode != 0)
                    return exitCode;
                bitCode = nbits | (bitCode << param->kParam);
            }

            int32_t sVal = ((bitCode >> 1) ^ ((bitCode & 1) ? UINT_MAX : 0));
            param->lineBuf1[i + 1] = sVal;
            param->kParam = _crxPredictKParameter(param->kParam, bitCode, 0);
            if (param->lineBuf2[i + 1] - param->kParam <= 1)
            {
                if (param->kParam >= 15)
                    param->kParam = 15;
            }
            else
                ++param->kParam;
        }
        else
        {
            uint32_t nbits = 0;
            int nSyms = 0;
            if (_crxBitstreamGetBits(&param->bitStream, 1, &nbits) == 0 && nbits != 0)
            {
                nSyms = 1;
                if (i != param->subbandWidth - 1)
                {
                    while (_crxBitstreamGetBits(&param->bitStream, 1, &nbits) == 0 && nbits != 0)
                    {
                        nSyms += JS[param->sParam];
                        if (i + nSyms > param->subbandWidth)
                        {
                            nSyms = param->subbandWidth - i;
                            break;
                        }
                        if (param->sParam < 31)
                            ++param->sParam;
                        if (i + nSyms == param->subbandWidth)
                            break;
                    }
                    if (i + nSyms < param->subbandWidth)
                    {
                        if (J[param->sParam]) {
                            int exitCode = _crxBitstreamGetBits(&param->bitStream, J[param->sParam], &nbits);
                            if (exitCode != 0)
                                return exitCode;
                            nSyms += nbits;
                        }
                        if (param->sParam > 0)
                            --param->sParam;
                    }
                    if (i + nSyms > param->subbandWidth) 
                        return -1;
                }
            }
            else if (i > param->subbandWidth)
                return -1;

            if (nSyms > 0)
            {
                memset(param->lineBuf1 + i + 1, 0, nSyms * sizeof(int32_t));
                memset(param->lineBuf2 + i, 0, nSyms * sizeof(int32_t));
                i += nSyms;
            }

            if (i >= param->subbandWidth - 1)
            {
                if (i == param->subbandWidth - 1)
                {
                    uint32_t bitCode = 0;
                    int exitCode = _crxBitstreamGetZeros(&param->bitStream, &bitCode);
                    if (exitCode != 0)
                        return exitCode;

                    if (bitCode >= 41) {
                        uint32_t nbits = 0;
                        exitCode = _crxBitstreamGetBits(&param->bitStream, 21, &nbits);
                        if (exitCode != 0)
                            return exitCode;
                        bitCode = nbits;
                    }
                    else if (param->kParam) {
                        uint32_t nbits = 0;
                        exitCode = _crxBitstreamGetBits(&param->bitStream, param->kParam, &nbits);
                        if (exitCode != 0)
                            return exitCode;
                        bitCode = nbits | (bitCode << param->kParam);
                    }

                    int32_t sVal = (((bitCode + 1) >> 1) ^ (((bitCode + 1) & 1) ? UINT_MAX : 0));
                    param->lineBuf1[i + 1] = sVal;
                    param->kParam = _crxPredictKParameter(param->kParam, bitCode, 15);
                    param->lineBuf2[i] = param->kParam;
                }
                continue;
            }
            else
            {
                uint32_t bitCode = 0;
                int exitCode = _crxBitstreamGetZeros(&param->bitStream, &bitCode);
                if (exitCode != 0)
                    return exitCode;

                if (bitCode >= 41) {
                    uint32_t nbits = 0;
                    exitCode = _crxBitstreamGetBits(&param->bitStream, 21, &nbits);
                    if (exitCode != 0)
                        return exitCode;
                    bitCode = nbits;
                }
                else if (param->kParam) {
                    uint32_t nbits = 0;
                    exitCode = _crxBitstreamGetBits(&param->bitStream, param->kParam, &nbits);
                    if (exitCode != 0)
                        return exitCode;
                    bitCode = nbits | (bitCode << param->kParam);
                }
                int32_t sVal = (((bitCode + 1) >> 1) ^ (((bitCode + 1) & 1) ? UINT_MAX : 0));
                param->lineBuf1[i + 1] = sVal;
                param->kParam = _crxPredictKParameter(param->kParam, bitCode, 0);
                if (param->lineBuf2[i + 1] - param->kParam <= 1)
                {
                    if (param->kParam >= 15)
                        param->kParam = 15;
                }
                else
                    ++param->kParam;
            }
        }
        param->lineBuf2[i] = param->kParam;
    }
    if (i == param->subbandWidth - 1)
    {
        uint32_t bitCode = 0;
        int exitCode = _crxBitstreamGetZeros(&param->bitStream, &bitCode);
        if (exitCode != 0)
            return exitCode;

        if (bitCode >= 41) {
            uint32_t nbits = 0;
            exitCode = _crxBitstreamGetBits(&param->bitStream, 21, &nbits);
            if (exitCode != 0)
                return exitCode;
            bitCode = nbits;
        }
        else if (param->kParam) {
            uint32_t nbits = 0;
            exitCode = _crxBitstreamGetBits(&param->bitStream, param->kParam, &nbits);
            if (exitCode != 0)
                return exitCode;
            bitCode = nbits | (bitCode << param->kParam);
        }
        int32_t sVal = ((bitCode >> 1) ^ ((bitCode & 1) ? UINT_MAX : 0));
        param->lineBuf1[i + 1] = sVal;
        param->kParam = _crxPredictKParameter(param->kParam, bitCode, 15);
        param->lineBuf2[i] = param->kParam;
    }

    return 0;
}

int crxDecodeTopLine(CrxBandParam* param)
{
    param->lineBuf1[0] = 0;

    int32_t length = param->subbandWidth;

    /* read the line from bitstream */
    for (; length > 1; --length)
    {
        if (param->lineBuf1[0])
            param->lineBuf1[1] = param->lineBuf1[0];
        else
        {
            int nSyms = 0;
            uint32_t nbits = 0;
            if (_crxBitstreamGetBits(&param->bitStream, 1, &nbits) == 0 && nbits != 0)
            {
                nSyms = 1;
                while (_crxBitstreamGetBits(&param->bitStream, 1, &nbits) == 0 && nbits != 0)
                {
                    nSyms += JS[param->sParam];
                    if (nSyms > length)
                    {
                        nSyms = length;
                        break;
                    }
                    if (param->sParam < 31)
                        ++param->sParam;
                    if (nSyms == length)
                        break;
                }
                if (nSyms < length)
                {
                    if (J[param->sParam]) {
                        uint32_t nbits = 0;
                        int exitCode = _crxBitstreamGetBits(&param->bitStream, J[param->sParam], &nbits);
                        if (exitCode != 0)
                            return exitCode;
                        nSyms += nbits;
                    }
                    if (param->sParam > 0)
                        --param->sParam;
                    if (nSyms > length)
                        return -1;
                }

                length -= nSyms;

                /* copy symbol nSyms times */
                while (nSyms-- > 0)
                {
                    param->lineBuf1[1] = param->lineBuf1[0];
                    ++param->lineBuf1;
                }

                if (length <= 0)
                    break;
            }

            param->lineBuf1[1] = 0;
        }

        uint32_t bitCode = 0;
        int exitCode = _crxBitstreamGetZeros(&param->bitStream, &bitCode);
        if (exitCode != 0)
            return exitCode;

        if (bitCode >= 41) {
            uint32_t nbits = 0;
            exitCode = _crxBitstreamGetBits(&param->bitStream, 21, &nbits);
            if (exitCode != 0)
                return exitCode;
            bitCode = nbits;
        }
        else if (param->kParam) {
            uint32_t nbits = 0;
            exitCode = _crxBitstreamGetBits(&param->bitStream, param->kParam, &nbits);
            if (exitCode != 0)
                return exitCode;
            bitCode = nbits | (bitCode << param->kParam);
        }
        int32_t sVal = ((bitCode >> 1) ^ ((bitCode & 1) ? UINT_MAX : 0));
        param->lineBuf1[1] += sVal;
        param->kParam = _crxPredictKParameter(param->kParam, bitCode, 15);
        ++param->lineBuf1;
    }

    if (length == 1)
    {
        param->lineBuf1[1] = param->lineBuf1[0];

        uint32_t bitCode = 0;
        int exitCode = _crxBitstreamGetZeros(&param->bitStream, &bitCode);
        if (exitCode != 0)
            return exitCode;

        if (bitCode >= 41) {
            uint32_t nbits = 0;
            exitCode = _crxBitstreamGetBits(&param->bitStream, 21, &nbits);
            if (exitCode != 0)
                return exitCode;
            bitCode = nbits;
        }
        else if (param->kParam) {
            uint32_t nbits = 0;
            exitCode = _crxBitstreamGetBits(&param->bitStream, param->kParam, &nbits);
            if (exitCode != 0)
                return exitCode;
            bitCode = nbits | (bitCode << param->kParam);
        }
        int32_t sVal = ((bitCode >> 1) ^ ((bitCode & 1) ? UINT_MAX : 0));
        param->lineBuf1[1] += sVal;
        param->kParam = _crxPredictKParameter(param->kParam, bitCode, 15);
        ++param->lineBuf1;
    }

    param->lineBuf1[1] = param->lineBuf1[0] + 1;

    return 0;
}

int crxDecodeTopLineRounded(CrxBandParam* param)
{
    param->lineBuf1[0] = 0;

    int32_t length = param->subbandWidth;

    /* read the line from bitstream */
    for (; length > 1; --length)
    {
        if (_abs(param->lineBuf1[0]) > param->roundedBitsMask)
            param->lineBuf1[1] = param->lineBuf1[0];
        else
        {
            uint32_t nbits = 0;
            int nSyms = 0;
            if (_crxBitstreamGetBits(&param->bitStream, 1, &nbits) == 0 && nbits != 0)
            {
                nSyms = 1;
                while (_crxBitstreamGetBits(&param->bitStream, 1, &nbits) == 0 && nbits != 0)
                {
                    nSyms += JS[param->sParam];
                    if (nSyms > length)
                    {
                        nSyms = length;
                        break;
                    }
                    if (param->sParam < 31)
                        ++param->sParam;
                    if (nSyms == length)
                        break;
                }
                if (nSyms < length)
                {
                    if (J[param->sParam]) {
                        uint32_t nbits = 0;
                        int exitCode = _crxBitstreamGetBits(&param->bitStream, J[param->sParam], &nbits);
                        if (exitCode != 0)
                            return exitCode;
                        nSyms += nbits;
                    }
                    if (param->sParam > 0)
                        --param->sParam;
                    if (nSyms > length)
                        return -1;
                }
            }

            length -= nSyms;

            /* copy symbol nSyms times */
            while (nSyms-- > 0)
            {
                param->lineBuf1[1] = param->lineBuf1[0];
                ++param->lineBuf1;
            }

            if (length <= 0)
                break;

            param->lineBuf1[1] = 0;
        }

        uint32_t bitCode = 0;
        int exitCode = _crxBitstreamGetZeros(&param->bitStream, &bitCode);
        if (exitCode != 0)
            return exitCode;

        if (bitCode >= 41) {
            uint32_t nbits = 0;
            exitCode = _crxBitstreamGetBits(&param->bitStream, 21, &nbits);
            if (exitCode != 0)
                return exitCode;
            bitCode = nbits;
        }
        else if (param->kParam) {
            uint32_t nbits = 0;
            exitCode = _crxBitstreamGetBits(&param->bitStream, param->kParam, &nbits);
            if (exitCode != 0)
                return exitCode;
            bitCode = nbits | (bitCode << param->kParam);
        }

        int32_t sVal = ((bitCode >> 1) ^ ((bitCode & 1) ? UINT_MAX : 0));
        param->lineBuf1[1] += param->roundedBitsMask * 2 * sVal + (sVal >> 31);
        param->kParam = _crxPredictKParameter(param->kParam, bitCode, 15);
        ++param->lineBuf1;
    }

    if (length == 1)
    {
        uint32_t bitCode = 0;
        int exitCode = _crxBitstreamGetZeros(&param->bitStream, &bitCode);
        if (exitCode != 0)
            return exitCode;

        if (bitCode >= 41) {
            uint32_t nbits = 0;
            exitCode = _crxBitstreamGetBits(&param->bitStream, 21, &nbits);
            if (exitCode != 0)
                return exitCode;
            bitCode = nbits;
        }
        else if (param->kParam) {
            uint32_t nbits = 0;
            exitCode = _crxBitstreamGetBits(&param->bitStream, param->kParam, &nbits);
            if (exitCode != 0)
                return exitCode;
            bitCode = nbits | (bitCode << param->kParam);
        }
        int32_t sVal = ((bitCode >> 1) ^ ((bitCode & 1) ? UINT_MAX : 0));
        param->lineBuf1[1] += param->roundedBitsMask * 2 * sVal + (sVal >> 31);
        param->kParam = _crxPredictKParameter(param->kParam, bitCode, 15);
        ++param->lineBuf1;
    }

    param->lineBuf1[1] = param->lineBuf1[0] + 1;

    return 0;
}

static int _crxDecodeTopLineNoRefPrevLine(CrxBandParam* param)
{
    param->lineBuf0[0] = 0;
    param->lineBuf1[0] = 0;
    int32_t length = param->subbandWidth;
    for (; length > 1; --length)
    {
        if (param->lineBuf1[0])
        {
            uint32_t bitCode = 0;
            int exitCode = _crxBitstreamGetZeros(&param->bitStream, &bitCode);
            if (exitCode != 0)
                return exitCode;

            if (bitCode >= 41) {
                uint32_t nbits = 0;
                exitCode = _crxBitstreamGetBits(&param->bitStream, 21, &nbits);
                if (exitCode != 0)
                    return exitCode;
                bitCode = nbits;
            }
            else if (param->kParam) {
                uint32_t nbits = 0;
                exitCode = _crxBitstreamGetBits(&param->bitStream, param->kParam, &nbits);
                if (exitCode != 0)
                    return exitCode;
                bitCode = nbits | (bitCode << param->kParam);
            }
            int32_t sVal = ((bitCode >> 1) ^ ((bitCode & 1) ? UINT_MAX : 0));
            param->lineBuf1[1] = sVal;
            param->kParam = _crxPredictKParameter(param->kParam, bitCode, 15);
        }
        else
        {
            int nSyms = 0;
            uint32_t nbits = 0;
            if (_crxBitstreamGetBits(&param->bitStream, 1, &nbits) == 0 && nbits != 0)
            {
                nSyms = 1;
                while (_crxBitstreamGetBits(&param->bitStream, 1, &nbits) == 0 && nbits != 0)
                {
                    nSyms += JS[param->sParam];
                    if (nSyms > length)
                    {
                        nSyms = length;
                        break;
                    }
                    if (param->sParam < 31)
                        ++param->sParam;
                    if (nSyms == length)
                        break;
                }
                if (nSyms < length)
                {
                    if (J[param->sParam]) {
                        uint32_t nbits = 0;
                        int exitCode = _crxBitstreamGetBits(&param->bitStream, J[param->sParam], &nbits);
                        if (exitCode != 0)
                            return exitCode;
                        nSyms += nbits;
                    }
                    if (param->sParam > 0)
                        --param->sParam;
                    if (nSyms > length)
                        return -1;
                }
            }

            length -= nSyms;

            /* copy symbol nSyms times */
            while (nSyms-- > 0)
            {
                param->lineBuf2[0] = 0;
                param->lineBuf1[1] = 0;
                ++param->lineBuf1;
                ++param->lineBuf2;
            }

            if (length <= 0)
                break;

            uint32_t bitCode = 0;
            int exitCode = _crxBitstreamGetZeros(&param->bitStream, &bitCode);
            if (exitCode != 0)
                return exitCode;

            if (bitCode >= 41) {
                uint32_t nbits = 0;
                exitCode = _crxBitstreamGetBits(&param->bitStream, 21, &nbits);
                if (exitCode != 0)
                    return exitCode;
                bitCode = nbits;
            }
            else if (param->kParam) {
                uint32_t nbits = 0;
                exitCode = _crxBitstreamGetBits(&param->bitStream, param->kParam, &nbits);
                if (exitCode != 0)
                    return exitCode;
                bitCode = nbits | (bitCode << param->kParam);
            }
            int32_t sVal = (((bitCode + 1) >> 1) ^ (((bitCode + 1) & 1) ? UINT_MAX : 0));
            param->lineBuf1[1] = sVal;
            param->kParam = _crxPredictKParameter(param->kParam, bitCode, 15);
        }
        param->lineBuf2[0] = param->kParam;
        ++param->lineBuf2;
        ++param->lineBuf1;
    }

    if (length == 1)
    {
        uint32_t bitCode = 0;
        int exitCode = _crxBitstreamGetZeros(&param->bitStream, &bitCode);
        if (exitCode != 0)
            return exitCode;

        if (bitCode >= 41) {
            uint32_t nbits = 0;
            exitCode = _crxBitstreamGetBits(&param->bitStream, 21, &nbits);
            if (exitCode != 0)
                return exitCode;
            bitCode = nbits;
        }
        else if (param->kParam) {
            uint32_t nbits = 0;
            exitCode = _crxBitstreamGetBits(&param->bitStream, param->kParam, &nbits);
            if (exitCode != 0)
                return exitCode;
            bitCode = nbits | (bitCode << param->kParam);
        }
        int32_t sVal = ((bitCode >> 1) ^ ((bitCode & 1) ? UINT_MAX : 0));
        param->lineBuf1[1] = sVal;
        param->kParam = _crxPredictKParameter(param->kParam, bitCode, 15);
        param->lineBuf2[0] = param->kParam;
        ++param->lineBuf1;
    }

    param->lineBuf1[1] = 0;

    return 0;
}

static int _crxDecodeLine2(CrxBandParam* param, uint8_t* bandBuf)
{
    if (!param || !bandBuf)
        return -1;
    if (param->curLine >= param->subbandHeight)
        return -1;

    if (param->curLine == 0)
    {
        int32_t lineLength = param->subbandWidth + 2;

        param->sParam = 0;
        param->kParam = 0;
        if (param->supportsPartial)
        {
            if (param->roundedBitsMask <= 0)
            {
                param->lineBuf0 = (int32_t*)param->paramData;
                param->lineBuf1 = param->lineBuf0 + lineLength;
                int32_t* lineBuf = param->lineBuf1 + 1;
                if (crxDecodeTopLine(param))
                    return -1;
                memcpy(bandBuf, lineBuf, param->subbandWidth * sizeof(int32_t));
                ++param->curLine;
            }
            else
            {
                param->roundedBits = 1;
                if (param->roundedBitsMask & ~1)
                {
                    while (param->roundedBitsMask >> param->roundedBits)
                        ++param->roundedBits;
                }
                param->lineBuf0 = (int32_t*)param->paramData;
                param->lineBuf1 = param->lineBuf0 + lineLength;
                int32_t* lineBuf = param->lineBuf1 + 1;
                if (crxDecodeTopLineRounded(param))
                    return -1;
                memcpy(bandBuf, lineBuf, param->subbandWidth * sizeof(int32_t));
                ++param->curLine;
            }
        }
        else
        {
            param->lineBuf2 = (int32_t*)param->nonProgrData;
            param->lineBuf0 = (int32_t*)param->paramData;
            param->lineBuf1 = param->lineBuf0 + lineLength;
            int32_t* lineBuf = param->lineBuf1 + 1;
            int exitCode = _crxDecodeTopLineNoRefPrevLine(param);
            if (exitCode != 0)
                return exitCode;
            memcpy(bandBuf, lineBuf, param->subbandWidth * sizeof(int32_t));
            ++param->curLine;
        }
    }
    else if (!param->supportsPartial)
    {
        int32_t lineLength = param->subbandWidth + 2;
        param->lineBuf2 = (int32_t*)param->nonProgrData;
        if (param->curLine & 1)
        {
            param->lineBuf1 = (int32_t*)param->paramData;
            param->lineBuf0 = param->lineBuf1 + lineLength;
        }
        else
        {
            param->lineBuf0 = (int32_t*)param->paramData;
            param->lineBuf1 = param->lineBuf0 + lineLength;
        }
        int32_t* lineBuf = param->lineBuf1 + 1;
        int exitCode = _crxDecodeLineNoRefPrevLine(param);
        if (exitCode != 0)
            return exitCode;
        memcpy(bandBuf, lineBuf, param->subbandWidth * sizeof(int32_t));
        ++param->curLine;
    }
    else if (param->roundedBitsMask <= 0)
    {
        int32_t lineLength = param->subbandWidth + 2;
        if (param->curLine & 1)
        {
            param->lineBuf1 = (int32_t*)param->paramData;
            param->lineBuf0 = param->lineBuf1 + lineLength;
        }
        else
        {
            param->lineBuf0 = (int32_t*)param->paramData;
            param->lineBuf1 = param->lineBuf0 + lineLength;
        }
        int32_t* lineBuf = param->lineBuf1 + 1;
        int exitCode = _crxDecodeLine(param);
        if (exitCode != 0)
            return exitCode;
        memcpy(bandBuf, lineBuf, param->subbandWidth * sizeof(int32_t));
        ++param->curLine;
    }
    else
    {
        int32_t lineLength = param->subbandWidth + 2;
        if (param->curLine & 1)
        {
            param->lineBuf1 = (int32_t*)param->paramData;
            param->lineBuf0 = param->lineBuf1 + lineLength;
        }
        else
        {
            param->lineBuf0 = (int32_t*)param->paramData;
            param->lineBuf1 = param->lineBuf0 + lineLength;
        }
        int32_t* lineBuf = param->lineBuf1 + 1;
        int exitCode = _crxDecodeLineRounded(param);
        if (exitCode != 0)
            return exitCode;
        memcpy(bandBuf, lineBuf, param->subbandWidth * sizeof(int32_t));
        ++param->curLine;
    }
    return 0;
}

static int crxUpdateQparam(CrxSubband* subband)
{
    uint32_t bitCode = 0;
    int exitCode = _crxBitstreamGetZeros(&subband->bandParam->bitStream, &bitCode);
    if (exitCode != 0)
        return exitCode;

    if (bitCode >= 23) {
        uint32_t nbits = 0;
        exitCode = _crxBitstreamGetBits(&subband->bandParam->bitStream, 8, &nbits);
        if (exitCode != 0)
            return exitCode;
        bitCode = nbits;
    }
    else if (subband->kParam) {
        uint32_t nbits = 0;
        exitCode = _crxBitstreamGetBits(&subband->bandParam->bitStream, subband->kParam, &nbits);
        if (exitCode != 0)
            return exitCode;
        bitCode = nbits | (bitCode << subband->kParam);
    }

    int32_t sVal = ((bitCode >> 1) ^ ((bitCode & 1) ? UINT_MAX : 0));
    subband->qParam += sVal; /* converting encoded to signed integer */
    subband->kParam = _crxPredictKParameter(subband->kParam, bitCode, 0);
    if (subband->kParam > 7)
        return -1;
    return 0;
}

static int getSubbandRow(CrxSubband* band, int row)
{
    if (row >= band->rowStartAddOn) {
        if (row >= band->height - band->rowEndAddOn)
            return band->height - band->rowEndAddOn - band->rowStartAddOn - 1;
        return row - band->rowEndAddOn;
    }
    return 0;
}

static int _crxDecodeLineWithIQuantization(CrxSubband* band, CrxQStep* qStep)
{
    if (!band->dataSize)
    {
        memset(band->bandBuf, 0, band->bandSize);
        return 0;
    }

    if (band->supportsPartial && !qStep && crxUpdateQparam(band))
        return -1;

    int exitCode = _crxDecodeLine2(band->bandParam, band->bandBuf);
    if (exitCode != 0)
        return exitCode;

    if (band->width <= 0)
        return 0;

    /* update band buffers */
    int32_t* bandBuf = (int32_t*)band->bandBuf;
    if (qStep)
    {
        /* new version */
        uint32_t* qStepTblPtr = &qStep->qStepTbl[qStep->width * getSubbandRow(band, band->bandParam->curLine - 1)];
        int i, lastIdx;

        for (i = 0; i < band->colStartAddOn; ++i)
        {
            uint32_t quantVal = band->qStepBase + ((qStepTblPtr[0] * band->qStepMult) >> 3);
            if (quantVal < 1)
                quantVal = 1;
            if (quantVal > 0x168000)
                quantVal = 0x168000;
            bandBuf[i] *= quantVal;
        }

        for (i = band->colStartAddOn; i < band->width - band->colEndAddOn; ++i)
        {
            uint32_t quantVal = band->qStepBase + ((qStepTblPtr[(i - band->colStartAddOn) >> band->levelShift] * band->qStepMult) >> 3);
            if (quantVal < 1)
                quantVal = 1;
            if (quantVal > 0x168000)
                quantVal = 0x168000;
            bandBuf[i] *= quantVal;
        }
        lastIdx = (band->width - band->colEndAddOn - band->colStartAddOn - 1) >> band->levelShift;
        for (i = band->width - band->colEndAddOn; i < band->width; ++i)
        {
            uint32_t quantVal = band->qStepBase + ((qStepTblPtr[lastIdx] * band->qStepMult) >> 3);
            if (quantVal < 1)
                quantVal = 1;
            if (quantVal > 0x168000)
                quantVal = 0x168000;
            bandBuf[i] *= quantVal;
        }
    }
    else
    {
        /* prev. version */
        int32_t qScale = q_step_tbl[band->qParam % 6] >> (6 - band->qParam / 6);
        if (band->qParam / 6 >= 6)
            qScale = q_step_tbl[band->qParam % 6] * (1 << (band->qParam / 6 + 26));

        if (qScale != 1) {
            int i;
            for (i = 0; i < band->width; ++i)
                bandBuf[i] *= qScale;
        }
    }

    return 0;
}

static void _crxHorizontal53(int32_t *lineBufLA, int32_t *lineBufLB, CrxWaveletTransform *wavelet, uint32_t tileFlag)
{
    int i;
    int32_t *band0Buf = wavelet->subband0Buf;
    int32_t *band1Buf = wavelet->subband1Buf;
    int32_t *band2Buf = wavelet->subband2Buf;
    int32_t *band3Buf = wavelet->subband3Buf;

  if (wavelet->width <= 1)
  {
    lineBufLA[0] = band0Buf[0];
    lineBufLB[0] = band2Buf[0];
  }
  else
  {
    if (tileFlag & E_HAS_TILES_ON_THE_LEFT)
    {
      lineBufLA[0] = band0Buf[0] - ((band1Buf[0] + band1Buf[1] + 2) >> 2);
      lineBufLB[0] = band2Buf[0] - ((band3Buf[0] + band3Buf[1] + 2) >> 2);
      ++band1Buf;
      ++band3Buf;
    }
    else
    {
      lineBufLA[0] = band0Buf[0] - ((band1Buf[0] + 1) >> 1);
      lineBufLB[0] = band2Buf[0] - ((band3Buf[0] + 1) >> 1);
    }
    ++band0Buf;
    ++band2Buf;

    for (i = 0; i < wavelet->width - 3; i += 2)
    {
      int32_t delta = band0Buf[0] - ((band1Buf[0] + band1Buf[1] + 2) >> 2);
      lineBufLA[1] = band1Buf[0] + ((delta + lineBufLA[0]) >> 1);
      lineBufLA[2] = delta;

      delta = band2Buf[0] - ((band3Buf[0] + band3Buf[1] + 2) >> 2);
      lineBufLB[1] = band3Buf[0] + ((delta + lineBufLB[0]) >> 1);
      lineBufLB[2] = delta;

      ++band0Buf;
      ++band1Buf;
      ++band2Buf;
      ++band3Buf;
      lineBufLA += 2;
      lineBufLB += 2;
    }
    if (tileFlag & E_HAS_TILES_ON_THE_RIGHT)
    {
      int32_t deltaA = band0Buf[0] - ((band1Buf[0] + band1Buf[1] + 2) >> 2);
      lineBufLA[1] = band1Buf[0] + ((deltaA + lineBufLA[0]) >> 1);

      int32_t deltaB = band2Buf[0] - ((band3Buf[0] + band3Buf[1] + 2) >> 2);
      lineBufLB[1] = band3Buf[0] + ((deltaB + lineBufLB[0]) >> 1);

      if (wavelet->width & 1)
      {
        lineBufLA[2] = deltaA;
        lineBufLB[2] = deltaB;
      }
    }
    else if (wavelet->width & 1)
    {
      lineBufLA[1] = band1Buf[0] + ((lineBufLA[0] + band0Buf[0] - ((band1Buf[0] + 1) >> 1)) >> 1);
      lineBufLA[2] = band0Buf[0] - ((band1Buf[0] + 1) >> 1);

      lineBufLB[1] = band3Buf[0] + ((lineBufLB[0] + band2Buf[0] - ((band3Buf[0] + 1) >> 1)) >> 1);
      lineBufLB[2] = band2Buf[0] - ((band3Buf[0] + 1) >> 1);
    }
    else
    {
      lineBufLA[1] = lineBufLA[0] + band1Buf[0];
      lineBufLB[1] = lineBufLB[0] + band3Buf[0];
    }
  }
}

static int32_t * _crxIdwt53FilterGetLine(CrxPlaneComp *comp, int32_t level)
{
  int32_t *result = comp->wvltTransform[level]
                        .lineBuf[(comp->wvltTransform[level].fltTapH - comp->wvltTransform[level].curH + 5) % 5 + 3];
  comp->wvltTransform[level].curH--;
  return result;
}

static int _crxIdwt53FilterDecode(CrxPlaneComp *comp, int32_t level, CrxQStep *qStep)
{
  if (comp->wvltTransform[level].curH)
    return 0;

  CrxSubband *sband = comp->subBands + 3 * level;
  CrxQStep *qStepLevel = qStep ? qStep + level : 0;

  if (comp->wvltTransform[level].height - 3 <= comp->wvltTransform[level].curLine &&
      !(comp->tileFlag & E_HAS_TILES_ON_THE_BOTTOM))
  {
    if (comp->wvltTransform[level].height & 1)
    {
      if (level)
      {
        if (_crxIdwt53FilterDecode(comp, level - 1, qStep))
          return -1;
      }
      else if (_crxDecodeLineWithIQuantization(sband, qStepLevel))
        return -1;

      if (_crxDecodeLineWithIQuantization(sband + 1, qStepLevel))
        return -1;
    }
  }
  else
  {
    if (level)
    {
      if (_crxIdwt53FilterDecode(comp, level - 1, qStep))
        return -1;
    }
    else if (_crxDecodeLineWithIQuantization(sband, qStepLevel)) /* LL band */
      return -1;

    if (_crxDecodeLineWithIQuantization(sband + 1, qStepLevel) || /* HL band */
        _crxDecodeLineWithIQuantization(sband + 2, qStepLevel) || /* LH band */
        _crxDecodeLineWithIQuantization(sband + 3, qStepLevel))   /* HH band */
      return -1;
  }

  return 0;
}

static int crxIdwt53FilterTransform(CrxPlaneComp* comp, uint32_t level)
{
    CrxWaveletTransform* wavelet = comp->wvltTransform + level;
    int i;

    if (wavelet->curH)
        return 0;

    if (wavelet->curLine >= wavelet->height - 3)
    {
        if (!(comp->tileFlag & E_HAS_TILES_ON_THE_BOTTOM))
        {
            if (wavelet->height & 1)
            {
                if (level)
                {
                    if (!wavelet[-1].curH)
                        if (crxIdwt53FilterTransform(comp, level - 1))
                            return -1;
                    wavelet->subband0Buf = _crxIdwt53FilterGetLine(comp, level - 1);
                }
                int32_t* band0Buf = wavelet->subband0Buf;
                int32_t* band1Buf = wavelet->subband1Buf;
                int32_t* lineBufH0 = wavelet->lineBuf[wavelet->fltTapH + 3];
                int32_t* lineBufH1 = wavelet->lineBuf[(wavelet->fltTapH + 1) % 5 + 3];
                int32_t* lineBufH2 = wavelet->lineBuf[(wavelet->fltTapH + 2) % 5 + 3];

                int32_t* lineBufL0 = wavelet->lineBuf[0];
                int32_t* lineBufL1 = wavelet->lineBuf[1];
                wavelet->lineBuf[1] = wavelet->lineBuf[2];
                wavelet->lineBuf[2] = lineBufL1;

                /* process L bands */
                if (wavelet->width <= 1)
                {
                    lineBufL0[0] = band0Buf[0];
                }
                else
                {
                    if (comp->tileFlag & E_HAS_TILES_ON_THE_LEFT)
                    {
                        lineBufL0[0] = band0Buf[0] - ((band1Buf[0] + band1Buf[1] + 2) >> 2);
                        ++band1Buf;
                    }
                    else
                    {
                        lineBufL0[0] = band0Buf[0] - ((band1Buf[0] + 1) >> 1);
                    }
                    ++band0Buf;
                    for (i = 0; i < wavelet->width - 3; i += 2)
                    {
                        int32_t delta = band0Buf[0] - ((band1Buf[0] + band1Buf[1] + 2) >> 2);
                        lineBufL0[1] = band1Buf[0] + ((lineBufL0[0] + delta) >> 1);
                        lineBufL0[2] = delta;
                        ++band0Buf;
                        ++band1Buf;
                        lineBufL0 += 2;
                    }
                    if (comp->tileFlag & E_HAS_TILES_ON_THE_RIGHT)
                    {
                        int32_t delta = band0Buf[0] - ((band1Buf[0] + band1Buf[1] + 2) >> 2);
                        lineBufL0[1] = band1Buf[0] + ((lineBufL0[0] + delta) >> 1);
                        if (wavelet->width & 1)
                            lineBufL0[2] = delta;
                    }
                    else if (wavelet->width & 1)
                    {
                        int32_t delta = band0Buf[0] - ((band1Buf[0] + 1) >> 1);
                        lineBufL0[1] = band1Buf[0] + ((lineBufL0[0] + delta) >> 1);
                        lineBufL0[2] = delta;
                    }
                    else
                        lineBufL0[1] = band1Buf[0] + lineBufL0[0];
                }

                /* process H bands */
                lineBufL0 = wavelet->lineBuf[0];
                lineBufL1 = wavelet->lineBuf[1];
                for (i = 0; i < wavelet->width; i++)
                {
                    int32_t delta = lineBufL0[i] - ((lineBufL1[i] + 1) >> 1);
                    lineBufH1[i] = lineBufL1[i] + ((delta + lineBufH0[i]) >> 1);
                    lineBufH2[i] = delta;
                }
                wavelet->curH += 3;
                wavelet->curLine += 3;
                wavelet->fltTapH = (wavelet->fltTapH + 3) % 5;
            }
            else {
                int32_t* lineBufL2 = wavelet->lineBuf[2];
                int32_t* lineBufH0 = wavelet->lineBuf[wavelet->fltTapH + 3];
                int32_t* lineBufH1 = wavelet->lineBuf[(wavelet->fltTapH + 1) % 5 + 3];
                wavelet->lineBuf[1] = lineBufL2;
                wavelet->lineBuf[2] = wavelet->lineBuf[1];

                for (i = 0; i < wavelet->width; i++)
                    lineBufH1[i] = lineBufH0[i] + lineBufL2[i];

                wavelet->curH += 2;
                wavelet->curLine += 2;
                wavelet->fltTapH = (wavelet->fltTapH + 2) % 5;
            }
        }
    }
    else {
        if (level) {
            if (!wavelet[-1].curH && crxIdwt53FilterTransform(comp, level - 1))
                return -1;
            wavelet->subband0Buf = _crxIdwt53FilterGetLine(comp, level - 1);
        }

        int32_t* band0Buf = wavelet->subband0Buf;
        int32_t* band1Buf = wavelet->subband1Buf;
        int32_t* band2Buf = wavelet->subband2Buf;
        int32_t* band3Buf = wavelet->subband3Buf;

        int32_t* lineBufL0 = wavelet->lineBuf[0];
        int32_t* lineBufL1 = wavelet->lineBuf[1];
        int32_t* lineBufL2 = wavelet->lineBuf[2];
        int32_t* lineBufH0 = wavelet->lineBuf[wavelet->fltTapH + 3];
        int32_t* lineBufH1 = wavelet->lineBuf[(wavelet->fltTapH + 1) % 5 + 3];
        int32_t* lineBufH2 = wavelet->lineBuf[(wavelet->fltTapH + 2) % 5 + 3];

        wavelet->lineBuf[1] = wavelet->lineBuf[2];
        wavelet->lineBuf[2] = lineBufL1;

        /* process L bands */
        if (wavelet->width <= 1) {
            lineBufL0[0] = band0Buf[0];
            lineBufL1[0] = band2Buf[0];
        }
        else {
            if (comp->tileFlag & E_HAS_TILES_ON_THE_LEFT) {
                lineBufL0[0] = band0Buf[0] - ((band1Buf[0] + band1Buf[1] + 2) >> 2);
                lineBufL1[0] = band2Buf[0] - ((band3Buf[0] + band3Buf[1] + 2) >> 2);
                ++band1Buf;
                ++band3Buf;
            }
            else {
                lineBufL0[0] = band0Buf[0] - ((band1Buf[0] + 1) >> 1);
                lineBufL1[0] = band2Buf[0] - ((band3Buf[0] + 1) >> 1);
            }
            ++band0Buf;
            ++band2Buf;
            for (i = 0; i < wavelet->width - 3; i += 2) {
                int32_t delta = band0Buf[0] - ((band1Buf[0] + band1Buf[1] + 2) >> 2);
                lineBufL0[1] = band1Buf[0] + ((delta + lineBufL0[0]) >> 1);
                lineBufL0[2] = delta;

                delta = band2Buf[0] - ((band3Buf[0] + band3Buf[1] + 2) >> 2);
                lineBufL1[1] = band3Buf[0] + ((delta + lineBufL1[0]) >> 1);
                lineBufL1[2] = delta;

                ++band0Buf;
                ++band1Buf;
                ++band2Buf;
                ++band3Buf;
                lineBufL0 += 2;
                lineBufL1 += 2;
            }
            if (comp->tileFlag & E_HAS_TILES_ON_THE_RIGHT) {
                int32_t deltaA = band0Buf[0] - ((band1Buf[0] + band1Buf[1] + 2) >> 2);
                lineBufL0[1] = band1Buf[0] + ((deltaA + lineBufL0[0]) >> 1);

                int32_t deltaB = band2Buf[0] - ((band3Buf[0] + band3Buf[1] + 2) >> 2);
                lineBufL1[1] = band3Buf[0] + ((deltaB + lineBufL1[0]) >> 1);

                if (wavelet->width & 1)
                {
                    lineBufL0[2] = deltaA;
                    lineBufL1[2] = deltaB;
                }
            }
            else if (wavelet->width & 1) {
                int32_t delta = band0Buf[0] - ((band1Buf[0] + 1) >> 1);
                lineBufL0[1] = band1Buf[0] + ((delta + lineBufL0[0]) >> 1);
                lineBufL0[2] = delta;

                delta = band2Buf[0] - ((band3Buf[0] + 1) >> 1);
                lineBufL1[1] = band3Buf[0] + ((delta + lineBufL1[0]) >> 1);
                lineBufL1[2] = delta;
            }
            else {
                lineBufL0[1] = lineBufL0[0] + band1Buf[0];
                lineBufL1[1] = lineBufL1[0] + band3Buf[0];
            }
        }

        /* process H bands */
        lineBufL0 = wavelet->lineBuf[0];
        lineBufL1 = wavelet->lineBuf[1];
        lineBufL2 = wavelet->lineBuf[2];
        for (i = 0; i < wavelet->width; i++)
        {
            int32_t delta = lineBufL0[i] - ((lineBufL2[i] + lineBufL1[i] + 2) >> 2);
            lineBufH1[i] = lineBufL1[i] + ((delta + lineBufH0[i]) >> 1);
            lineBufH2[i] = delta;
        }
        if (wavelet->curLine >= wavelet->height - 3 && wavelet->height & 1)
        {
            wavelet->curH += 3;
            wavelet->curLine += 3;
            wavelet->fltTapH = (wavelet->fltTapH + 3) % 5;
        }
        else {
            wavelet->curH += 2;
            wavelet->curLine += 2;
            wavelet->fltTapH = (wavelet->fltTapH + 2) % 5;
        }
    }

    return 0;
}

static int crxIdwt53FilterInitialize(CrxPlaneComp* comp, int32_t level, CrxQStep* qStep)
{
    int curLevel, curBand, i;

    if (level == 0)
        return 0;

    for (curLevel = 0, curBand = 0; curLevel < level; curLevel++, curBand += 3)
    {
        CrxQStep* qStepLevel = qStep ? qStep + curLevel : 0;
        CrxWaveletTransform* wavelet = comp->wvltTransform + curLevel;
        if (curLevel)
            wavelet[0].subband0Buf = _crxIdwt53FilterGetLine(comp, curLevel - 1);
        else if (_crxDecodeLineWithIQuantization(comp->subBands + curBand, qStepLevel))
            return -1;

        int32_t* lineBufH0 = wavelet->lineBuf[wavelet->fltTapH + 3];
        if (wavelet->height > 1)
        {
            if (_crxDecodeLineWithIQuantization(comp->subBands + curBand + 1, qStepLevel) ||
                _crxDecodeLineWithIQuantization(comp->subBands + curBand + 2, qStepLevel) ||
                _crxDecodeLineWithIQuantization(comp->subBands + curBand + 3, qStepLevel))
                return -1;

            int32_t* lineBufL0 = wavelet->lineBuf[0];
            int32_t* lineBufL1 = wavelet->lineBuf[1];
            int32_t* lineBufL2 = wavelet->lineBuf[2];

            if (comp->tileFlag & E_HAS_TILES_ON_THE_TOP)
            {
                _crxHorizontal53(lineBufL0, wavelet->lineBuf[1], wavelet, comp->tileFlag);
                if (_crxDecodeLineWithIQuantization(comp->subBands + curBand + 3, qStepLevel) ||
                    _crxDecodeLineWithIQuantization(comp->subBands + curBand + 2, qStepLevel))
                    return -1;

                int32_t* band2Buf = wavelet->subband2Buf;
                int32_t* band3Buf = wavelet->subband3Buf;

                /* process L band */
                if (wavelet->width <= 1)
                    lineBufL2[0] = band2Buf[0];
                else
                {
                    if (comp->tileFlag & E_HAS_TILES_ON_THE_LEFT)
                    {
                        lineBufL2[0] = band2Buf[0] - ((band3Buf[0] + band3Buf[1] + 2) >> 2);
                        ++band3Buf;
                    }
                    else
                        lineBufL2[0] = band2Buf[0] - ((band3Buf[0] + 1) >> 1);

                    ++band2Buf;

                    for (i = 0; i < wavelet->width - 3; i += 2)
                    {
                        int32_t delta = band2Buf[0] - ((band3Buf[0] + band3Buf[1] + 2) >> 2);
                        lineBufL2[1] = band3Buf[0] + ((lineBufL2[0] + delta) >> 1);
                        lineBufL2[2] = delta;

                        ++band2Buf;
                        ++band3Buf;
                        lineBufL2 += 2;
                    }
                    if (comp->tileFlag & E_HAS_TILES_ON_THE_RIGHT)
                    {
                        int32_t delta = band2Buf[0] - ((band3Buf[0] + band3Buf[1] + 2) >> 2);
                        lineBufL2[1] = band3Buf[0] + ((lineBufL2[0] + delta) >> 1);
                        if (wavelet->width & 1)
                            lineBufL2[2] = delta;
                    }
                    else if (wavelet->width & 1)
                    {
                        int32_t delta = band2Buf[0] - ((band3Buf[0] + 1) >> 1);

                        lineBufL2[1] = band3Buf[0] + ((lineBufL2[0] + delta) >> 1);
                        lineBufL2[2] = delta;
                    }
                    else
                    {
                        lineBufL2[1] = band3Buf[0] + lineBufL2[0];
                    }
                }

                /* process H band */
                for (i = 0; i < wavelet->width; i++)
                    lineBufH0[i] = lineBufL0[i] - ((lineBufL1[i] + lineBufL2[i] + 2) >> 2);
            }
            else
            {
                _crxHorizontal53(lineBufL0, wavelet->lineBuf[2], wavelet, comp->tileFlag);
                for (i = 0; i < wavelet->width; i++)
                    lineBufH0[i] = lineBufL0[i] - ((lineBufL2[i] + 1) >> 1);
            }

            if (_crxIdwt53FilterDecode(comp, curLevel, qStep) || crxIdwt53FilterTransform(comp, curLevel))
                return -1;
        }
        else
        {
            if (_crxDecodeLineWithIQuantization(comp->subBands + curBand + 1, qStepLevel))
                return -1;

            int32_t* band0Buf = wavelet->subband0Buf;
            int32_t* band1Buf = wavelet->subband1Buf;

            /* process H band */
            if (wavelet->width <= 1)
                lineBufH0[0] = band0Buf[0];
            else
            {
                if (comp->tileFlag & E_HAS_TILES_ON_THE_LEFT)
                {
                    lineBufH0[0] = band0Buf[0] - ((band1Buf[0] + band1Buf[1] + 2) >> 2);
                    ++band1Buf;
                }
                else
                    lineBufH0[0] = band0Buf[0] - ((band1Buf[0] + 1) >> 1);

                ++band0Buf;

                for (i = 0; i < wavelet->width - 3; i += 2)
                {
                    int32_t delta = band0Buf[0] - ((band1Buf[0] + band1Buf[1] + 2) >> 2);
                    lineBufH0[1] = band1Buf[0] + ((lineBufH0[0] + delta) >> 1);
                    lineBufH0[2] = delta;

                    ++band0Buf;
                    ++band1Buf;
                    lineBufH0 += 2;
                }

                if (comp->tileFlag & E_HAS_TILES_ON_THE_RIGHT)
                {
                    int32_t delta = band0Buf[0] - ((band1Buf[0] + band1Buf[1] + 2) >> 2);
                    lineBufH0[1] = band1Buf[0] + ((lineBufH0[0] + delta) >> 1);
                    lineBufH0[2] = delta;
                }
                else if (wavelet->width & 1)
                {
                    int32_t delta = band0Buf[0] - ((band1Buf[0] + 1) >> 1);
                    lineBufH0[1] = band1Buf[0] + ((lineBufH0[0] + delta) >> 1);
                    lineBufH0[2] = delta;
                }
                else
                {
                    lineBufH0[1] = band1Buf[0] + lineBufH0[0];
                }
            }
            ++wavelet->curLine;
            ++wavelet->curH;
            wavelet->fltTapH = (wavelet->fltTapH + 1) % 5;
        }
    }

    return 0;
}

void crxFreeSubbandData(CrxImage* image, CrxPlaneComp* comp)
{
    int i;

    if (comp->compBuf) {
        cmpack_free(comp->compBuf);
        comp->compBuf = 0;
    }

    if (comp->subBands) {
        for (i = 0; i < image->subbandCount; i++) {
            if (comp->subBands[i].bandParam) {
                cmpack_free(comp->subBands[i].bandParam);
                comp->subBands[i].bandParam = 0LL;
            }

            comp->subBands[i].bandBuf = 0;
            comp->subBands[i].bandSize = 0;
        }
    }
}

static void _crxConvertPlaneLine(CrxImage* img, int imageRow, int imageCol, int plane, int32_t* lineData, int lineLength)
{
    if (lineData)
    {
        uint32_t rawOffset = 4 * img->planeWidth * imageRow + 2 * imageCol;
        if (img->encType == 1)
        {
            int i;
            int32_t maxVal = 1 << (img->nBits - 1);
            int32_t minVal = -maxVal;
            maxVal = maxVal - 1;
            for (i = 0; i < lineLength; i++) {
                int32_t data = lineData[i];
                if (data < minVal)
                    data = minVal;
                if (data > maxVal)
                    data = maxVal;
                img->outBufs[plane][rawOffset + 2 * i] = data;
            }
        }
        else if (img->encType == 3)
        {
            /* copy to intermediate planeBuf */
            int i;
            rawOffset = plane * img->planeWidth * img->planeHeight + img->planeWidth * imageRow + imageCol;
            for (i = 0; i < lineLength; i++)
                img->planeBuf[rawOffset + i] = lineData[i];
        }
        else if (img->nPlanes == 4)
        {
            int32_t i, median = 1 << (img->nBits - 1);
            int32_t maxVal = (1 << img->nBits) - 1;
            for (i = 0; i < lineLength; i++) {
                int32_t data = median + lineData[i];
                if (data < 0)
                    data = 0;
                if (data > maxVal)
                    data = maxVal;
                img->outBufs[plane][rawOffset + 2 * i] = data;
            }
        }
        else if (img->nPlanes == 1)
        {
            int32_t i, maxVal = (1 << img->nBits) - 1;
            int32_t median = 1 << (img->nBits - 1);
            rawOffset = img->planeWidth * imageRow + imageCol;
            for (i = 0; i < lineLength; i++) {
                int32_t data = median + lineData[i];
                if (data < 0)
                    data = 0;
                if (data > maxVal)
                    data = maxVal;
                img->outBufs[0][rawOffset + i] = data;
            }
        }
    }
    else if (img->encType == 3 && img->planeBuf)
    {
        int i;
        int32_t planeSize = img->planeWidth * img->planeHeight;
        int16_t* plane0 = img->planeBuf + imageRow * img->planeWidth;
        int16_t* plane1 = plane0 + planeSize;
        int16_t* plane2 = plane1 + planeSize;
        int16_t* plane3 = plane2 + planeSize;

        int32_t median = 1 << (img->nBits - 1) << 10;
        int32_t maxVal = (1 << img->nBits) - 1;
        uint32_t rawLineOffset = 4 * img->planeWidth * imageRow;

        /* for this stage - all except imageRow is ignored */
        for (i = 0; i < img->planeWidth; i++)
        {
            int32_t gr = median + (plane0[i] << 10) - 168 * plane1[i] - 585 * plane3[i];
            int32_t val = 0;
            if (gr < 0)
                gr = -(((_abs(gr) + 512) >> 9) & ~1);
            else
                gr = ((_abs(gr) + 512) >> 9) & ~1;

            /* Essentially R = round(median + P0 + 1.474*P3) */
            val = (median + (plane0[i] << 10) + 1510 * plane3[i] + 512) >> 10;
            if (val < 0)
                val = 0;
            if (val > maxVal)
                val = maxVal;
            img->outBufs[0][rawLineOffset + 2 * i] = val;
            /* Essentially G1 = round(median + P0 + P2 - 0.164*P1 - 0.571*P3) */
            val = (plane2[i] + gr + 1) >> 1;
            if (val < 0)
                val = 0;
            if (val > maxVal)
                val = maxVal;
            img->outBufs[1][rawLineOffset + 2 * i] = val;
            /* Essentially G2 = round(median + P0 - P2 - 0.164*P1 - 0.571*P3) */
            val = (gr - plane2[i] + 1) >> 1;
            if (val < 0)
                val = 0;
            if (val > maxVal)
                val = maxVal;
            img->outBufs[2][rawLineOffset + 2 * i] = val;
            /* Essentially B = round(median + P0 + 1.881*P1) */
            val = (median + (plane0[i] << 10) + 1927 * plane1[i] + 512) >> 10;
            if (val < 0)
                val = 0;
            if (val > maxVal)
                val = maxVal;
            img->outBufs[3][rawLineOffset + 2 * i] = val;
        }
    }
}

static int _crxParamInit(CrxImage *img, CrxBandParam **param, uint32_t subbandMdatOffset, uint32_t subbandDataSize,
                 uint32_t subbandWidth, uint32_t subbandHeight, int supportsPartial, uint32_t roundedBitsMask)
{
  int32_t progrDataSize = supportsPartial ? 0 : sizeof(int32_t) * subbandWidth;
  int32_t paramLength = 2 * subbandWidth + 4;
  uint8_t *paramBuf = 0;
    paramBuf = (uint8_t *)
               cmpack_calloc(1, sizeof(CrxBandParam) + sizeof(int32_t) * paramLength + progrDataSize);

  if (!paramBuf)
    return -1;

  *param = (CrxBandParam *)paramBuf;

  paramBuf += sizeof(CrxBandParam);

  (*param)->paramData = (int32_t *)paramBuf;
  (*param)->nonProgrData = progrDataSize ? (*param)->paramData + paramLength : 0;
  (*param)->subbandWidth = subbandWidth;
  (*param)->subbandHeight = subbandHeight;
  (*param)->roundedBits = 0;
  (*param)->curLine = 0;
  (*param)->roundedBitsMask = roundedBitsMask;
  (*param)->supportsPartial = supportsPartial;
  (*param)->bitStream.bitData = 0;
  (*param)->bitStream.bitsLeft = 0;
  (*param)->bitStream.mdatSize = subbandDataSize;
  (*param)->bitStream.curPos = 0;
  (*param)->bitStream.curBufSize = 0;
  (*param)->bitStream.curBufOffset = subbandMdatOffset;
  (*param)->bitStream.input = img->input;
  (*param)->bitStream.input_size = img->input_size;

  return _crxFillBuffer(&(*param)->bitStream);
}

static int _crxSetupSubbandData(CrxImage* img, CrxPlaneComp* planeComp, const CrxTile* tile, uint32_t mdatOffset)
{
    long compDataSize = 0;
    long waveletDataOffset = 0;
    long compCoeffDataOffset = 0;
    int32_t subbandNum, toSubbands = 3 * img->levels + 1;
    int32_t transformWidth = 0;

    CrxSubband* subbands = planeComp->subBands;

    /* calculate sizes */
    for (subbandNum = 0; subbandNum < toSubbands; subbandNum++)
    {
        subbands[subbandNum].bandSize = subbands[subbandNum].width * sizeof(int32_t); /* 4bytes */
        compDataSize += subbands[subbandNum].bandSize;
    }

    if (img->levels)
    {
        int32_t level, encLevels = img->levels ? img->levels : 1;
        waveletDataOffset = (compDataSize + 7) & ~7;
        compDataSize = (sizeof(CrxWaveletTransform) * encLevels + waveletDataOffset + 7) & ~7;
        compCoeffDataOffset = compDataSize;

        /* calc wavelet line buffer sizes (always at one level up from current) */
        for (level = 0; level < img->levels; ++level)
            if (level < img->levels - 1)
                compDataSize += 8 * sizeof(int32_t) * planeComp->subBands[3 * (level + 1) + 2].width;
            else
                compDataSize += 8 * sizeof(int32_t) * tile->width;
    }
    /* buffer allocation */
    planeComp->compBuf = (uint8_t*)
        cmpack_malloc(compDataSize);
    if (!planeComp->compBuf)
        return -1;

    /* subbands buffer and sizes initialisation */
    uint32_t subbandMdatOffset = img->mdatOffset + mdatOffset;
    uint8_t* subbandBuf = planeComp->compBuf;

    for (subbandNum = 0; subbandNum < toSubbands; subbandNum++)
    {
        subbands[subbandNum].bandBuf = subbandBuf;
        subbandBuf += subbands[subbandNum].bandSize;
        subbands[subbandNum].mdatOffset = subbandMdatOffset + subbands[subbandNum].dataOffset;
    }

    /* wavelet data initialisation */
    if (img->levels)
    {
        CrxWaveletTransform* waveletTransforms = (CrxWaveletTransform*)(planeComp->compBuf + waveletDataOffset);
        int32_t* paramData = (int32_t*)(planeComp->compBuf + compCoeffDataOffset);
        int level;

        planeComp->wvltTransform = waveletTransforms;
        waveletTransforms[0].subband0Buf = (int32_t*)subbands->bandBuf;

        for (level = 0; level < img->levels; ++level)
        {
            int32_t band = 3 * level + 1;

            if (level >= img->levels - 1)
            {
                waveletTransforms[level].height = tile->height;
                transformWidth = tile->width;
            }
            else
            {
                waveletTransforms[level].height = subbands[band + 3].height;
                transformWidth = subbands[band + 4].width;
            }
            waveletTransforms[level].width = transformWidth;
            waveletTransforms[level].lineBuf[0] = paramData;
            waveletTransforms[level].lineBuf[1] = waveletTransforms[level].lineBuf[0] + transformWidth;
            waveletTransforms[level].lineBuf[2] = waveletTransforms[level].lineBuf[1] + transformWidth;
            waveletTransforms[level].lineBuf[3] = waveletTransforms[level].lineBuf[2] + transformWidth;
            waveletTransforms[level].lineBuf[4] = waveletTransforms[level].lineBuf[3] + transformWidth;
            waveletTransforms[level].lineBuf[5] = waveletTransforms[level].lineBuf[4] + transformWidth;
            waveletTransforms[level].lineBuf[6] = waveletTransforms[level].lineBuf[5] + transformWidth;
            waveletTransforms[level].lineBuf[7] = waveletTransforms[level].lineBuf[6] + transformWidth;
            waveletTransforms[level].curLine = 0;
            waveletTransforms[level].curH = 0;
            waveletTransforms[level].fltTapH = 0;
            waveletTransforms[level].subband1Buf = (int32_t*)subbands[band].bandBuf;
            waveletTransforms[level].subband2Buf = (int32_t*)subbands[band + 1].bandBuf;
            waveletTransforms[level].subband3Buf = (int32_t*)subbands[band + 2].bandBuf;

            paramData = waveletTransforms[level].lineBuf[7] + transformWidth;
        }
    }

    /* decoding params and bitstream initialisation */
    for (subbandNum = 0; subbandNum < toSubbands; subbandNum++)
    {
        if (subbands[subbandNum].dataSize)
        {
            int supportsPartial;
            uint32_t roundedBitsMask;

            if (planeComp->supportsPartial && subbandNum == 0)
            {
                roundedBitsMask = planeComp->roundedBitsMask;
                supportsPartial = 1;
            }
            else {
                roundedBitsMask = 0;
                supportsPartial = 0;
            }

            int exitCode = _crxParamInit(img, &subbands[subbandNum].bandParam, subbands[subbandNum].mdatOffset,
                subbands[subbandNum].dataSize, subbands[subbandNum].width, subbands[subbandNum].height,
                supportsPartial, roundedBitsMask);
            if (exitCode != 0)
                return exitCode;
        }
    }

    return 0;
}

static int _crxDecodePlane(void* p, uint32_t planeNumber)
{
    CrxImage* img = (CrxImage*)p;
    int i, tRow, imageRow = 0;

    for (tRow = 0; tRow < img->tileRows; tRow++)
    {
        int tCol, imageCol = 0;
        for (tCol = 0; tCol < img->tileCols; tCol++)
        {
            CrxTile* tile = img->tiles + tRow * img->tileCols + tCol;
            CrxPlaneComp* planeComp = tile->comps + planeNumber;
            uint32_t tileMdatOffset = (uint32_t)(tile->dataOffset + tile->mdatQPDataSize + tile->mdatExtraSize + planeComp->dataOffset);

            /* decode single tile */
            int exitCode = _crxSetupSubbandData(img, planeComp, tile, tileMdatOffset);
            if (exitCode != 0)
                return exitCode;

            if (img->levels)
            {
                if (crxIdwt53FilterInitialize(planeComp, img->levels, tile->qStep))
                    return -1;
                for (i = 0; i < tile->height; ++i)
                {
                    if (_crxIdwt53FilterDecode(planeComp, img->levels - 1, tile->qStep) ||
                        crxIdwt53FilterTransform(planeComp, img->levels - 1))
                        return -1;
                    int32_t* lineData = _crxIdwt53FilterGetLine(planeComp, img->levels - 1);
                    _crxConvertPlaneLine(img, imageRow + i, imageCol, planeNumber, lineData, tile->width);
                }
            }
            else
            {
                /* we have the only subband in this case */
                if (!planeComp->subBands->dataSize)
                {
                    memset(planeComp->subBands->bandBuf, 0, planeComp->subBands->bandSize);
                    return 0;
                }

                for (i = 0; i < tile->height; ++i)
                {
                    int exitCode = _crxDecodeLine2(planeComp->subBands->bandParam, planeComp->subBands->bandBuf);
                    if (exitCode != 0)
                        return exitCode;
                    int32_t* lineData = (int32_t*)planeComp->subBands->bandBuf;
                    _crxConvertPlaneLine(img, imageRow + i, imageCol, planeNumber, lineData, tile->width);
                }
            }
            imageCol += tile->width;
        }
        imageRow += img->tiles[tRow * img->tileCols].height;
    }

    return 0;
}

static int _crxReadQP(CrxBitstream* bitStrm, int32_t kParam, uint32_t* pBitCode)
{
    uint32_t bitCode = 0;
    int exitCode = _crxBitstreamGetZeros(bitStrm, &bitCode);
    if (exitCode != 0)
        return exitCode;

    if (bitCode >= 23) {
        uint32_t nbits = 0;
        exitCode = _crxBitstreamGetBits(bitStrm, 8, &nbits);
        if (exitCode != 0)
            return exitCode;
        bitCode = nbits;
    }
    else if (kParam) {
        uint32_t nbits = 0;
        exitCode = _crxBitstreamGetBits(bitStrm, kParam, &nbits);
        if (exitCode != 0)
            return exitCode;
        bitCode = nbits | (bitCode << kParam);
    }
    *pBitCode = bitCode;
    return 0;
}

static int _crxDecodeGolombTop(CrxBitstream* bitStrm, int32_t width, int32_t* lineBuf, int32_t* kParam)
{
    lineBuf[0] = 0;
    while (width-- > 0)
    {
        lineBuf[1] = lineBuf[0];
        uint32_t qp = 0;
        int exitCode = _crxReadQP(bitStrm, *kParam, &qp);
        if (exitCode != 0)
            return exitCode;

        int32_t sVal = ((qp >> 1) ^ ((qp & 1) ? UINT_MAX : 0));
        lineBuf[1] += sVal;
        *kParam = _crxPredictKParameter(*kParam, qp, 7);
        ++lineBuf;
    }
    lineBuf[1] = lineBuf[0] + 1;
    return 0;
}

static int _crxDecodeGolombNormal(CrxBitstream* bitStrm, int32_t width, int32_t* lineBuf0, int32_t* lineBuf1, int32_t* kParam)
{
    lineBuf1[0] = lineBuf0[1];
    int32_t deltaH = lineBuf0[1] - lineBuf0[0];
    while (width-- > 0)
    {
        lineBuf1[1] = _crxPrediction(lineBuf1[0], lineBuf0[1], deltaH, lineBuf0[0] - lineBuf1[0]);
        uint32_t qp = 0;
        int exitCode = _crxReadQP(bitStrm, *kParam, &qp);
        if (exitCode != 0)
            return exitCode;

        int32_t sVal = ((qp >> 1) ^ ((qp & 1) ? UINT_MAX : 0));
        lineBuf1[1] += sVal;
        if (width)
        {
            deltaH = lineBuf0[2] - lineBuf0[1];
            *kParam = _crxPredictKParameter(*kParam, (qp + 2 * _abs(deltaH)) >> 1, 7);
            ++lineBuf0;
        }
        else
            *kParam = _crxPredictKParameter(*kParam, qp, 7);
        ++lineBuf1;
    }
    lineBuf1[1] = lineBuf1[0] + 1;
    return 0;
}

static int _crxMakeQStep(CrxImage* img, CrxTile* tile, int32_t* qpTable)
{
    int qpRow, qpCol;

    if (img->levels > 3 || img->levels < 1)
        return -1;
    int qpWidth = (tile->width >> 3) + ((tile->width & 7) != 0);
    int qpHeight = (tile->height >> 1) + (tile->height & 1);
    int qpHeight4 = (tile->height >> 2) + ((tile->height & 3) != 0);
    int qpHeight8 = (tile->height >> 3) + ((tile->height & 7) != 0);
    uint32_t totalHeight = qpHeight;
    if (img->levels > 1)
        totalHeight += qpHeight4;
    if (img->levels > 2)
        totalHeight += qpHeight8;
    tile->qStep = (CrxQStep*)
        cmpack_malloc(totalHeight * qpWidth * sizeof(uint32_t) + img->levels * sizeof(CrxQStep));
    if (!tile->qStep)
        return -1;
    uint32_t* qStepTbl = (uint32_t*)(tile->qStep + img->levels);
    CrxQStep* qStep = tile->qStep;
    switch (img->levels)
    {
    case 3:
        qStep->qStepTbl = qStepTbl;
        qStep->width = qpWidth;
        qStep->height = qpHeight8;
        for (qpRow = 0; qpRow < qpHeight8; ++qpRow)
        {
            int a0 = 4 * qpRow;
            if (a0 >= qpHeight)
                a0 = qpHeight - 1;
            int row0Idx = qpWidth * a0;
            int a1 = 4 * qpRow + 1;
            if (a1 >= qpHeight)
                a1 = qpHeight - 1;
            int row1Idx = qpWidth * a1;
            int a2 = 4 * qpRow + 2;
            if (a2 >= qpHeight)
                a2 = qpHeight - 1;
            int row2Idx = qpWidth * a2;
            int a3 = 4 * qpRow + 3;
            if (a3 >= qpHeight)
                a3 = qpHeight - 1;
            int row3Idx = qpWidth * a3;

            for (qpCol = 0; qpCol < qpWidth; ++qpCol, ++qStepTbl)
            {
                int32_t quantVal = qpTable[row0Idx++] + qpTable[row1Idx++] + qpTable[row2Idx++] + qpTable[row3Idx++];
                /* not sure about this nonsense - why is it not just avg like with 2 levels? */
                quantVal = ((quantVal < 0) * 3 + quantVal) >> 2;
                if (quantVal / 6 >= 6)
                    *qStepTbl = q_step_tbl[quantVal % 6] * (1 << (quantVal / 6 + 26));
                else
                    *qStepTbl = q_step_tbl[quantVal % 6] >> (6 - quantVal / 6);
            }
        }
        /* continue to the next level - we always decode all levels */
        ++qStep;
    case 2:
        qStep->qStepTbl = qStepTbl;
        qStep->width = qpWidth;
        qStep->height = qpHeight4;
        for (qpRow = 0; qpRow < qpHeight4; ++qpRow)
        {
            int a0 = 2 * qpRow;
            if (a0 >= qpHeight)
                a0 = qpHeight - 1;
            int row0Idx = qpWidth * a0;
            int a1 = 2 * qpRow + 1;
            if (a1 >= qpHeight)
                a1 = qpHeight - 1;
            int row1Idx = qpWidth * a1;
            for (qpCol = 0; qpCol < qpWidth; ++qpCol, ++qStepTbl)
            {
                int32_t quantVal = (qpTable[row0Idx++] + qpTable[row1Idx++]) / 2;
                if (quantVal / 6 >= 6)
                    *qStepTbl = q_step_tbl[quantVal % 6] * (1 << (quantVal / 6 + 26));
                else
                    *qStepTbl = q_step_tbl[quantVal % 6] >> (6 - quantVal / 6);
            }
        }
        /* continue to the next level - we always decode all levels */
        ++qStep;
    case 1:
        qStep->qStepTbl = qStepTbl;
        qStep->width = qpWidth;
        qStep->height = qpHeight;
        for (qpRow = 0; qpRow < qpHeight; ++qpRow)
            for (qpCol = 0; qpCol < qpWidth; ++qpCol, ++qStepTbl, ++qpTable)
                if (*qpTable / 6 >= 6)
                    *qStepTbl = q_step_tbl[*qpTable % 6] * (1 << (*qpTable / 6 + 26));
                else
                    *qStepTbl = q_step_tbl[*qpTable % 6] >> (6 - *qpTable / 6);

        break;
    }
    return 0;
}

static void _crxSetupSubbandIdx(crx_data_header_t* hdr, CrxSubband* band, int level,
    short colStartIdx, short bandWidthExCoef, short rowStartIdx,
    short bandHeightExCoef)
{
    if (hdr->version == 0x200)
    {
        band->rowStartAddOn = rowStartIdx;
        band->rowEndAddOn = bandHeightExCoef;
        band->colStartAddOn = colStartIdx;
        band->colEndAddOn = bandWidthExCoef;
        band->levelShift = 3 - level;
    }
    else
    {
        band->rowStartAddOn = 0;
        band->rowEndAddOn = 0;
        band->colStartAddOn = 0;
        band->colEndAddOn = 0;
        band->levelShift = 0;
    }
}

static int _crxProcessSubbands(crx_data_header_t* hdr, CrxImage* img, CrxTile* tile, CrxPlaneComp* comp)
{
    CrxSubband* band = comp->subBands + img->subbandCount - 1; /*  set to last band */
    uint32_t bandHeight = tile->height;
    uint32_t bandWidth = tile->width;
    int32_t bandWidthExCoef = 0;
    int32_t bandHeightExCoef = 0;
    if (img->levels)
    {
        /* Build up subband sequences to crxDecode to a level in a header */

        /* Coefficient structure is a bit unclear and convoluted:
         *   3 levels max - 8 groups (for tile width rounded to 8 bytes)
         *                  of 3 band per level 4 sets of coefficients for each
         */
        int level;
        int32_t* rowExCoef = exCoefNumTbl + 0x30 * (img->levels - 1) + 6 * (tile->width & 7);
        int32_t* colExCoef = exCoefNumTbl + 0x30 * (img->levels - 1) + 6 * (tile->height & 7);
        for (level = 0; level < img->levels; ++level)
        {
            int32_t widthOddPixel = bandWidth & 1;
            int32_t heightOddPixel = bandHeight & 1;
            bandWidth = (widthOddPixel + bandWidth) >> 1;
            bandHeight = (heightOddPixel + bandHeight) >> 1;

            int32_t bandWidthExCoef0 = 0;
            int32_t bandWidthExCoef1 = 0;
            int32_t bandHeightExCoef0 = 0;
            int32_t bandHeightExCoef1 = 0;
            int32_t colStartIdx = 0;
            int32_t rowStartIdx = 0;
            if (tile->tileFlag & E_HAS_TILES_ON_THE_RIGHT)
            {
                bandWidthExCoef0 = rowExCoef[2 * level];
                bandWidthExCoef1 = rowExCoef[2 * level + 1];
            }
            if (tile->tileFlag & E_HAS_TILES_ON_THE_LEFT)
            {
                ++bandWidthExCoef0;
                colStartIdx = 1;
            }

            if (tile->tileFlag & E_HAS_TILES_ON_THE_BOTTOM)
            {
                bandHeightExCoef0 = colExCoef[2 * level];
                bandHeightExCoef1 = colExCoef[2 * level + 1];
            }
            if (tile->tileFlag & E_HAS_TILES_ON_THE_TOP)
            {
                ++bandHeightExCoef0;
                rowStartIdx = 1;
            }

            band[0].width = bandWidth + bandWidthExCoef0 - widthOddPixel;
            band[0].height = bandHeight + bandHeightExCoef0 - heightOddPixel;
            _crxSetupSubbandIdx(hdr, band, level + 1, colStartIdx, bandWidthExCoef0 - colStartIdx, rowStartIdx,
                bandHeightExCoef0 - rowStartIdx);

            band[-1].width = bandWidth + bandWidthExCoef1;
            band[-1].height = bandHeight + bandHeightExCoef0 - heightOddPixel;

            _crxSetupSubbandIdx(hdr, band - 1, level + 1, 0, bandWidthExCoef1, rowStartIdx,
                bandHeightExCoef0 - rowStartIdx);

            band[-2].width = bandWidth + bandWidthExCoef0 - widthOddPixel;
            band[-2].height = bandHeight + bandHeightExCoef1;
            _crxSetupSubbandIdx(hdr, band - 2, level + 1, colStartIdx, bandWidthExCoef0 - colStartIdx, 0,
                bandHeightExCoef1);

            band -= 3;
        }
        bandWidthExCoef = bandHeightExCoef = 0;
        if (tile->tileFlag & E_HAS_TILES_ON_THE_RIGHT)
            bandWidthExCoef = rowExCoef[2 * img->levels - 1];
        if (tile->tileFlag & E_HAS_TILES_ON_THE_BOTTOM)
            bandHeightExCoef = colExCoef[2 * img->levels - 1];
    }
    band->width = bandWidthExCoef + bandWidth;
    band->height = bandHeightExCoef + bandHeight;
    if (img->levels)
        _crxSetupSubbandIdx(hdr, band, img->levels, 0, bandWidthExCoef, 0, bandHeightExCoef);

    return 0;
}

static int _crxReadSubbandHeaders(CrxImage* img, CrxPlaneComp* comp, uint8_t** subbandMdatPtr, int32_t* mdatSize)
{
    if (!img->subbandCount)
        return 0;
    int curSubband, subbandOffset = 0;
    CrxSubband* band = comp->subBands;
    for (curSubband = 0; curSubband < img->subbandCount; curSubband++, band++)
    {
        if (*mdatSize < 4)
            return -1;

        int hdrSign = sgetn(2, *subbandMdatPtr);
        int hdrSize = sgetn(2, *subbandMdatPtr + 2);
        if (*mdatSize < hdrSize + 4)
            return -1;
        if ((hdrSign != 0xFF03 || hdrSize != 8) && (hdrSign != 0xFF13 || hdrSize != 16))
            return -1;

        int32_t subbandSize = sgetn(4, *subbandMdatPtr + 4);

        if (curSubband != ((*subbandMdatPtr)[8] & 0xF0) >> 4)
        {
            band->dataSize = subbandSize;
            return -1;
        }

        band->dataOffset = subbandOffset;
        band->kParam = 0;
        band->bandParam = 0;
        band->bandBuf = 0;
        band->bandSize = 0;

        if (hdrSign == 0xFF03)
        {
            /* old header */
            uint32_t bitData = sgetn(4, *subbandMdatPtr + 8);
            band->dataSize = subbandSize - (bitData & 0x7FFFF);
            band->supportsPartial = bitData & 0x8000000;
            band->qParam = (bitData >> 19) & 0xFF;
            band->qStepBase = 0;
            band->qStepMult = 0;
        }
        else
        {
            /* new header */
            if (sgetn(2, *subbandMdatPtr + 8) & 0xFFF)
                /* partial and qParam are not supported */
                return -1;
            if (sgetn(2, *subbandMdatPtr + 18))
                /* new header terninated by 2 zero bytes */
                return -1;
            band->supportsPartial = 0;
            band->qParam = 0;
            band->dataSize = subbandSize - sgetn(2, *subbandMdatPtr + 16);
            band->qStepBase = sgetn(4, *subbandMdatPtr + 12);
            band->qStepMult = sgetn(2, *subbandMdatPtr + 10);
        }

        subbandOffset += subbandSize;

        *subbandMdatPtr += hdrSize + 4;
        *mdatSize -= hdrSize + 4;
    }

    return 0;
}

static int _crxReadImageHeaders(crx_data_header_t* hdr, CrxImage* img, uint8_t* mdatPtr, int32_t mdatHdrSize)
{
    int nTiles = img->tileRows * img->tileCols;

    if (!nTiles)
        return -1;

    if (!img->tiles)
    {
        img->tiles = (CrxTile*)
            cmpack_calloc(sizeof(CrxTile) * nTiles + sizeof(CrxPlaneComp) * nTiles * img->nPlanes +
                sizeof(CrxSubband) * nTiles * img->nPlanes * img->subbandCount,
                1);
        if (!img->tiles)
            return -1;

        /* memory areas in allocated chunk */
        int curTile;
        CrxTile* tile = img->tiles;
        CrxPlaneComp* comps = (CrxPlaneComp*)(tile + nTiles);
        CrxSubband* bands = (CrxSubband*)(comps + img->nPlanes * nTiles);

        for (curTile = 0; curTile < nTiles; curTile++, tile++)
        {
            tile->tileFlag = 0; /* tile neighbouring flags */
            tile->tileNumber = curTile;
            tile->tileSize = 0;
            tile->comps = comps + curTile * img->nPlanes;

            if ((curTile + 1) % img->tileCols)
            {
                /* not the last tile in a tile row */
                tile->width = hdr->tileWidth;
                if (img->tileCols > 1)
                {
                    tile->tileFlag = E_HAS_TILES_ON_THE_RIGHT;
                    if (curTile % img->tileCols)
                        /* not the first tile in tile row */
                        tile->tileFlag |= E_HAS_TILES_ON_THE_LEFT;
                }
            }
            else
            {
                /* last tile in a tile row */
                tile->width = img->planeWidth - hdr->tileWidth * (img->tileCols - 1);
                if (img->tileCols > 1)
                    tile->tileFlag = E_HAS_TILES_ON_THE_LEFT;
            }
            if (curTile < nTiles - img->tileCols)
            {
                /* in first tile row */
                tile->height = hdr->tileHeight;
                if (img->tileRows > 1)
                {
                    tile->tileFlag |= E_HAS_TILES_ON_THE_BOTTOM;
                    if (curTile >= img->tileCols)
                        tile->tileFlag |= E_HAS_TILES_ON_THE_TOP;
                }
            }
            else
            {
                /* non first tile row */
                tile->height = img->planeHeight - hdr->tileHeight * (img->tileRows - 1);
                if (img->tileRows > 1)
                    tile->tileFlag |= E_HAS_TILES_ON_THE_TOP;
            }
            if (img->nPlanes)
            {
                int curComp, curBand;
                CrxPlaneComp* comp = tile->comps;
                CrxSubband* band = bands + curTile * img->nPlanes * img->subbandCount;

                for (curComp = 0; curComp < img->nPlanes; curComp++, comp++)
                {
                    comp->compNumber = curComp;
                    comp->supportsPartial = 1;
                    comp->tileFlag = tile->tileFlag;
                    comp->subBands = band;
                    comp->compBuf = 0;
                    comp->wvltTransform = 0;
                    if (img->subbandCount)
                    {
                        for (curBand = 0; curBand < img->subbandCount; curBand++, band++)
                        {
                            band->supportsPartial = 0;
                            band->qParam = 4;
                            band->bandParam = 0;
                            band->dataSize = 0;
                        }
                    }
                }
            }
        }
    }

    uint32_t tileOffset = 0;
    int32_t dataSize = mdatHdrSize;
    uint8_t* dataPtr = mdatPtr;
    CrxTile* tile = img->tiles;
    int curTile;

    for (curTile = 0; curTile < nTiles; ++curTile, ++tile)
    {
        if (dataSize < 4)
            return -1;

        int hdrSign = sgetn(2, dataPtr);
        int hdrSize = sgetn(2, dataPtr + 2);
        if ((hdrSign != 0xFF01 || hdrSize != 8) && (hdrSign != 0xFF11 || (hdrSize != 8 && hdrSize != 16)))
            return -1;
        if (dataSize < hdrSize + 4)
            return -1;
        int tailSign = sgetn(2, dataPtr + 10);
        if ((hdrSize == 8 && tailSign) || (hdrSize == 16 && tailSign != 0x4000))
            return -1;
        if (sgetn(2, dataPtr + 8) != (unsigned)curTile)
            return -1;

        dataSize -= hdrSize + 4;

        tile->tileSize = sgetn(4, dataPtr + 4);
        tile->dataOffset = tileOffset;
        tile->qStep = 0;
        if (hdrSize == 16)
        {
            /* extended header data - terminated by 0 bytes */
            if (sgetn(2, dataPtr + 18) != 0)
                return -1;
            tile->hasQPData = 1;
            tile->mdatQPDataSize = sgetn(4, dataPtr + 12);
            tile->mdatExtraSize = sgetn(2, dataPtr + 16);
        }
        else
        {
            tile->hasQPData = 0;
            tile->mdatQPDataSize = 0;
            tile->mdatExtraSize = 0;
        }

        dataPtr += hdrSize + 4;
        tileOffset += tile->tileSize;

        uint32_t compNum, compOffset = 0;
        CrxPlaneComp* comp = tile->comps;

        for (compNum = 0; compNum < img->nPlanes; ++compNum, ++comp)
        {
            if (dataSize < 0xC)
                return -1;
            hdrSign = sgetn(2, dataPtr);
            hdrSize = sgetn(2, dataPtr + 2);
            if ((hdrSign != 0xFF02 && hdrSign != 0xFF12) || hdrSize != 8)
                return -1;
            if (compNum != dataPtr[8] >> 4)
                return -1;
            if (sgetn(3, dataPtr + 9) != 0)
                return -1;

            comp->compSize = sgetn(4, dataPtr + 4);

            int32_t compHdrRoundedBits = (dataPtr[8] >> 1) & 3;
            comp->supportsPartial = (dataPtr[8] & 8) != 0;

            comp->dataOffset = compOffset;
            comp->tileFlag = tile->tileFlag;

            compOffset += comp->compSize;
            dataSize -= 0xC;
            dataPtr += 0xC;

            comp->roundedBitsMask = 0;

            if (compHdrRoundedBits)
            {
                if (img->levels || !comp->supportsPartial)
                    return -1;

                comp->roundedBitsMask = 1 << (compHdrRoundedBits - 1);
            }

            if (_crxReadSubbandHeaders(img, comp, &dataPtr, &dataSize) || _crxProcessSubbands(hdr, img, tile, comp))
                return -1;
        }
    }

    if (hdr->version != 0x200)
        return 0;

    tile = img->tiles;
    for (curTile = 0; curTile < nTiles; ++curTile, ++tile)
    {
        if (tile->hasQPData)
        {
            CrxBitstream bitStrm;
            bitStrm.bitData = 0;
            bitStrm.bitsLeft = 0;
            bitStrm.curPos = 0;
            bitStrm.curBufSize = 0;
            bitStrm.mdatSize = tile->mdatQPDataSize;
            bitStrm.curBufOffset = img->mdatOffset + tile->dataOffset;
            bitStrm.input = img->input;
            bitStrm.input_size = img->input_size;

            int exitCode = _crxFillBuffer(&bitStrm);
            if (exitCode != 0)
                return exitCode;

            unsigned int qpWidth = (tile->width >> 3) + ((tile->width & 7) != 0);
            unsigned int qpHeight = (tile->height >> 1) + (tile->height & 1);
            unsigned long totalQP = qpHeight * qpWidth;

            int32_t* qpTable = (int32_t*)cmpack_malloc((totalQP + 2 * (qpWidth + 2)) * sizeof(int32_t));
            memset(qpTable, 0, (totalQP + 2 * (qpWidth + 2)) * sizeof(int32_t));

            int32_t* qpCurElem = qpTable;
            /* 2 lines padded with extra pixels at the start and at the end */
            int32_t* qpLineBuf = qpTable + totalQP;
            int32_t kParam = 0;
            unsigned qpRow, qpCol;
            for (qpRow = 0; qpRow < qpHeight; ++qpRow)
            {
                int32_t* qpLine0 = qpRow & 1 ? qpLineBuf + qpWidth + 2 : qpLineBuf;
                int32_t* qpLine1 = qpRow & 1 ? qpLineBuf : qpLineBuf + qpWidth + 2;

                if (qpRow) {
                    int exitCode = _crxDecodeGolombNormal(&bitStrm, qpWidth, qpLine0, qpLine1, &kParam);
                    if (exitCode != 0) {
                        cmpack_free(qpTable);
                        return exitCode;
                    }
                }
                else {
                    int exitCode = _crxDecodeGolombTop(&bitStrm, qpWidth, qpLine1, &kParam);
                    if (exitCode != 0) {
                        cmpack_free(qpTable);
                        return exitCode;
                    }
                }

                for (qpCol = 0; qpCol < qpWidth; ++qpCol)
                    *qpCurElem++ = qpLine1[qpCol + 1] + 4;
            }

            /* now we read QP data - build tile QStep */
            exitCode = _crxMakeQStep(img, tile, qpTable);
            if (exitCode != 0) {
                cmpack_free(qpTable);
                return -1;
            }

            cmpack_free(qpTable);
        }
    }

    return 0;
}

static int crxSetupImageData(crx_data_header_t* hdr, CrxImage* img, int16_t* outBuf, uint32_t mdatOffset, uint32_t mdatSize,
    uint8_t* mdatHdrPtr, int32_t mdatHdrSize)
{
    int IncrBitTable[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0 };

    img->planeWidth = hdr->f_width;
    img->planeHeight = hdr->f_height;

    if (hdr->tileWidth < 0x16 || hdr->tileHeight < 0x16 || img->planeWidth > 0x7FFF || img->planeHeight > 0x7FFF)
        return -1;

    img->tileCols = (img->planeWidth + hdr->tileWidth - 1) / hdr->tileWidth;
    img->tileRows = (img->planeHeight + hdr->tileHeight - 1) / hdr->tileHeight;

    if (img->planeWidth - hdr->tileWidth * (img->tileCols - 1) < 0x16 ||
        img->planeHeight - hdr->tileHeight * (img->tileRows - 1) < 0x16)
        return -1;

    img->tiles = 0;
    img->levels = hdr->imageLevels;
    img->subbandCount = 3 * img->levels + 1; /* 3 bands per level + one last LL */
    img->nPlanes = hdr->nPlanes;
    img->nBits = hdr->nBits;
    img->encType = hdr->encType;
    img->samplePrecision = hdr->nBits + IncrBitTable[4 * hdr->encType + 2] + 1;
    img->mdatOffset = mdatOffset + hdr->mdatHdrSize;
    img->mdatSize = mdatSize;
    img->planeBuf = 0;
    img->outBufs[0] = img->outBufs[1] = img->outBufs[2] = img->outBufs[3] = 0;

    /* The encoding type 3 needs all 4 planes to be decoded to generate row of
     * RGGB values. It seems to be using some other colour space for raw encoding
     * It is a massive buffer so ideallly it will need a different approach:
     * decode planes line by line and convert single line then without
     * intermediate plane buffer. At the moment though it's too many changes so
     * left as is.
     */ 
    if (img->encType == 3 && img->nPlanes == 4 && img->nBits > 8)
    {
        img->planeBuf = (int16_t*)
            cmpack_malloc(img->planeHeight * img->planeWidth * img->nPlanes * ((img->samplePrecision + 7) >> 3));
        if (!img->planeBuf)
            return -1;
    }

    int32_t rowSize = 2 * img->planeWidth;

    if (img->nPlanes == 1)
        img->outBufs[0] = outBuf;
    else
        switch (hdr->cfaLayout)
        {
        case 0:
            /* R G
             * G B
             */
            img->outBufs[0] = outBuf;
            img->outBufs[1] = outBuf + 1;
            img->outBufs[2] = outBuf + rowSize;
            img->outBufs[3] = img->outBufs[2] + 1;
            break;
        case 1:
            /* G R
             * B G
             */ 
            img->outBufs[1] = outBuf;
            img->outBufs[0] = outBuf + 1;
            img->outBufs[3] = outBuf + rowSize;
            img->outBufs[2] = img->outBufs[3] + 1;
            break;
        case 2:
            /* G B
             * R G
             */ 
            img->outBufs[2] = outBuf;
            img->outBufs[3] = outBuf + 1;
            img->outBufs[0] = outBuf + rowSize;
            img->outBufs[1] = img->outBufs[0] + 1;
            break;
        case 3:
            /* B G
             * G R
             */ 
            img->outBufs[3] = outBuf;
            img->outBufs[2] = outBuf + 1;
            img->outBufs[1] = outBuf + rowSize;
            img->outBufs[0] = img->outBufs[1] + 1;
            break;
        }

    /* read header */
    return _crxReadImageHeaders(hdr, img, mdatHdrPtr, mdatHdrSize);
}

static void _crxFreeImageData(CrxImage* img)
{
    CrxTile* tile = img->tiles;
    int nTiles = img->tileRows * img->tileCols;
    int32_t curTile, curPlane;

    if (img->tiles) {
        for (curTile = 0; curTile < nTiles; curTile++) {
            if (tile[curTile].comps)
                for (curPlane = 0; curPlane < img->nPlanes; curPlane++)
                    crxFreeSubbandData(img, tile[curTile].comps + curPlane);
            if (tile[curTile].qStep)
                cmpack_free(tile[curTile].qStep);
        }
        cmpack_free(img->tiles);
        img->tiles = 0;
    }

    if (img->planeBuf) {
        cmpack_free(img->planeBuf);
        img->planeBuf = 0;
    }
}

int crxLoadRaw(crx_data_header_t* hdr_in, FILE* file, uint32_t file_size, uint32_t data_offset, uint32_t data_size, int16_t* raw_image)
{
    int32_t plane, i, exitCode = 0;

    crx_data_header_t hdr = *hdr_in;

    CrxImage img;
    img.input = file;
    img.input_size = file_size;

    /* update sizes for the planes */
    if (hdr.nPlanes == 4)
    {
        hdr.f_width >>= 1;
        hdr.f_height >>= 1;
        hdr.tileWidth >>= 1;
        hdr.tileHeight >>= 1;
    }

    uint8_t* hdrBuf = (uint8_t*)cmpack_malloc(hdr.mdatHdrSize);

    /* read image header */
    if (fseek(file, data_offset, SEEK_SET) != 0 || fread(hdrBuf, 1, hdr.mdatHdrSize, file) != hdr.mdatHdrSize)
        return -1;

    /* parse and setup the image data */
    int data_error = 0;
    if (crxSetupImageData(&hdr, &img, raw_image, data_offset, data_size, hdrBuf, hdr.mdatHdrSize))
        data_error++;
    cmpack_free(hdrBuf);

    if (data_error == 0) {
        for (plane = 0; plane < hdr.nPlanes; ++plane) {
            exitCode = _crxDecodePlane(&img, plane);
            if (exitCode != 0) {
                data_error++;
                break;
            }
        }
        if (img.encType == 3 && exitCode == 0 && data_error == 0) {
            for (i = 0; i < img.planeHeight; ++i)
                _crxConvertPlaneLine(&img, i, 0, 0, 0, 0);
        }
    }

    _crxFreeImageData(&img);
    return ((exitCode == 0 && data_error == 0) ? 0 : -1);
}

int crxParseImageHeader(crx_data_header_t* hdr, const unsigned char* cmp1TagData)
{
    hdr->version = sgetn(2, cmp1TagData + 4);
    hdr->f_width = sgetn(4, cmp1TagData + 8);
    hdr->f_height = sgetn(4, cmp1TagData + 12);
    hdr->tileWidth = sgetn(4, cmp1TagData + 16);
    hdr->tileHeight = sgetn(4, cmp1TagData + 20);
    hdr->nBits = cmp1TagData[24];
    hdr->nPlanes = cmp1TagData[25] >> 4;
    hdr->cfaLayout = cmp1TagData[25] & 0xF;
    hdr->encType = cmp1TagData[26] >> 4;
    hdr->imageLevels = cmp1TagData[26] & 0xF;
    hdr->hasTileCols = cmp1TagData[27] >> 7;
    hdr->hasTileRows = (cmp1TagData[27] >> 6) & 1;
    hdr->mdatHdrSize = sgetn(4, cmp1TagData + 28);

    /* validation */
    if ((hdr->version != 0x100 && hdr->version != 0x200) || !hdr->mdatHdrSize)
        return -1;
    if (hdr->encType == 1)
    {
        if (hdr->nBits > 15)
            return -1;
    }
    else
    {
        if (hdr->encType && hdr->encType != 3)
            return -1;
        if (hdr->nBits > 14)
            return -1;
    }

    if (hdr->nPlanes == 1)
    {
        if (hdr->cfaLayout || hdr->encType || hdr->nBits != 8)
            return -1;
    }
    else if (hdr->nPlanes != 4 || hdr->f_width & 1 || hdr->f_height & 1 || hdr->tileWidth & 1 || hdr->tileHeight & 1 ||
        hdr->cfaLayout > 3 || hdr->nBits == 8)
        return -1;

    if (hdr->tileWidth > hdr->f_width || hdr->tileHeight > hdr->f_height)
        return -1;

    if (hdr->imageLevels > 3 || hdr->hasTileCols > 1 || hdr->hasTileRows > 1)
        return -1;

    return 0;
}
