#pragma once

// This is a cosmetic restructing and port to C++ class of 'NanoJPEG', found
// at http://keyj.s2000.ws/?p=137. It's been made somewhat thread safe in that
// all context information is pulled into an object, rather than being global
// as the original was. Other than that, the original is superior in
// configurability, comments, cleanliness, portability, etc. and should be
// preferred. The only other possible benefit this version can claim is that
// it's crammed into one header file.
//
// Scott Graham <scott.jpegdecoder@h4ck3r.net>
//
// The original license follows:
//
// NanoJPEG -- KeyJ's Tiny Baseline JPEG Decoder
// version 1.0 (2009-04-29)
// by Martin J. Fiedler <martin.fiedler@gmx.net>
//
// This software is published under the terms of KeyJ's Research License,
// version 0.2. Usage of this software is subject to the following conditions:
// 0. There's no warranty whatsoever. The author(s) of this software can not
//    be held liable for any damages that occur when using this software.
// 1. This software may be used freely for both non-commercial and commercial
//    purposes.
// 2. This software may be redistributed freely as long as no fees are charged
//    for the distribution and this license information is included.
// 3. This software may be modified freely except for this license information,
//    which must not be changed in any way.
// 4. If anything other than configuration, indentation or comments have been
//    altered in the code, the original author(s) must receive a copy of the
//    modified code.

#include <cstdlib>
#include <cstring>

#ifdef _MSC_VER
#    pragma warning(push)
#    pragma warning(disable : 4127) // conditional expression is constant
#    pragma warning(disable : 4706) // assignment within conditional
#endif

namespace jpeg {
class Decoder
{
public:
    enum DecodeResult
    {
        OK = 0,            // decoding successful
        NotAJpeg,          // not a JPEG file
        Unsupported,       // unsupported format
        OutOfMemory,       // out of memory
        InternalError,     // internal error
        SyntaxError,       // syntax error
        Internal_Finished, // used internally, will never be reported
    };

    // decode the raw data. object is very large, and probably shouldn't
    // go on the stack.
    Decoder(const unsigned char* data, size_t size,
        void* (*allocFunc)(size_t) = malloc, void (*freeFunc)(void*) = free);
    ~Decoder();

    // the result of decode
    DecodeResult GetResult() const;

    // all remaining functions below are only valid if GetResult() == OK.

    int GetWidth() const;
    int GetHeight() const;
    bool IsColor() const;

    // if IsColor() then 24bit as R,G,B bytes
    // else 8 bit luminance
    unsigned char* GetImage() const;

    // in bytes
    size_t GetImageSize() const;

    //////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////
    //
    // Implementation follows
    //
    //////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////

private:
    struct VlcCode
    {
        unsigned char bits, code;
    };

    struct Component
    {
        int cid;
        int ssx, ssy;
        int width, height;
        int stride;
        int qtsel;
        int actabsel, dctabsel;
        int dcpred;
        unsigned char* pixels;
    };

    struct Context
    {
        DecodeResult error;
        const unsigned char* pos;
        int size;
        int length;
        int width, height;
        int mbwidth, mbheight;
        int mbsizex, mbsizey;
        int ncomp;
        Component comp[3];
        int qtused, qtavail;
        unsigned char qtab[4][64];
        VlcCode vlctab[4][65536];
        int buf, bufbits;
        int block[64];
        int rstinterval;
        unsigned char* rgb;
    };

    Context ctx;
    char ZZ[64];
    void* (*AllocMem)(size_t);
    void (*FreeMem)(void*);

    static inline unsigned char Clip(const int x)
    {
        return (x < 0) ? 0 : ((x > 0xFF) ? 0xFF : (unsigned char)x);
    }

    enum
    {
        W1 = 2841,
        W2 = 2676,
        W3 = 2408,
        W5 = 1609,
        W6 = 1108,
        W7 = 565,
    };

    static inline void RowIDCT(int* blk)
    {
        int x0, x1, x2, x3, x4, x5, x6, x7, x8;
        if (!((x1 = blk[4] << 11) | (x2 = blk[6]) | (x3 = blk[2]) |
                (x4 = blk[1]) | (x5 = blk[7]) | (x6 = blk[5]) |
                (x7 = blk[3]))) {
            blk[0] = blk[1] = blk[2] = blk[3] = blk[4] = blk[5] = blk[6] =
                blk[7] = blk[0] << 3;
            return;
        }
        x0 = (blk[0] << 11) + 128;
        x8 = W7 * (x4 + x5);
        x4 = x8 + (W1 - W7) * x4;
        x5 = x8 - (W1 + W7) * x5;
        x8 = W3 * (x6 + x7);
        x6 = x8 - (W3 - W5) * x6;
        x7 = x8 - (W3 + W5) * x7;
        x8 = x0 + x1;
        x0 -= x1;
        x1 = W6 * (x3 + x2);
        x2 = x1 - (W2 + W6) * x2;
        x3 = x1 + (W2 - W6) * x3;
        x1 = x4 + x6;
        x4 -= x6;
        x6 = x5 + x7;
        x5 -= x7;
        x7 = x8 + x3;
        x8 -= x3;
        x3 = x0 + x2;
        x0 -= x2;
        x2 = (181 * (x4 + x5) + 128) >> 8;
        x4 = (181 * (x4 - x5) + 128) >> 8;
        blk[0] = (x7 + x1) >> 8;
        blk[1] = (x3 + x2) >> 8;
        blk[2] = (x0 + x4) >> 8;
        blk[3] = (x8 + x6) >> 8;
        blk[4] = (x8 - x6) >> 8;
        blk[5] = (x0 - x4) >> 8;
        blk[6] = (x3 - x2) >> 8;
        blk[7] = (x7 - x1) >> 8;
    }

    static inline void ColIDCT(const int* blk, unsigned char* out, int stride)
    {
        int x0, x1, x2, x3, x4, x5, x6, x7, x8;
        if (!((x1 = blk[8 * 4] << 8) | (x2 = blk[8 * 6]) | (x3 = blk[8 * 2]) |
                (x4 = blk[8 * 1]) | (x5 = blk[8 * 7]) | (x6 = blk[8 * 5]) |
                (x7 = blk[8 * 3]))) {
            x1 = Clip(((blk[0] + 32) >> 6) + 128);
            for (x0 = 8; x0; --x0) {
                *out = (unsigned char)x1;
                out += stride;
            }
            return;
        }
        x0 = (blk[0] << 8) + 8192;
        x8 = W7 * (x4 + x5) + 4;
        x4 = (x8 + (W1 - W7) * x4) >> 3;
        x5 = (x8 - (W1 + W7) * x5) >> 3;
        x8 = W3 * (x6 + x7) + 4;
        x6 = (x8 - (W3 - W5) * x6) >> 3;
        x7 = (x8 - (W3 + W5) * x7) >> 3;
        x8 = x0 + x1;
        x0 -= x1;
        x1 = W6 * (x3 + x2) + 4;
        x2 = (x1 - (W2 + W6) * x2) >> 3;
        x3 = (x1 + (W2 - W6) * x3) >> 3;
        x1 = x4 + x6;
        x4 -= x6;
        x6 = x5 + x7;
        x5 -= x7;
        x7 = x8 + x3;
        x8 -= x3;
        x3 = x0 + x2;
        x0 -= x2;
        x2 = (181 * (x4 + x5) + 128) >> 8;
        x4 = (181 * (x4 - x5) + 128) >> 8;
        *out = Clip(((x7 + x1) >> 14) + 128);
        out += stride;
        *out = Clip(((x3 + x2) >> 14) + 128);
        out += stride;
        *out = Clip(((x0 + x4) >> 14) + 128);
        out += stride;
        *out = Clip(((x8 + x6) >> 14) + 128);
        out += stride;
        *out = Clip(((x8 - x6) >> 14) + 128);
        out += stride;
        *out = Clip(((x0 - x4) >> 14) + 128);
        out += stride;
        *out = Clip(((x3 - x2) >> 14) + 128);
        out += stride;
        *out = Clip(((x7 - x1) >> 14) + 128);
    }

#define JPEG_DECODER_THROW(e)                                                  \
    do {                                                                       \
        ctx.error = e;                                                         \
        return;                                                                \
    } while (0)

    inline int ShowBits(int bits)
    {
        unsigned char newbyte;
        if (!bits) return 0;
        while (ctx.bufbits < bits) {
            if (ctx.size <= 0) {
                ctx.buf = (ctx.buf << 8) | 0xFF;
                ctx.bufbits += 8;
                continue;
            }
            newbyte = *ctx.pos++;
            ctx.size--;
            ctx.bufbits += 8;
            ctx.buf = (ctx.buf << 8) | newbyte;
            if (newbyte == 0xFF) {
                if (ctx.size) {
                    unsigned char marker = *ctx.pos++;
                    ctx.size--;
                    switch (marker) {
                    case 0: break;
                    case 0xD9: ctx.size = 0; break;
                    default:
                        if ((marker & 0xF8) != 0xD0)
                            ctx.error = SyntaxError;
                        else {
                            ctx.buf = (ctx.buf << 8) | marker;
                            ctx.bufbits += 8;
                        }
                    }
                } else
                    ctx.error = SyntaxError;
            }
        }
        return (ctx.buf >> (ctx.bufbits - bits)) & ((1 << bits) - 1);
    }

    inline void SkipBits(int bits)
    {
        if (ctx.bufbits < bits) (void)ShowBits(bits);
        ctx.bufbits -= bits;
    }

    inline int GetBits(int bits)
    {
        int res = ShowBits(bits);
        SkipBits(bits);
        return res;
    }

    inline void ByteAlign() { ctx.bufbits &= 0xF8; }

    inline void Skip(int count)
    {
        ctx.pos += count;
        ctx.size -= count;
        ctx.length -= count;
        if (ctx.size < 0) ctx.error = SyntaxError;
    }

    static inline unsigned short Decode16(const unsigned char* pos)
    {
        return (pos[0] << 8) | pos[1];
    }

    inline void DecodeLength()
    {
        if (ctx.size < 2) JPEG_DECODER_THROW(SyntaxError);
        ctx.length = Decode16(ctx.pos);
        if (ctx.length > ctx.size) JPEG_DECODER_THROW(SyntaxError);
        Skip(2);
    }

    inline void SkipMarker()
    {
        DecodeLength();
        Skip(ctx.length);
    }

    inline void DecodeSOF()
    {
        int i, ssxmax = 0, ssymax = 0;
        Component* c;
        DecodeLength();
        if (ctx.length < 9) JPEG_DECODER_THROW(SyntaxError);
        if (ctx.pos[0] != 8) JPEG_DECODER_THROW(Unsupported);
        ctx.height = Decode16(ctx.pos + 1);
        ctx.width = Decode16(ctx.pos + 3);
        ctx.ncomp = ctx.pos[5];
        Skip(6);
        switch (ctx.ncomp) {
        case 1:
        case 3: break;
        default: JPEG_DECODER_THROW(Unsupported);
        }
        if (ctx.length < (ctx.ncomp * 3)) JPEG_DECODER_THROW(SyntaxError);
        for (i = 0, c = ctx.comp; i < ctx.ncomp; ++i, ++c) {
            c->cid = ctx.pos[0];
            if (!(c->ssx = ctx.pos[1] >> 4)) JPEG_DECODER_THROW(SyntaxError);
            if (c->ssx & (c->ssx - 1))
                JPEG_DECODER_THROW(Unsupported); // non-power of two
            if (!(c->ssy = ctx.pos[1] & 15)) JPEG_DECODER_THROW(SyntaxError);
            if (c->ssy & (c->ssy - 1))
                JPEG_DECODER_THROW(Unsupported); // non-power of two
            if ((c->qtsel = ctx.pos[2]) & 0xFC) JPEG_DECODER_THROW(SyntaxError);
            Skip(3);
            ctx.qtused |= 1 << c->qtsel;
            if (c->ssx > ssxmax) ssxmax = c->ssx;
            if (c->ssy > ssymax) ssymax = c->ssy;
        }
        ctx.mbsizex = ssxmax << 3;
        ctx.mbsizey = ssymax << 3;
        ctx.mbwidth = (ctx.width + ctx.mbsizex - 1) / ctx.mbsizex;
        ctx.mbheight = (ctx.height + ctx.mbsizey - 1) / ctx.mbsizey;
        for (i = 0, c = ctx.comp; i < ctx.ncomp; ++i, ++c) {
            c->width = (ctx.width * c->ssx + ssxmax - 1) / ssxmax;
            c->stride = (c->width + 7) & 0x7FFFFFF8;
            c->height = (ctx.height * c->ssy + ssymax - 1) / ssymax;
            c->stride = ctx.mbwidth * ctx.mbsizex * c->ssx / ssxmax;
            if (((c->width < 3) && (c->ssx != ssxmax)) ||
                ((c->height < 3) && (c->ssy != ssymax)))
                JPEG_DECODER_THROW(Unsupported);
            if (!(c->pixels = (unsigned char*)AllocMem(
                      c->stride *
                      (ctx.mbheight * ctx.mbsizey * c->ssy / ssymax))))
                JPEG_DECODER_THROW(OutOfMemory);
        }
        if (ctx.ncomp == 3) {
            ctx.rgb =
                (unsigned char*)AllocMem(ctx.width * ctx.height * ctx.ncomp);
            if (!ctx.rgb) JPEG_DECODER_THROW(OutOfMemory);
        }
        Skip(ctx.length);
    }

    inline void DecodeDHT()
    {
        int codelen, currcnt, remain, spread, i, j;
        VlcCode* vlc;
        unsigned char counts[16];
        DecodeLength();
        while (ctx.length >= 17) {
            i = ctx.pos[0];
            if (i & 0xEC) JPEG_DECODER_THROW(SyntaxError);
            if (i & 0x02) JPEG_DECODER_THROW(Unsupported);
            i = (i | (i >> 3)) & 3; // combined DC/AC + tableid value
            for (codelen = 1; codelen <= 16; ++codelen)
                counts[codelen - 1] = ctx.pos[codelen];
            Skip(17);
            vlc = &ctx.vlctab[i][0];
            remain = spread = 65536;
            for (codelen = 1; codelen <= 16; ++codelen) {
                spread >>= 1;
                currcnt = counts[codelen - 1];
                if (!currcnt) continue;
                if (ctx.length < currcnt) JPEG_DECODER_THROW(SyntaxError);
                remain -= currcnt << (16 - codelen);
                if (remain < 0) JPEG_DECODER_THROW(SyntaxError);
                for (i = 0; i < currcnt; ++i) {
                    unsigned char code = ctx.pos[i];
                    for (j = spread; j; --j) {
                        vlc->bits = (unsigned char)codelen;
                        vlc->code = code;
                        ++vlc;
                    }
                }
                Skip(currcnt);
            }
            while (remain--) {
                vlc->bits = 0;
                ++vlc;
            }
        }
        if (ctx.length) JPEG_DECODER_THROW(SyntaxError);
    }

    inline void DecodeDQT()
    {
        int i;
        unsigned char* t;
        DecodeLength();
        while (ctx.length >= 65) {
            i = ctx.pos[0];
            if (i & 0xFC) JPEG_DECODER_THROW(SyntaxError);
            ctx.qtavail |= 1 << i;
            t = &ctx.qtab[i][0];
            for (i = 0; i < 64; ++i)
                t[i] = ctx.pos[i + 1];
            Skip(65);
        }
        if (ctx.length) JPEG_DECODER_THROW(SyntaxError);
    }

    inline void DecodeDRI()
    {
        DecodeLength();
        if (ctx.length < 2) JPEG_DECODER_THROW(SyntaxError);
        ctx.rstinterval = Decode16(ctx.pos);
        Skip(ctx.length);
    }

    inline int GetVLC(VlcCode* vlc, unsigned char* code)
    {
        int value = ShowBits(16);
        int bits = vlc[value].bits;
        if (!bits) {
            ctx.error = SyntaxError;
            return 0;
        }
        SkipBits(bits);
        value = vlc[value].code;
        if (code) *code = (unsigned char)value;
        bits = value & 15;
        if (!bits) return 0;
        value = GetBits(bits);
        //if (value < (1 << (bits - 1))) value += ((-1) << bits) + 1;
        if (value < (1 << (bits - 1))) value += ((0xffffffff) << bits) + 1;
        return value;
    }

    inline void DecodeBlock(Component* c, unsigned char* out)
    {
        unsigned char code = 0;
        int value, coef = 0;
        memset(ctx.block, 0, sizeof(ctx.block));
        c->dcpred += GetVLC(&ctx.vlctab[c->dctabsel][0], NULL);
        ctx.block[0] = (c->dcpred) * ctx.qtab[c->qtsel][0];
        do {
            value = GetVLC(&ctx.vlctab[c->actabsel][0], &code);
            if (!code) break; // EOB
            if (!(code & 0x0F) && (code != 0xF0))
                JPEG_DECODER_THROW(SyntaxError);
            coef += (code >> 4) + 1;
            if (coef > 63) JPEG_DECODER_THROW(SyntaxError);
            ctx.block[(int)ZZ[coef]] = value * ctx.qtab[c->qtsel][coef];
        } while (coef < 63);
        for (coef = 0; coef < 64; coef += 8)
            RowIDCT(&ctx.block[coef]);
        for (coef = 0; coef < 8; ++coef)
            ColIDCT(&ctx.block[coef], &out[coef], c->stride);
    }

    inline void DecodeScan()
    {
        int i, mbx, mby, sbx, sby;
        int rstcount = ctx.rstinterval, nextrst = 0;
        Component* c;
        DecodeLength();
        if (ctx.length < (4 + 2 * ctx.ncomp)) JPEG_DECODER_THROW(SyntaxError);
        if (ctx.pos[0] != ctx.ncomp) JPEG_DECODER_THROW(Unsupported);
        Skip(1);
        for (i = 0, c = ctx.comp; i < ctx.ncomp; ++i, ++c) {
            if (ctx.pos[0] != c->cid) JPEG_DECODER_THROW(SyntaxError);
            if (ctx.pos[1] & 0xEE) JPEG_DECODER_THROW(SyntaxError);
            c->dctabsel = ctx.pos[1] >> 4;
            c->actabsel = (ctx.pos[1] & 1) | 2;
            Skip(2);
        }
        if (ctx.pos[0] || (ctx.pos[1] != 63) || ctx.pos[2])
            JPEG_DECODER_THROW(Unsupported);
        Skip(ctx.length);
        for (mby = 0; mby < ctx.mbheight; ++mby)
            for (mbx = 0; mbx < ctx.mbwidth; ++mbx) {
                for (i = 0, c = ctx.comp; i < ctx.ncomp; ++i, ++c)
                    for (sby = 0; sby < c->ssy; ++sby)
                        for (sbx = 0; sbx < c->ssx; ++sbx) {
                            DecodeBlock(c,
                                &c->pixels[((mby * c->ssy + sby) * c->stride +
                                               mbx * c->ssx + sbx)
                                           << 3]);
                            if (ctx.error) return;
                        }
                if (ctx.rstinterval && !(--rstcount)) {
                    ByteAlign();
                    i = GetBits(16);
                    if (((i & 0xFFF8) != 0xFFD0) || ((i & 7) != nextrst))
                        JPEG_DECODER_THROW(SyntaxError);
                    nextrst = (nextrst + 1) & 7;
                    rstcount = ctx.rstinterval;
                    for (i = 0; i < 3; ++i)
                        ctx.comp[i].dcpred = 0;
                }
            }
        ctx.error = Internal_Finished;
    }

    enum
    {
        CF4A = (-9),
        CF4B = (111),
        CF4C = (29),
        CF4D = (-3),
        CF3A = (28),
        CF3B = (109),
        CF3C = (-9),
        CF3X = (104),
        CF3Y = (27),
        CF3Z = (-3),
        CF2A = (139),
        CF2B = (-11),
    };

    inline unsigned char CF(const int x) { return Clip((x + 64) >> 7); }

    inline void UpsampleH(Component* c)
    {
        const int xmax = c->width - 3;
        unsigned char *out, *lin, *lout;
        int x, y;
        out = (unsigned char*)AllocMem((c->width * c->height) << 1);
        if (!out) JPEG_DECODER_THROW(OutOfMemory);
        lin = c->pixels;
        lout = out;
        for (y = c->height; y; --y) {
            lout[0] = CF(CF2A * lin[0] + CF2B * lin[1]);
            lout[1] = CF(CF3X * lin[0] + CF3Y * lin[1] + CF3Z * lin[2]);
            lout[2] = CF(CF3A * lin[0] + CF3B * lin[1] + CF3C * lin[2]);
            for (x = 0; x < xmax; ++x) {
                lout[(x << 1) + 3] = CF(CF4A * lin[x] + CF4B * lin[x + 1] +
                                        CF4C * lin[x + 2] + CF4D * lin[x + 3]);
                lout[(x << 1) + 4] = CF(CF4D * lin[x] + CF4C * lin[x + 1] +
                                        CF4B * lin[x + 2] + CF4A * lin[x + 3]);
            }
            lin += c->stride;
            lout += c->width << 1;
            lout[-3] = CF(CF3A * lin[-1] + CF3B * lin[-2] + CF3C * lin[-3]);
            lout[-2] = CF(CF3X * lin[-1] + CF3Y * lin[-2] + CF3Z * lin[-3]);
            lout[-1] = CF(CF2A * lin[-1] + CF2B * lin[-2]);
        }
        c->width <<= 1;
        c->stride = c->width;
        FreeMem(c->pixels);
        c->pixels = out;
    }

    inline void UpsampleV(Component* c)
    {
        const int w = c->width, s1 = c->stride, s2 = s1 + s1;
        unsigned char *out, *cin, *cout;
        int x, y;
        out = (unsigned char*)AllocMem((c->width * c->height) << 1);
        if (!out) JPEG_DECODER_THROW(OutOfMemory);
        for (x = 0; x < w; ++x) {
            cin = &c->pixels[x];
            cout = &out[x];
            *cout = CF(CF2A * cin[0] + CF2B * cin[s1]);
            cout += w;
            *cout = CF(CF3X * cin[0] + CF3Y * cin[s1] + CF3Z * cin[s2]);
            cout += w;
            *cout = CF(CF3A * cin[0] + CF3B * cin[s1] + CF3C * cin[s2]);
            cout += w;
            cin += s1;
            for (y = c->height - 3; y; --y) {
                *cout = CF(CF4A * cin[-s1] + CF4B * cin[0] + CF4C * cin[s1] +
                           CF4D * cin[s2]);
                cout += w;
                *cout = CF(CF4D * cin[-s1] + CF4C * cin[0] + CF4B * cin[s1] +
                           CF4A * cin[s2]);
                cout += w;
                cin += s1;
            }
            cin += s1;
            *cout = CF(CF3A * cin[0] + CF3B * cin[-s1] + CF3C * cin[-s2]);
            cout += w;
            *cout = CF(CF3X * cin[0] + CF3Y * cin[-s1] + CF3Z * cin[-s2]);
            cout += w;
            *cout = CF(CF2A * cin[0] + CF2B * cin[-s1]);
        }
        c->height <<= 1;
        c->stride = c->width;
        FreeMem(c->pixels);
        c->pixels = out;
    }

    inline void Convert()
    {
        int i;
        Component* c;
        for (i = 0, c = ctx.comp; i < ctx.ncomp; ++i, ++c) {
            while ((c->width < ctx.width) || (c->height < ctx.height)) {
                if (c->width < ctx.width) UpsampleH(c);
                if (ctx.error) return;
                if (c->height < ctx.height) UpsampleV(c);
                if (ctx.error) return;
            }
            if ((c->width < ctx.width) || (c->height < ctx.height))
                JPEG_DECODER_THROW(InternalError);
        }
        if (ctx.ncomp == 3) {
            // convert to RGB
            int x, yy;
            unsigned char* prgb = ctx.rgb;
            const unsigned char* py = ctx.comp[0].pixels;
            const unsigned char* pcb = ctx.comp[1].pixels;
            const unsigned char* pcr = ctx.comp[2].pixels;
            for (yy = ctx.height; yy; --yy) {
                for (x = 0; x < ctx.width; ++x) {
                    int y = py[x] << 8;
                    int cb = pcb[x] - 128;
                    int cr = pcr[x] - 128;
                    *prgb++ = Clip((y + 359 * cr + 128) >> 8);
                    *prgb++ = Clip((y - 88 * cb - 183 * cr + 128) >> 8);
                    *prgb++ = Clip((y + 454 * cb + 128) >> 8);
                }
                py += ctx.comp[0].stride;
                pcb += ctx.comp[1].stride;
                pcr += ctx.comp[2].stride;
            }
        } else if (ctx.comp[0].width != ctx.comp[0].stride) {
            // grayscale -> only remove stride
            unsigned char* pin = &ctx.comp[0].pixels[ctx.comp[0].stride];
            unsigned char* pout = &ctx.comp[0].pixels[ctx.comp[0].width];
            int y;
            for (y = ctx.comp[0].height - 1; y; --y) {
                memcpy(pout, pin, ctx.comp[0].width);
                pin += ctx.comp[0].stride;
                pout += ctx.comp[0].width;
            }
            ctx.comp[0].stride = ctx.comp[0].width;
        }
    }

    DecodeResult Decode(const unsigned char* jpeg, const int size)
    {
        ctx.pos = (const unsigned char*)jpeg;
        ctx.size = size & 0x7FFFFFFF;
        if (ctx.size < 2) return NotAJpeg;
        if ((ctx.pos[0] ^ 0xFF) | (ctx.pos[1] ^ 0xD8)) return NotAJpeg;
        Skip(2);
        while (!ctx.error) {
            if ((ctx.size < 2) || (ctx.pos[0] != 0xFF)) return SyntaxError;
            Skip(2);
            switch (ctx.pos[-1]) {
            case 0xC0: DecodeSOF(); break;
            case 0xC4: DecodeDHT(); break;
            case 0xDB: DecodeDQT(); break;
            case 0xDD: DecodeDRI(); break;
            case 0xDA: DecodeScan(); break;
            case 0xFE: SkipMarker(); break;
            default:
                if ((ctx.pos[-1] & 0xF0) == 0xE0)
                    SkipMarker();
                else
                    return Unsupported;
            }
        }
        if (ctx.error != Internal_Finished) return ctx.error;
        ctx.error = OK;
        Convert();
        return ctx.error;
    }
};

inline Decoder::Decoder(const unsigned char* data, size_t size,
    void* (*allocFunc)(size_t), void (*freeFunc)(void*))
    : AllocMem(allocFunc), FreeMem(freeFunc)
{
    // should be static data, but this keeps us as a header
    char temp[64] = {0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12,
        19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49,
        56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38,
        31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63};
    memcpy(ZZ, temp, sizeof(ZZ));
    memset(&ctx, 0, sizeof(Context));
    Decode(data, size);
}

inline Decoder::DecodeResult Decoder::GetResult() const
{
    return ctx.error;
}
inline int Decoder::GetWidth() const
{
    return ctx.width;
}
inline int Decoder::GetHeight() const
{
    return ctx.height;
}
inline bool Decoder::IsColor() const
{
    return ctx.ncomp != 1;
}
inline unsigned char* Decoder::GetImage() const
{
    return (ctx.ncomp == 1) ? ctx.comp[0].pixels : ctx.rgb;
}
inline size_t Decoder::GetImageSize() const
{
    return ctx.width * ctx.height * ctx.ncomp;
}

inline Decoder::~Decoder()
{
    int i;
    for (i = 0; i < 3; ++i)
        if (ctx.comp[i].pixels) FreeMem((void*)ctx.comp[i].pixels);
    if (ctx.rgb) FreeMem((void*)ctx.rgb);
}

} // namespace jpeg
#ifdef _MSC_VER
#    pragma warning(pop)
#endif

#undef JPEG_DECODER_THROW

