/* ***** BEGIN LICENSE BLOCK *****
 * Source last modified: $Id: pxpngdec.cpp,v 1.2.24.1 2004/07/09 01:52:12 hubbe Exp $
 * 
 * Portions Copyright (c) 1995-2004 RealNetworks, Inc. All Rights Reserved.
 * 
 * The contents of this file, and the files included with this file,
 * are subject to the current version of the RealNetworks Public
 * Source License (the "RPSL") available at
 * http://www.helixcommunity.org/content/rpsl unless you have licensed
 * the file under the current version of the RealNetworks Community
 * Source License (the "RCSL") available at
 * http://www.helixcommunity.org/content/rcsl, in which case the RCSL
 * will apply. You may also obtain the license terms directly from
 * RealNetworks.  You may not use this file except in compliance with
 * the RPSL or, if you have a valid RCSL with RealNetworks applicable
 * to this file, the RCSL.  Please see the applicable RPSL or RCSL for
 * the rights, obligations and limitations governing use of the
 * contents of the file.
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL") in which case the provisions of the GPL are applicable
 * instead of those above. If you wish to allow use of your version of
 * this file only under the terms of the GPL, and not to allow others
 * to use your version of this file under the terms of either the RPSL
 * or RCSL, indicate your decision by deleting the provisions above
 * and replace them with the notice and other provisions required by
 * the GPL. If you do not delete the provisions above, a recipient may
 * use your version of this file under the terms of any one of the
 * RPSL, the RCSL or the GPL.
 * 
 * This file is part of the Helix DNA Technology. RealNetworks is the
 * developer of the Original Code and owns the copyrights in the
 * portions it created.
 * 
 * This file, and the files included with this file, is distributed
 * and made available on an 'AS IS' basis, WITHOUT WARRANTY OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED, AND REALNETWORKS HEREBY DISCLAIMS
 * ALL SUCH WARRANTIES, INCLUDING WITHOUT LIMITATION, ANY WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET
 * ENJOYMENT OR NON-INFRINGEMENT.
 * 
 * Technology Compatibility Kit Test Suite(s) Location:
 *    http://www.helixcommunity.org/content/tck
 * 
 * Contributor(s):
 * 
 * ***** END LICENSE BLOCK ***** */

// include
#include "hxtypes.h"
#include "hxwintyp.h"
#include "hxresult.h"
#include "hxcom.h"
#include "hxcomm.h"
#include "ihxpckts.h"
#include "hxvsurf.h"

// pnmisc
#include "baseobj.h"
#include "netbyte.h"

// pxcomlib

// libpng
#include "png.h"

// pxpnglib
#include "pxpngdec.h"

// pndebug
#include "hxheap.h"
#ifdef _DEBUG
#undef HX_THIS_FILE     
static char HX_THIS_FILE[] = __FILE__;
#endif
#include "errdbg.h"
#include "hxperf.h"

PXPNGDecode::PXPNGDecode()
{
    m_lRefCount = 0;
    Reset();
}

PXPNGDecode::~PXPNGDecode()
{
    Deallocate();
}

void PXPNGDecode::Reset()
{
    m_pPNGStruct          = NULL;
    m_pPNGInfo            = NULL;
    m_pPNGEndInfo         = NULL;
    m_pOutputBuffer       = NULL;
    m_ppImageRow          = NULL;
    m_bSingleBufferIO     = FALSE;
    m_bDupFirstBuffer     = FALSE;
    m_bFirstDecompress    = TRUE;
    m_bDeferFirstBuffer   = FALSE;
    m_pFirstBuffer        = NULL;
    m_ulFirstBufferOffset = 0;
    m_bFinished           = FALSE;
    m_ulLastSeqNum        = 0;
    m_bValid              = TRUE;
}

void PXPNGDecode::Deallocate()
{
    DeallocateErrorHandling(m_pPNGStruct);
    DeallocateIOHandling(m_pPNGStruct);
    if (m_pPNGStruct)
    {
        png_destroy_read_struct(&m_pPNGStruct, &m_pPNGInfo, &m_pPNGEndInfo);
    }
    HX_RELEASE(m_pOutputBuffer);
    HX_VECTOR_DELETE(m_ppImageRow);
    Reset();
}

STDMETHODIMP PXPNGDecode::QueryInterface(REFIID riid, void** ppvObj)
{
    HX_RESULT retVal = HXR_OK;

    if (IsEqualIID(riid, IID_IUnknown))
    {
        AddRef();
        *ppvObj = (IUnknown*) this;
    }
    else
    {
        *ppvObj = NULL;
        retVal  = HXR_NOINTERFACE;
    }

    return retVal;
}

STDMETHODIMP_(UINT32) PXPNGDecode::AddRef()
{
    return InterlockedIncrement(&m_lRefCount);
}

STDMETHODIMP_(UINT32) PXPNGDecode::Release()
{
    
    if (InterlockedDecrement(&m_lRefCount) > 0)
        return m_lRefCount;

    delete this;

    return 0;
}

BOOL PXPNGDecode::ValidInputData(IHXBuffer* pBuffer)
{
    BOOL bRet = FALSE;

    if (pBuffer)
    {
        if (pBuffer->GetSize() >= 8)
        {
            BYTE* pBuf = pBuffer->GetBuffer();
            if (pBuf)
            {
                UINT32 ulSig0 = (pBuf[0] << 24) |
                                (pBuf[1] << 16) |
                                (pBuf[2] <<  8) |
                                (pBuf[3]);
                UINT32 ulSig1 = (pBuf[4] << 24) |
                                (pBuf[5] << 16) |
                                (pBuf[6] <<  8) |
                                (pBuf[7]);
                if (ulSig0 == kSig0 && ulSig1 == kSig1)
                {
                    bRet = TRUE;
                }
            }
        }
    }

    return bRet;
}

HX_RESULT PXPNGDecode::Init(IUnknown*   pContext,
                            IHXBuffer* pBuffer,
                            BOOL        bDupFirstBuffer)
{
    HX_LOG_BLOCK( "PXPNGDecode::Init" );

    HX_RESULT retVal = HXR_FAIL;

    if (pContext && pBuffer)
    {
        // Clear out and reset everything
        Deallocate();
        // Save member
        m_bDupFirstBuffer  = bDupFirstBuffer;
        // Init the first decompress flag
        m_bFirstDecompress = TRUE;
        // Init the finished flag
        m_bFinished        = FALSE;
        // Create a PXUserError struct
        PXUserError* pUserError = new PXUserError;
        if (pUserError)
        {
            // Init the members of the struct
            pUserError->m_pContext = pContext;
            pUserError->m_pContext->AddRef();
            pUserError->m_pErrorStr = NULL;
            // Create the read struct
            m_pPNGStruct = png_create_read_struct(PNG_LIBPNG_VER_STRING,
                                                  (png_voidp) pUserError,
                                                  HandleError,
                                                  HandleWarning);
            if (m_pPNGStruct)
            {
                // Set the error return jump
                if (setjmp(m_pPNGStruct->jmpbuf))
                {
                    // If we get here, then libpng threw an error and
                    // we longjmp'd back here.
                    return HXR_FAIL;
                }
                // Create the info struct
                m_pPNGInfo = png_create_info_struct(m_pPNGStruct);
                if (m_pPNGInfo)
                {
                    // Create the end info struct
                    m_pPNGEndInfo = png_create_info_struct(m_pPNGStruct);
                    if (m_pPNGEndInfo)
                    {
                        // If we have a complete IEND chunk, then we have
                        // the entire file. Therefore, we'll process this as a
                        // single buffer read. If we dont' have a complete IEND
                        // chunk, then we will attempt to process it as 
                        // a progressive read.
                        if (IsCompleteChunkPresent(pBuffer, kIEND))
                        {
                            // Set the single-buffer-IO flag
                            m_bSingleBufferIO = TRUE;
                            // First we need to create a single-buffer read struct
                            PXUserIOSingle* pUserIO = new PXUserIOSingle;
                            if (pUserIO)
                            {
                                // Set the members of the struct
                                pUserIO->m_pBuffer   = pBuffer;
                                pUserIO->m_ulOffset  = 0;
                                pUserIO->m_pBuffer->AddRef();
                                // Set up the single-buffer callback
                                png_set_read_fn(m_pPNGStruct,
                                                (png_voidp) pUserIO,
                                                (png_rw_ptr) SingleBufferRead);
                                // Now we can call to read all the way up
                                // to (but not including) the actual image data
                                // After this call, then all the accessor methods
                                // (GetWidth(), GetHeight(), etc.) should be valid
                                png_read_info(m_pPNGStruct, m_pPNGInfo);
                                // Clear the return value
                                retVal = HXR_OK;
                            }
                        }
                        else
                        {
                            // Clear the single-buffer-IO flag
                            m_bSingleBufferIO = FALSE;
                            // We require that at least the first kMinIDATBytes
                            // of the IDAT chunk to be present (just the length and the
                            // IDAT chunk type) in the first buffer. (This forces
                            // libpng to make the info callback after the first
                            // call to png_process_data(), which we need if we are
                            // going to be able to provide width/height info to
                            // the app). If we have more than the first kMinIDATBytes
                            // in the first buffer, that's OK - we will only supply
                            // up to the first 8 bytes to the first call to 
                            // png_process_data() and defer the processing of the rest
                            // of the first buffer.
                            UINT32 ulIDATBytes  = 0;
                            UINT32 ulIDATOffset = 0;
                            BOOL   bComplete    = FALSE;
                            BOOL   bIDATPresent = IsChunkPresent(pBuffer,
                                                                 kIDAT,
                                                                 ulIDATOffset,
                                                                 ulIDATBytes,
                                                                 bComplete);
                            if (bIDATPresent && ulIDATBytes >= kMinIDATBytes)
                            {
                                // Create a progressive read struct
                                PXUserIOProgressive* pUserIO = new PXUserIOProgressive;
                                if (pUserIO)
                                {
                                    // Set the member
                                    pUserIO->m_ulAppState  = kAppStateConstructed;
                                    pUserIO->m_ulDataState = kDataStateConstructed;
                                    pUserIO->m_ulNumRows   = 0;
                                    pUserIO->m_ppImageRow  = NULL;
                                    // Set up the progressive read callbacks
                                    png_set_progressive_read_fn(m_pPNGStruct,
                                                                (png_voidp) pUserIO,
                                                                InfoCallback,
                                                                RowCallback,
                                                                EndCallback);
                                    // Now if we have MORE than kMinIDATBytes in the
                                    // first buffer, then that's OK - we will defer
                                    // the processing of those bytes until later.
                                    // However, we must save the first buffer and
                                    // the offset and set a flag saying we need to
                                    // process the first buffer.
                                    UINT32 ulBytesToProcessNow = 0;
                                    if (ulIDATBytes > kMinIDATBytes)
                                    {
                                        // Save these members so we can process the
                                        // rest of the first buffer later
                                        m_bDeferFirstBuffer   = TRUE;
                                        m_ulFirstBufferOffset = ulIDATOffset + kMinIDATBytes;
                                        HX_RELEASE(m_pFirstBuffer);
                                        m_pFirstBuffer = pBuffer;
                                        m_pFirstBuffer->AddRef();
                                        // Compute the number of bytes to process
                                        // right now
                                        ulBytesToProcessNow = m_ulFirstBufferOffset;
                                    }
                                    else
                                    {
                                        // We had exactly kMinIDATBytes in the first buffer,
                                        // so there's no need to defer processing of
                                        // any part of the buffer
                                        m_bDeferFirstBuffer = FALSE;
                                        ulBytesToProcessNow = pBuffer->GetSize();
                                    }
#ifdef XXXMEH_DEBUG_LOG
                                    DEBUG_OUTF("c:\\pxpnglib.log", (s, "Init(): calling png_process_data()\n"));
#endif
                                    // Process the first buffer
                                    png_process_data(m_pPNGStruct,
                                                     m_pPNGInfo,
                                                     (png_bytep)  pBuffer->GetBuffer(),
                                                     (png_size_t) ulBytesToProcessNow);
                                    // Set the app state
                                    pUserIO->m_ulAppState = kAppStateInit;
                                    // Clear the return value
                                    retVal = HXR_OK;
                                }
                            }
                            else
                            {
                                CopyErrorString(m_pPNGStruct, "Not enough data in first packet.");
                            }
                        }
                    }
                }
            }
        }
    }

    return retVal;
}


HX_RESULT PXPNGDecode::SetDecompressParam(IHXBuffer* pOutputBuffer,
                                          UINT32      ulOutputWidth,
                                          UINT32      ulOutputHeight,
                                          UINT32      ulRowStride,
                                          UINT32      ulBitsPerPixel,
                                          UINT32      ulColorFormat,
                                          BOOL        bRowsInverted,
                                          BOOL        bTransparentIsZero)
{
    HX_LOG_BLOCK( "PXPNGDecode::SetDecompressParam" );

    HX_RESULT retVal = HXR_FAIL;

    if (pOutputBuffer && ulOutputWidth && ulOutputHeight && ulRowStride &&
        ulBitsPerPixel == 32 && ulColorFormat == HX_RGB)
    {
        if (m_pPNGStruct && m_pPNGInfo)
        {
            // We either need to be in single-buffer mode (in which
            // case we know we've read up through the data) or be in
            // progressive-read mode and gotten the InfoCallback.
            if (m_bSingleBufferIO ||
                (!m_bSingleBufferIO &&
                 IsDataStateEqual(m_pPNGStruct, kDataStateInfoCallback)))
            {
                // Set the error return jump
                if (setjmp(m_pPNGStruct->jmpbuf))
                {
                    // If we get here, then libpng threw an error and
                    // we longjmp'd back here.
                    return HXR_FAIL;
                }
                // Check for full size decode
                if (ulOutputWidth  == (UINT32) png_get_image_width(m_pPNGStruct, m_pPNGInfo) &&
                    ulOutputHeight == (UINT32) png_get_image_height(m_pPNGStruct, m_pPNGInfo))
                {
                    if (m_bSingleBufferIO)
                    {
                        // Set up the transforms
                        SetReadTransforms(m_pPNGStruct, m_pPNGInfo);
                    }
                    // libpng by default assumes that 0 is fully transparent and 255 is
                    // fully opaque. PXPNGDecode by default outputs alpha in the opposite
                    // sense (0 == fully opaque and 255 == fully transparent). The following
                    // allows the application using PXPNGDecode to switch back to libpng's
                    // default behavior.
                    if (bTransparentIsZero)
                    {
                        png_clear_invert_alpha(m_pPNGStruct);
                    }
                    // Save a copy of the output buffer
                    HX_RELEASE(m_pOutputBuffer);
                    m_pOutputBuffer = pOutputBuffer;
                    m_pOutputBuffer->AddRef();
                    // Set up the row pointers
                    retVal = SetupRowPointers(ulOutputHeight,
                                              pOutputBuffer,
                                              ulRowStride,
                                              bRowsInverted);
                    if (SUCCEEDED(retVal) && !m_bSingleBufferIO)
                    {
                        // We just need to copy the number of rows and the
                        // m_ppImageRow pointer into progressive user i/o struct
                        PXUserIOProgressive* pUserIO = (PXUserIOProgressive*) png_get_progressive_ptr(m_pPNGStruct);
                        if (pUserIO)
                        {
                            pUserIO->m_ulNumRows  = ulOutputHeight;
                            HX_VECTOR_DELETE(pUserIO->m_ppImageRow);
                            pUserIO->m_ppImageRow = m_ppImageRow;
                        }
                    }
                }
            }
        }
    }

    return retVal;
}

HX_RESULT PXPNGDecode::Decompress(IHXBuffer* pBuffer)
{
    HX_LOG_BLOCK( "PXPNGDecode::Decompress" );
    
    HX_RESULT retVal = HXR_OK;

    if (m_pPNGStruct && m_ppImageRow && m_pPNGEndInfo && !m_bFinished)
    {
        // Set the error return jump
        if (setjmp(m_pPNGStruct->jmpbuf))
        {
            // If we get here, then libpng threw an error and
            // we longjmp'd back here.
            return HXR_FAIL;
        }
        // Are we in single-buffer or progressive-read mode?
        if (m_bSingleBufferIO)
        {
            // We are in single-buffer mode. Therefore, we
            // can now decompress the whole image. Note that
            // in this mode it doesn't matter whether they
            // duplicate the first buffer or not.
            png_read_image(m_pPNGStruct, m_ppImageRow);
            // Now read the end info (might have text or time info)
            png_read_end(m_pPNGStruct, m_pPNGEndInfo);
            // Set the finished flag
            m_bFinished = TRUE;
        }
        else
        {
            // If we had deferred processing of part of the
            // first buffer, then we need to do that now.
            if (m_bDeferFirstBuffer)
            {
                // Clear the flag
                m_bDeferFirstBuffer = FALSE;
#ifdef XXXMEH_DEBUG_LOG
                DEBUG_OUTF("c:\\pxpnglib.log", (s, "Decompress(): deferred first buffer call to png_process_data()\n"));
#endif
                // Process the rest of the first buffer
                png_process_data(m_pPNGStruct,
                                 m_pPNGInfo,
                                 (png_bytep)  m_pFirstBuffer->GetBuffer() +
                                              m_ulFirstBufferOffset,
                                 (png_size_t) m_pFirstBuffer->GetSize() -
                                              m_ulFirstBufferOffset);
                // Release the first buffer
                HX_RELEASE(m_pFirstBuffer);
                m_ulFirstBufferOffset = 0;
            }
            // Make sure if the first buffer is duplicated,
            // that we are not passing it in twice.
            if (!(m_bFirstDecompress && m_bDupFirstBuffer))
            {
#ifdef XXXMEH_DEBUG_LOG
                DEBUG_OUTF("c:\\pxpnglib.log", (s, "Decompress(): normal call to png_process_data()\n"));
#endif
                png_process_data(m_pPNGStruct,
                                 m_pPNGInfo,
                                 (png_bytep)  pBuffer->GetBuffer(),
                                 (png_size_t) pBuffer->GetSize());
            }
            // Check to see if the EndCallback has been 
            // called. If so, then we are finished.
            if (IsDataStateEqual(m_pPNGStruct, kDataStateEndCallback))
            {
                m_bFinished = TRUE;
            }
        }
        // Clear the first decompress flag
        m_bFirstDecompress = FALSE;
    }
    else
    {
        retVal = HXR_UNEXPECTED;
    }

    return retVal;
}

HX_RESULT PXPNGDecode::GetErrorString(REF(IHXBuffer*) rpErrStr)
{
    HX_RESULT retVal = HXR_FAIL;

    if (m_pPNGStruct)
    {
        PXUserError* pUserError = (PXUserError*) png_get_error_ptr(m_pPNGStruct);
        if (pUserError && pUserError->m_pErrorStr)
        {
            HX_RELEASE(rpErrStr);
            rpErrStr = pUserError->m_pErrorStr;
            rpErrStr->AddRef();
        }
    }

    return retVal;
}

void PXPNGDecode::DeallocateErrorHandling(png_structp png_ptr)
{
    if (png_ptr)
    {
        PXUserError* pUserError = (PXUserError*) png_get_error_ptr(png_ptr);
        if (pUserError)
        {
            HX_RELEASE(pUserError->m_pContext);
            HX_RELEASE(pUserError->m_pErrorStr);
        }
        HX_DELETE(pUserError);
    }
}

void PXPNGDecode::DeallocateIOHandling(png_structp png_ptr)
{
    if (png_ptr)
    {
        if (m_bSingleBufferIO)
        {
            // Deallocate any single-buffer IO 
            PXUserIOSingle* pUserIOSingle = (PXUserIOSingle*) png_get_io_ptr(png_ptr);
            if (pUserIOSingle)
            {
                HX_RELEASE(pUserIOSingle->m_pBuffer);
            }
            HX_DELETE(pUserIOSingle);
        }
        else
        {
            // Deallocate any progressive-read IO
            PXUserIOProgressive* pUserIOProg = (PXUserIOProgressive*) png_get_progressive_ptr(png_ptr);
            HX_DELETE(pUserIOProg);
        }
    }
}

BOOL PXPNGDecode::IsChunkPresent(IHXBuffer* pBuffer, UINT32 ulChunkType,
                                 REF(UINT32) rulOffset, REF(UINT32) rulNumBytes,
                                 REF(BOOL) rbComplete)
{
    BOOL bRet = FALSE;

    if (pBuffer)
    {
        BYTE* pBufStart = pBuffer->GetBuffer();
        BYTE* pBufLimit = pBuffer->GetBuffer() + pBuffer->GetSize();
        BYTE* pBuf      = pBufStart;
        if (pBuf)
        {
            UINT32 ulSig0 = (pBuf[0] << 24) |
                            (pBuf[1] << 16) |
                            (pBuf[2] <<  8) |
                            (pBuf[3]);
            UINT32 ulSig1 = (pBuf[4] << 24) |
                            (pBuf[5] << 16) |
                            (pBuf[6] <<  8) |
                            (pBuf[7]);
            if (ulSig0 == kSig0 && ulSig1 == kSig1)
            {
                // We detected a signature, so we will skip it
                pBuf += 8;
            }
            // Scan the chunks
            while (pBuf < pBufLimit)
            {
                if (pBuf + 8 <= pBufLimit)
                {
                    UINT32 ulLength = (pBuf[0] << 24) |
                                      (pBuf[1] << 16) |
                                      (pBuf[2] <<  8) |
                                      (pBuf[3]);
                    UINT32 ulType   = (pBuf[4] << 24) |
                                      (pBuf[5] << 16) |
                                      (pBuf[6] <<  8) |
                                      (pBuf[7]);
                    UINT32 ulTotal  = 8 + ulLength + 4;
                    if (ulType == ulChunkType)
                    {
                        bRet      = TRUE;
                        rulOffset = pBuf - pBufStart;
                        if (pBuf + ulTotal <= pBufLimit)
                        {
                            rbComplete  = TRUE;
                            rulNumBytes = ulTotal;
                        }
                        else
                        {
                            rbComplete  = FALSE;
                            rulNumBytes = pBufLimit - pBuf;
                        }
			break;
                    }
                    pBuf += ulTotal;
                }
                else
                {
                    pBuf += 8;
                }
            }
        }
    }

    return bRet;
}

BOOL PXPNGDecode::IsCompleteChunkPresent(IHXBuffer* pBuffer, UINT32 ulChunkType)
{
    UINT32 ulOffset   = 0;
    UINT32 ulNumBytes = 0;
    BOOL   bComplete  = FALSE;
    BOOL   bPresent   = IsChunkPresent(pBuffer, ulChunkType, ulOffset,
                                       ulNumBytes, bComplete);
    return (bPresent && bComplete ? TRUE : FALSE);
}

BOOL PXPNGDecode::GetIHDRInfo(IHXBuffer* pBuffer, REF(UINT32) rulWidth, REF(UINT32) rulHeight)
{
    UINT32 ulOffset   = 0;
    UINT32 ulNumBytes = 0;
    BOOL   bComplete  = FALSE;
    BOOL   bPresent   = IsChunkPresent(pBuffer, kIHDR, ulOffset, ulNumBytes, bComplete);
    if (bPresent && bComplete)
    {
        BYTE* pBuf = pBuffer->GetBuffer() + ulOffset + 8;
        rulWidth   = (pBuf[0] << 24) |
                     (pBuf[1] << 16) |
                     (pBuf[2] <<  8) |
                     (pBuf[3]);
        rulHeight  = (pBuf[4] << 24) |
                     (pBuf[5] << 16) |
                     (pBuf[6] <<  8) |
                     (pBuf[7]);
    }

    return (bPresent && bComplete ? TRUE : FALSE);
}

void PXPNGDecode::SetReadTransforms(png_structp png_ptr, png_infop info)
{
    if (png_ptr && info)
    {
        UINT32 ulColorType = (UINT32) png_get_color_type(png_ptr, info);
        UINT32 ulBitDepth  = (UINT32) png_get_bit_depth(png_ptr, info);
        BOOL   bBigEndian  = TestBigEndian();

        // We handle transformations on a per-color-type basis
        if (ulColorType == PNG_COLOR_TYPE_GRAY)
        {
            // All bit depths (1, 2, 4, 8, 16) are allowed for PNG_COLOR_TYPE_GRAY, so
            // if it's less than 8bpp, we need to expand to 8bpp
            if (ulBitDepth < 8 || png_get_valid(png_ptr, info, PNG_INFO_tRNS))
            {
                png_set_expand(png_ptr);
            }
            // If it's 16bpp, we need to go down to 8bpp
            if (ulBitDepth > 8)
            {
                png_set_strip_16(png_ptr);
            }
            // Convert gray to RGB
            png_set_gray_to_rgb(png_ptr);
            // If we have a tRNS chunk, then we will have expanded it
            // to a full alpha channel in png_set_expand(), so we
            // have to check so we can deal with alpha
            if (png_get_valid(png_ptr, info, PNG_INFO_tRNS))
            {
                // If we are big-endan we want ARGB, if we are little endian, we need BGRA.
                // However, we don't need to worry about RGB vs. BGR since we are converting
                // from gray so R = G = B.
                if (bBigEndian)
                {
                    png_set_swap_alpha(png_ptr);
                }
                // PNG thinks that A = 0 is fully transparent and A = 255 is fully
                // opaque. We want the opposite (A = 0 is fully opaque and A = 255 is
                // fully transparent). So we call png_set_invert_alpha().
                png_set_invert_alpha(png_ptr);
            }
            else
            {
                // We DON'T have a tRNS chunk, so we must fill the alpha
                // byte with 0, either before or after the RGB triplet.
                png_set_filler(png_ptr, 0, (bBigEndian ? PNG_FILLER_BEFORE : PNG_FILLER_AFTER));
            }
        }
        else if (ulColorType == PNG_COLOR_TYPE_GRAY_ALPHA)
        {
            // Only bit depths 8 and 16 are allowed, so we need to just chop 16 down to 8
            if (ulBitDepth > 8)
            {
                png_set_strip_16(png_ptr);
            }
            // Convert gray to RGB
            png_set_gray_to_rgb(png_ptr);
            // If we are big-endan we want ARGB, if we are little endian, we need BGRA.
            // However, we don't need to worry about RGB vs. BGR since we are converting
            // from gray so R = G = B.
            if (bBigEndian)
            {
                png_set_swap_alpha(png_ptr);
            }
            // PNG thinks that A = 0 is fully transparent and A = 255 is fully
            // opaque. We want the opposite (A = 0 is fully opaque and A = 255 is
            // fully transparent). So we call png_set_invert_alpha().
            png_set_invert_alpha(png_ptr);
        }
        else if (ulColorType == PNG_COLOR_TYPE_PALETTE)
        {
            // Convert palettized to RGB
            png_set_expand(png_ptr);
            // If we are not big endian we need BGR instead of RGB
            if (!bBigEndian)
            {
                png_set_bgr(png_ptr);
            }
            // If we DID expand a tRNS chunk to a full alpha channel, then
            // we need to set how we read alpha. If we did NOT, then we need to
            // set the filler value.
            if (png_get_valid(png_ptr, info, PNG_INFO_tRNS))
            {
                // If we are big-endan we want ARGB, if we are little endian, we need BGRA.
                if (bBigEndian)
                {
                    png_set_swap_alpha(png_ptr);
                }
                // PNG thinks that A = 0 is fully transparent and A = 255 is fully
                // opaque. We want the opposite (A = 0 is fully opaque and A = 255 is
                // fully transparent). So we call png_set_invert_alpha().
                png_set_invert_alpha(png_ptr);
            }
            else
            {
                png_set_filler(png_ptr, 0, (bBigEndian ? PNG_FILLER_BEFORE : PNG_FILLER_AFTER));
            }
        }
        else if (ulColorType == PNG_COLOR_TYPE_RGB)
        {
            // Only bit depths 8 and 16 are allowed, so we need to just chop 16 down to 8
            if (ulBitDepth > 8)
            {
                png_set_strip_16(png_ptr);
            }
            // Set BGR vs. RGB
            if (!bBigEndian)
            {
                png_set_bgr(png_ptr);
            }
            // If there is a tRNS chunk, we need to expand to full alpha channel
            if (png_get_valid(png_ptr, info, PNG_INFO_tRNS))
            {
                // Expand tRNS chunk to full alpha channel
                png_set_expand(png_ptr);
                // If we are big-endan we want ARGB, if we are little endian, we need BGRA.
                if (bBigEndian)
                {
                    png_set_swap_alpha(png_ptr);
                }
                // PNG thinks that A = 0 is fully transparent and A = 255 is fully
                // opaque. We want the opposite (A = 0 is fully opaque and A = 255 is
                // fully transparent). So we call png_set_invert_alpha().
                png_set_invert_alpha(png_ptr);
            }
            else
            {
                // There will not be an alpha channel, so we need to set the filler
                png_set_filler(png_ptr, 0, (bBigEndian ? PNG_FILLER_BEFORE : PNG_FILLER_AFTER));
            }
        }
        else if (ulColorType == PNG_COLOR_TYPE_RGB_ALPHA)
        {
            // Only bit depths 8 and 16 are allowed, so we need to just chop 16 down to 8
            if (ulBitDepth > 8)
            {
                png_set_strip_16(png_ptr);
            }
            // Now we know we have 32bpp with alpha, we we just need
            // to get the ordering right. Right now, we will get RGBA.
            if (bBigEndian)
            {
                // Get ARGB instead of RGBA
                png_set_swap_alpha(png_ptr);
            }
            else
            {
                // Get BGRA instead of RGBA
                png_set_bgr(png_ptr);
            }
            // PNG thinks that A = 0 is fully transparent and A = 255 is fully
            // opaque. We want the opposite (A = 0 is fully opaque and A = 255 is
            // fully transparent). So we call png_set_invert_alpha().
            png_set_invert_alpha(png_ptr);
        }

        // Tell libpng to handle the interlacing details
        INT32 lNumPasses = png_set_interlace_handling(png_ptr);

        // Update the info struct
        png_read_update_info(png_ptr, info);
    }
}

HX_RESULT PXPNGDecode::SetupRowPointers(UINT32 ulHeight, IHXBuffer* pBuffer,
                                        UINT32 ulRowStride, BOOL bRowsInverted)
{
    HX_RESULT retVal = HXR_FAIL;

    if (ulHeight)
    {
        HX_VECTOR_DELETE(m_ppImageRow);
        m_ppImageRow = new BYTE* [ulHeight];
        if (m_ppImageRow)
        {
            for (UINT32 i = 0; i < ulHeight; i++)
            {
                UINT32 ulRow    = (bRowsInverted ? ulHeight - 1 - i : i);
                m_ppImageRow[i] = pBuffer->GetBuffer() + ulRow * ulRowStride;
            }
            retVal = HXR_OK;
        }
    }

    return retVal;
}

void PXPNGDecode::SingleBufferRead(png_structp png_ptr, png_bytep data, png_size_t length)
{
    HX_RESULT retVal = HXR_FAIL;

    if (png_ptr && data && length)
    {
        PXUserIOSingle* pUserIO = (PXUserIOSingle*) png_get_io_ptr(png_ptr);
        if (pUserIO && pUserIO->m_pBuffer)
        {
            UINT32 ulNumBytes = (UINT32) length;
            if (pUserIO->m_ulOffset + ulNumBytes > pUserIO->m_pBuffer->GetSize())
            {
                ulNumBytes = pUserIO->m_pBuffer->GetSize() - pUserIO->m_ulOffset;
            }
            // Copy the data
            memcpy(data, /* Flawfinder: ignore */
                   pUserIO->m_pBuffer->GetBuffer() + pUserIO->m_ulOffset,
                   ulNumBytes);
            // Update the offset
            pUserIO->m_ulOffset += ulNumBytes;
            // Clear the error return
            retVal = HXR_OK;
        }
    }

    if (FAILED(retVal))
    {
        png_error(png_ptr, "read Error");
    }
}

void PXPNGDecode::HandleError(png_structp png_ptr, png_const_charp message)
{
    // Copy the error string into our custom error struct
    CopyErrorString(png_ptr, message);

    // longjmp back to the error location
    longjmp(png_ptr->jmpbuf, 1);
}

void PXPNGDecode::HandleWarning(png_structp png_ptr, png_const_charp message)
{
    // Copy the error string into our custom error struct
    CopyErrorString(png_ptr, message);
}

void PXPNGDecode::CopyErrorString(png_structp png_ptr, png_const_charp message)
{
    if (png_ptr && message)
    {
        PXUserError* pUserError = (PXUserError*) png_get_error_ptr(png_ptr);
        if (pUserError)
        {
            IUnknown* pContext = pUserError->m_pContext;
            if (pContext)
            {
                IHXCommonClassFactory* pFactory = NULL;
                HX_RESULT retVal = pContext->QueryInterface(IID_IHXCommonClassFactory,
                                                            (void**) &pFactory);
                if (SUCCEEDED(retVal))
                {
                    IHXBuffer* pBuffer = NULL;
                    retVal = pFactory->CreateInstance(CLSID_IHXBuffer,
                                                      (void**) &pBuffer);
                    if (SUCCEEDED(retVal))
                    {
                        retVal = pBuffer->Set((const BYTE*) message, strlen(message) + 1);
                        if (SUCCEEDED(retVal))
                        {
                            HX_RELEASE(pUserError->m_pErrorStr);
                            pUserError->m_pErrorStr = pBuffer;
                            pUserError->m_pErrorStr->AddRef();
                        }
                    }
                    HX_RELEASE(pBuffer);
                }
                HX_RELEASE(pFactory);
            }
        }
    }
}

void PXPNGDecode::InfoCallback(png_structp png_ptr, png_infop info)
{
    // Set the data state
    SetProgressiveDataState(png_ptr, kDataStateInfoCallback);
    // Set up the read transforms
    SetReadTransforms(png_ptr, info);
#ifdef XXXMEH_DEBUG_LOG
    DEBUG_OUTF("c:\\pxpnglib.log", (s, "InfoCallback(0x%08X, 0x%08X)\n", png_ptr, info));
#endif
}

void PXPNGDecode::RowCallback(png_structp png_ptr, png_bytep new_row,
                              png_uint_32 row_num, int pass)
{
    // Set the data state
    SetProgressiveDataState(png_ptr, kDataStateRowCallback);
    // We just need to combine the old row with the new row (if
    // interlaced) or copy the new row (if not interlaced)
    PXUserIOProgressive* pUserIO = (PXUserIOProgressive*) png_get_progressive_ptr(png_ptr);
    if (pUserIO && pUserIO->m_ppImageRow && row_num < pUserIO->m_ulNumRows)
    {
        png_progressive_combine_row(png_ptr, pUserIO->m_ppImageRow[row_num], new_row);
    }
#ifdef XXXMEH_DEBUG_LOG
    DEBUG_OUTF("c:\\pxpnglib.log", (s, "RowCallback(0x%08X, %lu, %lu)\n", new_row, row_num, pass));
#endif
}

void PXPNGDecode::EndCallback(png_structp png_ptr, png_infop info)
{
    // Set the data state
    SetProgressiveDataState(png_ptr, kDataStateEndCallback);
#ifdef XXXMEH_DEBUG_LOG
    DEBUG_OUTF("c:\\pxpnglib.log", (s, "EndCallback(0x%08X, 0x%08X)\n", png_ptr, info));
#endif
}

void PXPNGDecode::SetProgressiveAppState(png_structp png_ptr, UINT32 ulState)
{
    if (png_ptr)
    {
        PXUserIOProgressive* pUserIO = (PXUserIOProgressive*) png_get_progressive_ptr(png_ptr);
        if (pUserIO)
        {
            pUserIO->m_ulAppState = ulState;
        }
    }
}

void PXPNGDecode::SetProgressiveDataState(png_structp png_ptr, UINT32 ulState)
{
    if (png_ptr)
    {
        PXUserIOProgressive* pUserIO = (PXUserIOProgressive*) png_get_progressive_ptr(png_ptr);
        if (pUserIO)
        {
            pUserIO->m_ulDataState = ulState;
        }
    }
}

BOOL PXPNGDecode::IsDataStateEqual(png_structp png_ptr, UINT32 ulState)
{
    BOOL bRet = FALSE;

    if (png_ptr)
    {
        PXUserIOProgressive* pUserIO = (PXUserIOProgressive*) png_get_progressive_ptr(png_ptr);
        if (pUserIO && pUserIO->m_ulDataState == ulState)
        {
            bRet = TRUE;
        }
    }

    return bRet;
}
