/******************************************************************************
 *
 * Project:  JPEG2000 driver based on OpenJPEG library
 * Purpose:  JPEG2000 driver based on OpenJPEG library
 * Author:   Even Rouault, <even dot rouault at spatialys dot com>
 *
 ******************************************************************************
 * Copyright (c) 2010-2014, Even Rouault <even dot rouault at spatialys dot com>
 * Copyright (c) 2015, European Union (European Environment Agency)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

/* This file is to be used with openjpeg 2.1 or later */
#ifdef __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunknown-pragmas"
#pragma clang diagnostic ignored "-Wdocumentation"
#endif

#include <openjpeg.h>
#include <opj_config.h>

#ifdef __clang__
#pragma clang diagnostic pop
#endif

#define IS_OPENJPEG_OR_LATER(major, minor, patch)                              \
    ((OPJ_VERSION_MAJOR * 10000 + OPJ_VERSION_MINOR * 100 +                    \
      OPJ_VERSION_BUILD) >= ((major)*10000 + (minor)*100 + (patch)))

#include <cassert>
#include <vector>

#include "cpl_atomic_ops.h"
#include "cpl_multiproc.h"
#include "cpl_string.h"
#include "cpl_worker_thread_pool.h"
#include "gdal_frmts.h"
#include "gdaljp2abstractdataset.h"
#include "gdaljp2metadata.h"
#include "vrt/vrtdataset.h"

#include <algorithm>

// #define DEBUG_IO

/************************************************************************/
/*                  JP2OpenJPEGDataset_ErrorCallback()                  */
/************************************************************************/

static void JP2OpenJPEGDataset_ErrorCallback(const char *pszMsg,
                                             CPL_UNUSED void *unused)
{
    CPLError(CE_Failure, CPLE_AppDefined, "%s", pszMsg);
}

/************************************************************************/
/*               JP2OpenJPEGDataset_WarningCallback()                   */
/************************************************************************/

static void JP2OpenJPEGDataset_WarningCallback(const char *pszMsg,
                                               CPL_UNUSED void *unused)
{
    if (strcmp(pszMsg, "No incltree created.\n") == 0 ||
        strcmp(pszMsg, "No imsbtree created.\n") == 0 ||
        strcmp(pszMsg, "tgt_create tree->numnodes == 0, no tree created.\n") ==
            0)
    {
        // Ignore warnings related to empty tag-trees. There's nothing wrong
        // about that.
        // Fixed submitted upstream with
        // https://github.com/uclouvain/openjpeg/pull/893
        return;
    }
    if (strcmp(pszMsg, "Empty SOT marker detected: Psot=12.\n") == 0)
    {
        static int bWarningEmitted = FALSE;
        if (bWarningEmitted)
            return;
        bWarningEmitted = TRUE;
    }
    if (strcmp(pszMsg, "JP2 box which are after the codestream will not be "
                       "read by this function.\n") != 0)
    {
        std::string osMsg(pszMsg);
        if (!osMsg.empty() && osMsg.back() == '\n')
            osMsg.resize(osMsg.size() - 1);
        CPLError(CE_Warning, CPLE_AppDefined, "%s", osMsg.c_str());
    }
}

/************************************************************************/
/*                 JP2OpenJPEGDataset_InfoCallback()                    */
/************************************************************************/

static void JP2OpenJPEGDataset_InfoCallback(const char *pszMsg,
                                            CPL_UNUSED void *unused)
{
    std::string osMsg(pszMsg);
    if (!osMsg.empty() && osMsg.back() == '\n')
        osMsg.resize(osMsg.size() - 1);
    CPLDebug("OPENJPEG", "info: %s", osMsg.c_str());
}

typedef struct
{
    VSILFILE *fp;
    vsi_l_offset nBaseOffset;
} JP2OpenJPEGFile;

/************************************************************************/
/*                      JP2OpenJPEGDataset_Read()                       */
/************************************************************************/

static OPJ_SIZE_T JP2OpenJPEGDataset_Read(void *pBuffer, OPJ_SIZE_T nBytes,
                                          void *pUserData)
{
    JP2OpenJPEGFile *psJP2OpenJPEGFile = (JP2OpenJPEGFile *)pUserData;
    OPJ_SIZE_T nRet = static_cast<OPJ_SIZE_T>(
        VSIFReadL(pBuffer, 1, nBytes, psJP2OpenJPEGFile->fp));
#ifdef DEBUG_IO
    CPLDebug("OPENJPEG",
             "JP2OpenJPEGDataset_Read(" CPL_FRMT_GUIB ") = " CPL_FRMT_GUIB,
             static_cast<GUIntBig>(nBytes), static_cast<GUIntBig>(nRet));
#endif
    if (nRet == 0)
        nRet = static_cast<OPJ_SIZE_T>(-1);

    return nRet;
}

/************************************************************************/
/*                      JP2OpenJPEGDataset_Write()                      */
/************************************************************************/

static OPJ_SIZE_T JP2OpenJPEGDataset_Write(void *pBuffer, OPJ_SIZE_T nBytes,
                                           void *pUserData)
{
    JP2OpenJPEGFile *psJP2OpenJPEGFile = (JP2OpenJPEGFile *)pUserData;
    OPJ_SIZE_T nRet = static_cast<OPJ_SIZE_T>(
        VSIFWriteL(pBuffer, 1, nBytes, psJP2OpenJPEGFile->fp));
#ifdef DEBUG_IO
    CPLDebug("OPENJPEG",
             "JP2OpenJPEGDataset_Write(" CPL_FRMT_GUIB ") = " CPL_FRMT_GUIB,
             static_cast<GUIntBig>(nBytes), static_cast<GUIntBig>(nRet));
#endif
    if (nRet != nBytes)
        return static_cast<OPJ_SIZE_T>(-1);
    return nRet;
}

/************************************************************************/
/*                       JP2OpenJPEGDataset_Seek()                      */
/************************************************************************/

static OPJ_BOOL JP2OpenJPEGDataset_Seek(OPJ_OFF_T nBytes, void *pUserData)
{
    JP2OpenJPEGFile *psJP2OpenJPEGFile = (JP2OpenJPEGFile *)pUserData;
#ifdef DEBUG_IO
    CPLDebug("OPENJPEG", "JP2OpenJPEGDataset_Seek(" CPL_FRMT_GUIB ")",
             static_cast<GUIntBig>(nBytes));
#endif
    return VSIFSeekL(psJP2OpenJPEGFile->fp,
                     psJP2OpenJPEGFile->nBaseOffset + nBytes, SEEK_SET) == 0;
}

/************************************************************************/
/*                     JP2OpenJPEGDataset_Skip()                        */
/************************************************************************/

static OPJ_OFF_T JP2OpenJPEGDataset_Skip(OPJ_OFF_T nBytes, void *pUserData)
{
    JP2OpenJPEGFile *psJP2OpenJPEGFile = (JP2OpenJPEGFile *)pUserData;
    vsi_l_offset nOffset = VSIFTellL(psJP2OpenJPEGFile->fp);
    nOffset += nBytes;
#ifdef DEBUG_IO
    CPLDebug("OPENJPEG",
             "JP2OpenJPEGDataset_Skip(" CPL_FRMT_GUIB " -> " CPL_FRMT_GUIB ")",
             static_cast<GUIntBig>(nBytes), static_cast<GUIntBig>(nOffset));
#endif
    VSIFSeekL(psJP2OpenJPEGFile->fp, nOffset, SEEK_SET);
    return nBytes;
}

/************************************************************************/
/* ==================================================================== */
/*                           JP2OpenJPEGDataset                         */
/* ==================================================================== */
/************************************************************************/

class JP2OpenJPEGRasterBand;

class JP2OpenJPEGDataset final : public GDALJP2AbstractDataset
{
    friend class JP2OpenJPEGRasterBand;

    std::string m_osFilename;
    VSILFILE *fp = nullptr; /* Large FILE API */
    vsi_l_offset nCodeStreamStart = 0;
    vsi_l_offset nCodeStreamLength = 0;

    OPJ_COLOR_SPACE eColorSpace = OPJ_CLRSPC_UNKNOWN;
    int nRedIndex = 0;
    int nGreenIndex = 1;
    int nBlueIndex = 2;
    int nAlphaIndex = -1;

    int bIs420 = FALSE;

    int nParentXSize = 0;
    int nParentYSize = 0;
    int iLevel = 0;
    int nOverviewCount = 0;
    JP2OpenJPEGDataset **papoOverviewDS = nullptr;
    bool bUseSetDecodeArea = false;
    bool bSingleTiled = false;
#if IS_OPENJPEG_OR_LATER(2, 3, 0)
    opj_codec_t **m_ppCodec = nullptr;
    opj_stream_t **m_ppStream = nullptr;
    opj_image_t **m_ppsImage = nullptr;
    JP2OpenJPEGFile *m_psJP2OpenJPEGFile = nullptr;
    int *m_pnLastLevel = nullptr;
#endif
    int m_nX0 = 0;
    int m_nY0 = 0;

    int nThreads = -1;
    int m_nBlocksToLoad = 0;
    int GetNumThreads();
    int bEnoughMemoryToLoadOtherBands = TRUE;
    int bRewrite = FALSE;
    int bHasGeoreferencingAtOpening = FALSE;

#if IS_OPENJPEG_OR_LATER(2, 5, 0)
    bool m_bStrict = true;
#endif

  protected:
    virtual int CloseDependentDatasets() override;
    virtual VSILFILE *GetFileHandle() override
    {
        return fp;
    }

  public:
    JP2OpenJPEGDataset();
    virtual ~JP2OpenJPEGDataset();

    static int Identify(GDALOpenInfo *poOpenInfo);
    static GDALDataset *Open(GDALOpenInfo *);
    static GDALDataset *CreateCopy(const char *pszFilename,
                                   GDALDataset *poSrcDS, int bStrict,
                                   char **papszOptions,
                                   GDALProgressFunc pfnProgress,
                                   void *pProgressData);

    CPLErr SetSpatialRef(const OGRSpatialReference *poSRS) override;

    virtual CPLErr SetGeoTransform(double *) override;

    CPLErr SetGCPs(int nGCPCountIn, const GDAL_GCP *pasGCPListIn,
                   const OGRSpatialReference *poSRS) override;

    virtual CPLErr SetMetadata(char **papszMetadata,
                               const char *pszDomain = "") override;
    virtual CPLErr SetMetadataItem(const char *pszName, const char *pszValue,
                                   const char *pszDomain = "") override;

    virtual CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
                             int nXSize, int nYSize, void *pData, int nBufXSize,
                             int nBufYSize, GDALDataType eBufType,
                             int nBandCount, int *panBandMap,
                             GSpacing nPixelSpace, GSpacing nLineSpace,
                             GSpacing nBandSpace,
                             GDALRasterIOExtraArg *psExtraArg) override;

    CPLErr IBuildOverviews(const char *pszResampling, int nOverviews,
                           const int *panOverviewList, int nListBands,
                           const int *panBandList, GDALProgressFunc pfnProgress,
                           void *pProgressData,
                           CSLConstList papszOptions) override;

    static void WriteBox(VSILFILE *fp, GDALJP2Box *poBox);
    static void WriteGDALMetadataBox(VSILFILE *fp, GDALDataset *poSrcDS,
                                     char **papszOptions);
    static void WriteXMLBoxes(VSILFILE *fp, GDALDataset *poSrcDS,
                              char **papszOptions);
    static void WriteXMPBox(VSILFILE *fp, GDALDataset *poSrcDS,
                            char **papszOptions);
    static void WriteIPRBox(VSILFILE *fp, GDALDataset *poSrcDS,
                            char **papszOptions);

    CPLErr ReadBlock(int nBand, VSILFILE *fp, int nBlockXOff, int nBlockYOff,
                     void *pImage, int nBandCount, int *panBandMap);

    int PreloadBlocks(JP2OpenJPEGRasterBand *poBand, int nXOff, int nYOff,
                      int nXSize, int nYSize, int nBandCount, int *panBandMap);

    static void JP2OpenJPEGReadBlockInThread(void *userdata);
};

/************************************************************************/
/* ==================================================================== */
/*                         JP2OpenJPEGRasterBand                        */
/* ==================================================================== */
/************************************************************************/

class JP2OpenJPEGRasterBand final : public GDALPamRasterBand
{
    friend class JP2OpenJPEGDataset;
    int bPromoteTo8Bit;
    GDALColorTable *poCT;

  public:
    JP2OpenJPEGRasterBand(JP2OpenJPEGDataset *poDS, int nBand,
                          GDALDataType eDataType, int nBits, int bPromoteTo8Bit,
                          int nBlockXSize, int nBlockYSize);
    virtual ~JP2OpenJPEGRasterBand();

    virtual CPLErr IReadBlock(int, int, void *) override;
    virtual CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
                             int nXSize, int nYSize, void *pData, int nBufXSize,
                             int nBufYSize, GDALDataType eBufType,
                             GSpacing nPixelSpace, GSpacing nLineSpace,
                             GDALRasterIOExtraArg *psExtraArg) override;

    virtual GDALColorInterp GetColorInterpretation() override;
    virtual GDALColorTable *GetColorTable() override
    {
        return poCT;
    }

    virtual int GetOverviewCount() override;
    virtual GDALRasterBand *GetOverview(int iOvrLevel) override;

    virtual int HasArbitraryOverviews() override
    {
        return poCT == nullptr;
    }
};

/************************************************************************/
/*                        JP2OpenJPEGRasterBand()                       */
/************************************************************************/

JP2OpenJPEGRasterBand::JP2OpenJPEGRasterBand(
    JP2OpenJPEGDataset *poDSIn, int nBandIn, GDALDataType eDataTypeIn,
    int nBits, int bPromoteTo8BitIn, int nBlockXSizeIn, int nBlockYSizeIn)

{
    this->eDataType = eDataTypeIn;
    this->nBlockXSize = nBlockXSizeIn;
    this->nBlockYSize = nBlockYSizeIn;
    this->bPromoteTo8Bit = bPromoteTo8BitIn;
    poCT = nullptr;

    if ((nBits % 8) != 0)
        GDALRasterBand::SetMetadataItem(
            "NBITS", CPLString().Printf("%d", nBits), "IMAGE_STRUCTURE");
    GDALRasterBand::SetMetadataItem("COMPRESSION", "JPEG2000",
                                    "IMAGE_STRUCTURE");
    this->poDS = poDSIn;
    this->nBand = nBandIn;
}

/************************************************************************/
/*                      ~JP2OpenJPEGRasterBand()                        */
/************************************************************************/

JP2OpenJPEGRasterBand::~JP2OpenJPEGRasterBand()
{
    delete poCT;
}

/************************************************************************/
/*                            CLAMP_0_255()                             */
/************************************************************************/

static CPL_INLINE GByte CLAMP_0_255(int val)
{
    if (val < 0)
        return 0;
    else if (val > 255)
        return 255;
    else
        return (GByte)val;
}

/************************************************************************/
/*                             IReadBlock()                             */
/************************************************************************/

CPLErr JP2OpenJPEGRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
                                         void *pImage)
{
    JP2OpenJPEGDataset *poGDS = (JP2OpenJPEGDataset *)poDS;

#ifdef DEBUG_VERBOSE
    int nXOff = nBlockXOff * nBlockXSize;
    int nYOff = nBlockYOff * nBlockYSize;
    int nXSize = std::min(nBlockXSize, nRasterXSize - nXOff);
    int nYSize = std::min(nBlockYSize, nRasterYSize - nYOff);
    if (poGDS->iLevel == 0)
    {
        CPLDebug("OPENJPEG", "ds.GetRasterBand(%d).ReadRaster(%d,%d,%d,%d)",
                 nBand, nXOff, nYOff, nXSize, nYSize);
    }
    else
    {
        CPLDebug("OPENJPEG",
                 "ds.GetRasterBand(%d).GetOverview(%d).ReadRaster(%d,%d,%d,%d)",
                 nBand, poGDS->iLevel - 1, nXOff, nYOff, nXSize, nYSize);
    }
#endif

    if (poGDS->bEnoughMemoryToLoadOtherBands)
        return poGDS->ReadBlock(nBand, poGDS->fp, nBlockXOff, nBlockYOff,
                                pImage, poGDS->nBands, nullptr);
    else
        return poGDS->ReadBlock(nBand, poGDS->fp, nBlockXOff, nBlockYOff,
                                pImage, 1, &nBand);
}

/************************************************************************/
/*                             IRasterIO()                              */
/************************************************************************/

CPLErr JP2OpenJPEGRasterBand::IRasterIO(
    GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
    void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
    GSpacing nPixelSpace, GSpacing nLineSpace, GDALRasterIOExtraArg *psExtraArg)
{
    JP2OpenJPEGDataset *poGDS = (JP2OpenJPEGDataset *)poDS;

    if (eRWFlag != GF_Read)
        return CE_Failure;

    /* ==================================================================== */
    /*      Do we have overviews that would be appropriate to satisfy       */
    /*      this request?                                                   */
    /* ==================================================================== */
    if ((nBufXSize < nXSize || nBufYSize < nYSize) && GetOverviewCount() > 0)
    {
        int bTried;
        CPLErr eErr = TryOverviewRasterIO(
            eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
            eBufType, nPixelSpace, nLineSpace, psExtraArg, &bTried);
        if (bTried)
            return eErr;
    }

    int nRet =
        poGDS->PreloadBlocks(this, nXOff, nYOff, nXSize, nYSize, 0, nullptr);
    if (nRet < 0)
        return CE_Failure;
    poGDS->bEnoughMemoryToLoadOtherBands = nRet;

    CPLErr eErr = GDALPamRasterBand::IRasterIO(
        eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
        eBufType, nPixelSpace, nLineSpace, psExtraArg);

    // cppcheck-suppress redundantAssignment
    poGDS->bEnoughMemoryToLoadOtherBands = TRUE;
    return eErr;
}

/************************************************************************/
/*                            GetNumThreads()                           */
/************************************************************************/

int JP2OpenJPEGDataset::GetNumThreads()
{
    if (nThreads >= 1)
        return nThreads;

    const char *pszThreads = CPLGetConfigOption("GDAL_NUM_THREADS", "ALL_CPUS");
    if (EQUAL(pszThreads, "ALL_CPUS"))
        nThreads = CPLGetNumCPUs();
    else
        nThreads = atoi(pszThreads);
    if (nThreads > 128)
        nThreads = 128;
    if (nThreads <= 0)
        nThreads = 1;
    return nThreads;
}

/************************************************************************/
/*                   JP2OpenJPEGReadBlockInThread()                     */
/************************************************************************/

class JobStruct
{
  public:
    JP2OpenJPEGDataset *poGDS;
    int nBand;
    std::vector<std::pair<int, int>> oPairs;
    volatile int nCurPair;
    int nBandCount;
    int *panBandMap;
    volatile bool bSuccess;
};

void JP2OpenJPEGDataset::JP2OpenJPEGReadBlockInThread(void *userdata)
{
    int nPair;
    JobStruct *poJob = (JobStruct *)userdata;

    JP2OpenJPEGDataset *poGDS = poJob->poGDS;
    int nBand = poJob->nBand;
    int nPairs = (int)poJob->oPairs.size();
    int nBandCount = poJob->nBandCount;
    int *panBandMap = poJob->panBandMap;
    VSILFILE *fp = VSIFOpenL(poGDS->m_osFilename.c_str(), "rb");
    if (fp == nullptr)
    {
        CPLDebug("OPENJPEG", "Cannot open %s", poGDS->m_osFilename.c_str());
        poJob->bSuccess = false;
        // VSIFree(pDummy);
        return;
    }

    while ((nPair = CPLAtomicInc(&(poJob->nCurPair))) < nPairs &&
           poJob->bSuccess)
    {
        int nBlockXOff = poJob->oPairs[nPair].first;
        int nBlockYOff = poJob->oPairs[nPair].second;
        poGDS->AcquireMutex();
        GDALRasterBlock *poBlock =
            poGDS->GetRasterBand(nBand)->GetLockedBlockRef(nBlockXOff,
                                                           nBlockYOff, TRUE);
        poGDS->ReleaseMutex();
        if (poBlock == nullptr)
        {
            poJob->bSuccess = false;
            break;
        }

        void *pDstBuffer = poBlock->GetDataRef();
        if (poGDS->ReadBlock(nBand, fp, nBlockXOff, nBlockYOff, pDstBuffer,
                             nBandCount, panBandMap) != CE_None)
        {
            poJob->bSuccess = false;
        }

        poBlock->DropLock();
    }

    VSIFCloseL(fp);
    // VSIFree(pDummy);
}

/************************************************************************/
/*                           PreloadBlocks()                            */
/************************************************************************/

int JP2OpenJPEGDataset::PreloadBlocks(JP2OpenJPEGRasterBand *poBand, int nXOff,
                                      int nYOff, int nXSize, int nYSize,
                                      int nBandCount, int *panBandMap)
{
    int bRet = TRUE;
    int nXStart = nXOff / poBand->nBlockXSize;
    int nXEnd = (nXOff + nXSize - 1) / poBand->nBlockXSize;
    int nYStart = nYOff / poBand->nBlockYSize;
    int nYEnd = (nYOff + nYSize - 1) / poBand->nBlockYSize;
    GIntBig nReqMem = (GIntBig)(nXEnd - nXStart + 1) * (nYEnd - nYStart + 1) *
                      poBand->nBlockXSize * poBand->nBlockYSize *
                      (GDALGetDataTypeSize(poBand->eDataType) / 8);

    int nMaxThreads = GetNumThreads();
    if (!bUseSetDecodeArea && nMaxThreads > 1)
    {
        if (nReqMem > GDALGetCacheMax64() / (nBandCount == 0 ? 1 : nBandCount))
            return FALSE;

        JobStruct oJob;
        m_nBlocksToLoad = 0;
        try
        {
            for (int nBlockXOff = nXStart; nBlockXOff <= nXEnd; ++nBlockXOff)
            {
                for (int nBlockYOff = nYStart; nBlockYOff <= nYEnd;
                     ++nBlockYOff)
                {
                    GDALRasterBlock *poBlock =
                        poBand->TryGetLockedBlockRef(nBlockXOff, nBlockYOff);
                    if (poBlock != nullptr)
                    {
                        poBlock->DropLock();
                        continue;
                    }
                    oJob.oPairs.push_back(
                        std::pair<int, int>(nBlockXOff, nBlockYOff));
                    m_nBlocksToLoad++;
                }
            }
        }
        catch (const std::bad_alloc &)
        {
            CPLError(CE_Failure, CPLE_OutOfMemory, "Out of memory error");
            m_nBlocksToLoad = 0;
            return -1;
        }

        if (m_nBlocksToLoad > 1)
        {
            const int l_nThreads = std::min(m_nBlocksToLoad, nMaxThreads);
            CPLJoinableThread **pahThreads =
                (CPLJoinableThread **)VSI_CALLOC_VERBOSE(
                    sizeof(CPLJoinableThread *), l_nThreads);
            if (pahThreads == nullptr)
            {
                m_nBlocksToLoad = 0;
                return -1;
            }
            int i;

            CPLDebug("OPENJPEG", "%d blocks to load (%d threads)",
                     m_nBlocksToLoad, l_nThreads);

            oJob.poGDS = this;
            oJob.nBand = poBand->GetBand();
            oJob.nCurPair = -1;
            if (nBandCount > 0)
            {
                oJob.nBandCount = nBandCount;
                oJob.panBandMap = panBandMap;
            }
            else
            {
                if (nReqMem <= GDALGetCacheMax64() / nBands)
                {
                    oJob.nBandCount = nBands;
                    oJob.panBandMap = nullptr;
                }
                else
                {
                    bRet = FALSE;
                    oJob.nBandCount = 1;
                    oJob.panBandMap = &oJob.nBand;
                }
            }
            oJob.bSuccess = true;

            /* Flushes all dirty blocks from cache to disk to avoid them */
            /* to be flushed randomly, and simultaneously, from our worker
             * threads, */
            /* which might cause races in the output driver. */
            /* This is a workaround to a design defect of the block cache */
            GDALRasterBlock::FlushDirtyBlocks();

            for (i = 0; i < l_nThreads; i++)
            {
                pahThreads[i] = CPLCreateJoinableThread(
                    JP2OpenJPEGReadBlockInThread, &oJob);
                if (pahThreads[i] == nullptr)
                    oJob.bSuccess = false;
            }
            TemporarilyDropReadWriteLock();
            for (i = 0; i < l_nThreads; i++)
                CPLJoinThread(pahThreads[i]);
            ReacquireReadWriteLock();
            CPLFree(pahThreads);
            if (!oJob.bSuccess)
            {
                m_nBlocksToLoad = 0;
                return -1;
            }
            m_nBlocksToLoad = 0;
        }
    }

    return bRet;
}

/************************************************************************/
/*                             IRasterIO()                              */
/************************************************************************/

CPLErr JP2OpenJPEGDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
                                     int nXSize, int nYSize, void *pData,
                                     int nBufXSize, int nBufYSize,
                                     GDALDataType eBufType, int nBandCount,
                                     int *panBandMap, GSpacing nPixelSpace,
                                     GSpacing nLineSpace, GSpacing nBandSpace,
                                     GDALRasterIOExtraArg *psExtraArg)
{
    if (eRWFlag != GF_Read)
        return CE_Failure;

    if (nBandCount < 1)
        return CE_Failure;

    JP2OpenJPEGRasterBand *poBand =
        (JP2OpenJPEGRasterBand *)GetRasterBand(panBandMap[0]);

    /* ==================================================================== */
    /*      Do we have overviews that would be appropriate to satisfy       */
    /*      this request?                                                   */
    /* ==================================================================== */

    if ((nBufXSize < nXSize || nBufYSize < nYSize) &&
        poBand->GetOverviewCount() > 0)
    {
        int bTried;
        CPLErr eErr = TryOverviewRasterIO(
            eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
            eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace,
            nBandSpace, psExtraArg, &bTried);
        if (bTried)
            return eErr;
    }

    bEnoughMemoryToLoadOtherBands = PreloadBlocks(
        poBand, nXOff, nYOff, nXSize, nYSize, nBandCount, panBandMap);

    CPLErr eErr = GDALPamDataset::IRasterIO(
        eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
        eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace, nBandSpace,
        psExtraArg);

    bEnoughMemoryToLoadOtherBands = TRUE;
    return eErr;
}

/************************************************************************/
/*                          IBuildOverviews()                           */
/************************************************************************/

CPLErr JP2OpenJPEGDataset::IBuildOverviews(
    const char *pszResampling, int nOverviews, const int *panOverviewList,
    int nListBands, const int *panBandList, GDALProgressFunc pfnProgress,
    void *pProgressData, CSLConstList papszOptions)

{
    // In order for building external overviews to work properly, we
    // discard any concept of internal overviews when the user
    // first requests to build external overviews.
    for (int i = 0; i < nOverviewCount; i++)
    {
        delete papoOverviewDS[i];
    }
    CPLFree(papoOverviewDS);
    papoOverviewDS = nullptr;
    nOverviewCount = 0;

    return GDALPamDataset::IBuildOverviews(
        pszResampling, nOverviews, panOverviewList, nListBands, panBandList,
        pfnProgress, pProgressData, papszOptions);
}

/************************************************************************/
/*                    JP2OpenJPEGCreateReadStream()                     */
/************************************************************************/

static opj_stream_t *
JP2OpenJPEGCreateReadStream(JP2OpenJPEGFile *psJP2OpenJPEGFile,
                            vsi_l_offset nSize)
{
    opj_stream_t *pStream = opj_stream_create(
        1024, TRUE);  // Default 1MB is way too big for some datasets
    if (pStream == nullptr)
        return nullptr;

    VSIFSeekL(psJP2OpenJPEGFile->fp, psJP2OpenJPEGFile->nBaseOffset, SEEK_SET);
    opj_stream_set_user_data_length(pStream, nSize);

    opj_stream_set_read_function(pStream, JP2OpenJPEGDataset_Read);
    opj_stream_set_seek_function(pStream, JP2OpenJPEGDataset_Seek);
    opj_stream_set_skip_function(pStream, JP2OpenJPEGDataset_Skip);
    opj_stream_set_user_data(pStream, psJP2OpenJPEGFile, nullptr);

    return pStream;
}

/************************************************************************/
/*                             ReadBlock()                              */
/************************************************************************/

CPLErr JP2OpenJPEGDataset::ReadBlock(int nBand, VSILFILE *fpIn, int nBlockXOff,
                                     int nBlockYOff, void *pImage,
                                     int nBandCount, int *panBandMap)
{
    CPLErr eErr = CE_None;
    opj_codec_t *pCodec = nullptr;
    opj_stream_t *pStream = nullptr;
    opj_image_t *psImage = nullptr;
    JP2OpenJPEGFile sJP2OpenJPEGFile;  // keep it in this scope

    JP2OpenJPEGRasterBand *poBand =
        (JP2OpenJPEGRasterBand *)GetRasterBand(nBand);
    int nBlockXSize = poBand->nBlockXSize;
    int nBlockYSize = poBand->nBlockYSize;
    GDALDataType eDataType = poBand->eDataType;

    int nDataTypeSize = (GDALGetDataTypeSize(eDataType) / 8);

    int nTileNumber = nBlockXOff + nBlockYOff * poBand->nBlocksPerRow;
    const int nWidthToRead =
        std::min(nBlockXSize, nRasterXSize - nBlockXOff * nBlockXSize);
    const int nHeightToRead =
        std::min(nBlockYSize, nRasterYSize - nBlockYOff * nBlockYSize);

#if IS_OPENJPEG_OR_LATER(2, 3, 0)
    if (m_ppCodec && CPLTestBool(CPLGetConfigOption(
                         "USE_OPENJPEG_SINGLE_TILE_OPTIM", "YES")))
    {
        if ((*m_pnLastLevel == -1 || *m_pnLastLevel == iLevel) &&
            *m_ppCodec != nullptr && *m_ppStream != nullptr &&
            *m_ppsImage != nullptr)
        {
            pCodec = *m_ppCodec;
            pStream = *m_ppStream;
            psImage = *m_ppsImage;
        }
        else
        {
            // For some reason, we need to "reboot" all the machinery if
            // changing of overview level. Should be fixed in openjpeg
            if (*m_ppCodec)
                opj_destroy_codec(*m_ppCodec);
            if (*m_ppStream)
                opj_stream_destroy(*m_ppStream);
            if (*m_ppsImage)
                opj_image_destroy(*m_ppsImage);
            *m_ppCodec = nullptr;
            *m_ppStream = nullptr;
            *m_ppsImage = nullptr;
        }
    }
    *m_pnLastLevel = iLevel;

    if (pCodec == nullptr)
#endif
    {
        pCodec = opj_create_decompress(OPJ_CODEC_J2K);
        if (pCodec == nullptr)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "opj_create_decompress() failed");
            eErr = CE_Failure;
            goto end;
        }

        opj_set_info_handler(pCodec, JP2OpenJPEGDataset_InfoCallback, nullptr);
        opj_set_warning_handler(pCodec, JP2OpenJPEGDataset_WarningCallback,
                                nullptr);
        opj_set_error_handler(pCodec, JP2OpenJPEGDataset_ErrorCallback,
                              nullptr);

        opj_dparameters_t parameters;
        opj_set_default_decoder_parameters(&parameters);

        if (!opj_setup_decoder(pCodec, &parameters))
        {
            CPLError(CE_Failure, CPLE_AppDefined, "opj_setup_decoder() failed");
            eErr = CE_Failure;
            goto end;
        }

#if IS_OPENJPEG_OR_LATER(2, 5, 0)
        if (!m_bStrict)
        {
            opj_decoder_set_strict_mode(pCodec, false);
        }
#endif

#if IS_OPENJPEG_OR_LATER(2, 3, 0)
        if (m_psJP2OpenJPEGFile)
        {
            pStream = JP2OpenJPEGCreateReadStream(m_psJP2OpenJPEGFile,
                                                  nCodeStreamLength);
        }
        else
#endif
        {
            sJP2OpenJPEGFile.fp = fpIn;
            sJP2OpenJPEGFile.nBaseOffset = nCodeStreamStart;
            pStream = JP2OpenJPEGCreateReadStream(&sJP2OpenJPEGFile,
                                                  nCodeStreamLength);
        }
        if (pStream == nullptr)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "JP2OpenJPEGCreateReadStream() failed");
            eErr = CE_Failure;
            goto end;
        }

#if IS_OPENJPEG_OR_LATER(2, 2, 0)
        if (getenv("OPJ_NUM_THREADS") == nullptr)
        {
            if (m_nBlocksToLoad <= 1)
                opj_codec_set_threads(pCodec, GetNumThreads());
            else
                opj_codec_set_threads(pCodec,
                                      GetNumThreads() / m_nBlocksToLoad);
        }
#endif

        if (!opj_read_header(pStream, pCodec, &psImage))
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "opj_read_header() failed (psImage=%p)", psImage);
#if IS_OPENJPEG_OR_LATER(2, 2, 0)
            // Hopefully the situation is better on openjpeg 2.2 regarding
            // cleanup
            eErr = CE_Failure;
            goto end;
#else
            // We may leak objects, but the cleanup of openjpeg can cause
            // double frees sometimes...
            return CE_Failure;
#endif
        }
    }

    if (!opj_set_decoded_resolution_factor(pCodec, iLevel))
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "opj_set_decoded_resolution_factor() failed");
        eErr = CE_Failure;
        goto end;
    }

    if (bUseSetDecodeArea)
    {
        /* We need to explicitly set the resolution factor on the image */
        /* otherwise opj_set_decode_area() will assume we decode at full */
        /* resolution. */
        /* If using parameters.cp_reduce instead of
         * opj_set_decoded_resolution_factor() */
        /* we wouldn't need to do that, as opj_read_header() would automatically
         */
        /* assign the comps[].factor to the appropriate value */
        for (unsigned int iBand = 0; iBand < psImage->numcomps; iBand++)
        {
            psImage->comps[iBand].factor = iLevel;
        }
        /* The decode area must be expressed in grid reference, ie at full*/
        /* scale */
        if (!opj_set_decode_area(
                pCodec, psImage,
                m_nX0 + static_cast<int>(
                            static_cast<GIntBig>(nBlockXOff * nBlockXSize) *
                            nParentXSize / nRasterXSize),
                m_nY0 + static_cast<int>(
                            static_cast<GIntBig>(nBlockYOff * nBlockYSize) *
                            nParentYSize / nRasterYSize),
                m_nX0 + static_cast<int>(
                            static_cast<GIntBig>(nBlockXOff * nBlockXSize +
                                                 nWidthToRead) *
                            nParentXSize / nRasterXSize),
                m_nY0 + static_cast<int>(
                            static_cast<GIntBig>(nBlockYOff * nBlockYSize +
                                                 nHeightToRead) *
                            nParentYSize / nRasterYSize)))
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "opj_set_decode_area() failed");
            eErr = CE_Failure;
            goto end;
        }
        if (!opj_decode(pCodec, pStream, psImage))
        {
            CPLError(CE_Failure, CPLE_AppDefined, "opj_decode() failed");
            eErr = CE_Failure;
            goto end;
        }
    }
    else
    {
        if (!opj_get_decoded_tile(pCodec, pStream, psImage, nTileNumber))
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "opj_get_decoded_tile() failed");
            eErr = CE_Failure;
            goto end;
        }
    }

    for (unsigned int iBand = 0; iBand < psImage->numcomps; iBand++)
    {
        if (psImage->comps[iBand].data == nullptr)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "psImage->comps[%d].data == nullptr", iBand);
            eErr = CE_Failure;
            goto end;
        }
    }

    for (int xBand = 0; xBand < nBandCount; xBand++)
    {
        GDALRasterBlock *poBlock = nullptr;
        int iBand = (panBandMap) ? panBandMap[xBand] : xBand + 1;
        int bPromoteTo8Bit =
            ((JP2OpenJPEGRasterBand *)GetRasterBand(iBand))->bPromoteTo8Bit;

        void *pDstBuffer = nullptr;
        if (iBand == nBand)
            pDstBuffer = pImage;
        else
        {
            AcquireMutex();
            poBlock = ((JP2OpenJPEGRasterBand *)GetRasterBand(iBand))
                          ->TryGetLockedBlockRef(nBlockXOff, nBlockYOff);
            if (poBlock != nullptr)
            {
                ReleaseMutex();
                poBlock->DropLock();
                continue;
            }

            poBlock = GetRasterBand(iBand)->GetLockedBlockRef(nBlockXOff,
                                                              nBlockYOff, TRUE);
            ReleaseMutex();
            if (poBlock == nullptr)
            {
                continue;
            }

            pDstBuffer = poBlock->GetDataRef();
        }

        if (bIs420)
        {
            if ((int)psImage->comps[0].w < nWidthToRead ||
                (int)psImage->comps[0].h < nHeightToRead ||
                psImage->comps[1].w != (psImage->comps[0].w + 1) / 2 ||
                psImage->comps[1].h != (psImage->comps[0].h + 1) / 2 ||
                psImage->comps[2].w != (psImage->comps[0].w + 1) / 2 ||
                psImage->comps[2].h != (psImage->comps[0].h + 1) / 2 ||
                (nBands == 4 && ((int)psImage->comps[3].w < nWidthToRead ||
                                 (int)psImage->comps[3].h < nHeightToRead)))
            {
                CPLError(CE_Failure, CPLE_AssertionFailed,
                         "Assertion at line %d of %s failed", __LINE__,
                         __FILE__);
                if (poBlock != nullptr)
                    poBlock->DropLock();
                eErr = CE_Failure;
                goto end;
            }

            GByte *pDst = (GByte *)pDstBuffer;
            if (iBand == 4)
            {
                const OPJ_INT32 *pSrcA = psImage->comps[3].data;
                for (GPtrDiff_t j = 0; j < nHeightToRead; j++)
                {
                    memcpy(pDst + j * nBlockXSize,
                           pSrcA + j * psImage->comps[0].w, nWidthToRead);
                }
            }
            else
            {
                const OPJ_INT32 *pSrcY = psImage->comps[0].data;
                const OPJ_INT32 *pSrcCb = psImage->comps[1].data;
                const OPJ_INT32 *pSrcCr = psImage->comps[2].data;
                for (GPtrDiff_t j = 0; j < nHeightToRead; j++)
                {
                    for (int i = 0; i < nWidthToRead; i++)
                    {
                        int Y = pSrcY[j * psImage->comps[0].w + i];
                        int Cb =
                            pSrcCb[(j / 2) * psImage->comps[1].w + (i / 2)];
                        int Cr =
                            pSrcCr[(j / 2) * psImage->comps[2].w + (i / 2)];
                        if (iBand == 1)
                            pDst[j * nBlockXSize + i] =
                                CLAMP_0_255((int)(Y + 1.402 * (Cr - 128)));
                        else if (iBand == 2)
                            pDst[j * nBlockXSize + i] =
                                CLAMP_0_255((int)(Y - 0.34414 * (Cb - 128) -
                                                  0.71414 * (Cr - 128)));
                        else if (iBand == 3)
                            pDst[j * nBlockXSize + i] =
                                CLAMP_0_255((int)(Y + 1.772 * (Cb - 128)));
                    }
                }
            }

            if (bPromoteTo8Bit)
            {
                for (GPtrDiff_t j = 0; j < nHeightToRead; j++)
                {
                    for (int i = 0; i < nWidthToRead; i++)
                    {
                        pDst[j * nBlockXSize + i] *= 255;
                    }
                }
            }
        }
        else
        {
            if ((int)psImage->comps[iBand - 1].w < nWidthToRead ||
                (int)psImage->comps[iBand - 1].h < nHeightToRead)
            {
                CPLError(CE_Failure, CPLE_AssertionFailed,
                         "Assertion at line %d of %s failed", __LINE__,
                         __FILE__);
                if (poBlock != nullptr)
                    poBlock->DropLock();
                eErr = CE_Failure;
                goto end;
            }

            if (bPromoteTo8Bit)
            {
                for (GPtrDiff_t j = 0; j < nHeightToRead; j++)
                {
                    for (int i = 0; i < nWidthToRead; i++)
                    {
                        psImage->comps[iBand - 1]
                            .data[j * psImage->comps[iBand - 1].w + i] *= 255;
                    }
                }
            }

            if ((int)psImage->comps[iBand - 1].w == nBlockXSize &&
                (int)psImage->comps[iBand - 1].h == nBlockYSize)
            {
                GDALCopyWords64(psImage->comps[iBand - 1].data, GDT_Int32, 4,
                                pDstBuffer, eDataType, nDataTypeSize,
                                static_cast<GPtrDiff_t>(nBlockXSize) *
                                    nBlockYSize);
            }
            else
            {
                for (GPtrDiff_t j = 0; j < nHeightToRead; j++)
                {
                    GDALCopyWords(psImage->comps[iBand - 1].data +
                                      j * psImage->comps[iBand - 1].w,
                                  GDT_Int32, 4,
                                  (GByte *)pDstBuffer +
                                      j * nBlockXSize * nDataTypeSize,
                                  eDataType, nDataTypeSize, nWidthToRead);
                }
            }
        }

        if (poBlock != nullptr)
            poBlock->DropLock();
    }

end:
#if IS_OPENJPEG_OR_LATER(2, 3, 0)
    if (m_ppCodec != nullptr && CPLTestBool(CPLGetConfigOption(
                                    "USE_OPENJPEG_SINGLE_TILE_OPTIM", "YES")))
    {
        *m_ppCodec = pCodec;
        *m_ppStream = pStream;
        *m_ppsImage = psImage;
    }
    else
#endif
    {
        if (pCodec && pStream)
            opj_end_decompress(pCodec, pStream);
        if (pStream)
            opj_stream_destroy(pStream);
        if (pCodec)
            opj_destroy_codec(pCodec);
        if (psImage)
            opj_image_destroy(psImage);
    }

    return eErr;
}

/************************************************************************/
/*                         GetOverviewCount()                           */
/************************************************************************/

int JP2OpenJPEGRasterBand::GetOverviewCount()
{
    JP2OpenJPEGDataset *poGDS = cpl::down_cast<JP2OpenJPEGDataset *>(poDS);
    if (!poGDS->AreOverviewsEnabled())
        return 0;

    if (GDALPamRasterBand::GetOverviewCount() > 0)
        return GDALPamRasterBand::GetOverviewCount();

    return poGDS->nOverviewCount;
}

/************************************************************************/
/*                            GetOverview()                             */
/************************************************************************/

GDALRasterBand *JP2OpenJPEGRasterBand::GetOverview(int iOvrLevel)
{
    if (GDALPamRasterBand::GetOverviewCount() > 0)
        return GDALPamRasterBand::GetOverview(iOvrLevel);

    JP2OpenJPEGDataset *poGDS = (JP2OpenJPEGDataset *)poDS;
    if (iOvrLevel < 0 || iOvrLevel >= poGDS->nOverviewCount)
        return nullptr;

    return poGDS->papoOverviewDS[iOvrLevel]->GetRasterBand(nBand);
}

/************************************************************************/
/*                       GetColorInterpretation()                       */
/************************************************************************/

GDALColorInterp JP2OpenJPEGRasterBand::GetColorInterpretation()
{
    JP2OpenJPEGDataset *poGDS = (JP2OpenJPEGDataset *)poDS;

    if (poCT)
        return GCI_PaletteIndex;

    if (nBand == poGDS->nAlphaIndex + 1)
        return GCI_AlphaBand;

    if (poGDS->nBands <= 2 && poGDS->eColorSpace == OPJ_CLRSPC_GRAY)
        return GCI_GrayIndex;
    else if (poGDS->eColorSpace == OPJ_CLRSPC_SRGB ||
             poGDS->eColorSpace == OPJ_CLRSPC_SYCC)
    {
        if (nBand == poGDS->nRedIndex + 1)
            return GCI_RedBand;
        if (nBand == poGDS->nGreenIndex + 1)
            return GCI_GreenBand;
        if (nBand == poGDS->nBlueIndex + 1)
            return GCI_BlueBand;
    }

    return GCI_Undefined;
}

/************************************************************************/
/* ==================================================================== */
/*                           JP2OpenJPEGDataset                         */
/* ==================================================================== */
/************************************************************************/

/************************************************************************/
/*                        JP2OpenJPEGDataset()                          */
/************************************************************************/

JP2OpenJPEGDataset::JP2OpenJPEGDataset()
{
}

/************************************************************************/
/*                         ~JP2OpenJPEGDataset()                        */
/************************************************************************/

JP2OpenJPEGDataset::~JP2OpenJPEGDataset()

{
    FlushCache(true);

#if IS_OPENJPEG_OR_LATER(2, 3, 0)
    if (iLevel == 0)
    {
        if (m_ppCodec)
            opj_destroy_codec(*m_ppCodec);
        delete m_ppCodec;
        if (m_ppStream)
            opj_stream_destroy(*m_ppStream);
        delete m_ppStream;
        if (m_ppsImage)
            opj_image_destroy(*m_ppsImage);
        delete m_ppsImage;
        CPLFree(m_psJP2OpenJPEGFile);
        delete m_pnLastLevel;
    }
#endif

    if (iLevel == 0 && fp != nullptr)
    {
        if (bRewrite)
        {
            GDALJP2Box oBox(fp);
            vsi_l_offset nOffsetJP2C = 0;
            vsi_l_offset nLengthJP2C = 0;
            vsi_l_offset nOffsetXML = 0;
            vsi_l_offset nOffsetASOC = 0;
            vsi_l_offset nOffsetUUID = 0;
            vsi_l_offset nOffsetIHDR = 0;
            vsi_l_offset nLengthIHDR = 0;
            int bMSIBox = FALSE;
            int bGMLData = FALSE;
            int bUnsupportedConfiguration = FALSE;
            if (oBox.ReadFirst())
            {
                while (strlen(oBox.GetType()) > 0)
                {
                    if (EQUAL(oBox.GetType(), "jp2c"))
                    {
                        if (nOffsetJP2C == 0)
                        {
                            nOffsetJP2C = VSIFTellL(fp);
                            nLengthJP2C = oBox.GetDataLength();
                        }
                        else
                            bUnsupportedConfiguration = TRUE;
                    }
                    else if (EQUAL(oBox.GetType(), "jp2h"))
                    {
                        GDALJP2Box oSubBox(fp);
                        if (oSubBox.ReadFirstChild(&oBox) &&
                            EQUAL(oSubBox.GetType(), "ihdr"))
                        {
                            nOffsetIHDR = VSIFTellL(fp);
                            nLengthIHDR = oSubBox.GetDataLength();
                        }
                    }
                    else if (EQUAL(oBox.GetType(), "xml "))
                    {
                        if (nOffsetXML == 0)
                            nOffsetXML = VSIFTellL(fp);
                    }
                    else if (EQUAL(oBox.GetType(), "asoc"))
                    {
                        if (nOffsetASOC == 0)
                            nOffsetASOC = VSIFTellL(fp);

                        GDALJP2Box oSubBox(fp);
                        if (oSubBox.ReadFirstChild(&oBox) &&
                            EQUAL(oSubBox.GetType(), "lbl "))
                        {
                            char *pszLabel = (char *)oSubBox.ReadBoxData();
                            if (pszLabel != nullptr &&
                                EQUAL(pszLabel, "gml.data"))
                            {
                                bGMLData = TRUE;
                            }
                            else
                                bUnsupportedConfiguration = TRUE;
                            CPLFree(pszLabel);
                        }
                        else
                            bUnsupportedConfiguration = TRUE;
                    }
                    else if (EQUAL(oBox.GetType(), "uuid"))
                    {
                        if (nOffsetUUID == 0)
                            nOffsetUUID = VSIFTellL(fp);
                        if (GDALJP2Metadata::IsUUID_MSI(oBox.GetUUID()))
                            bMSIBox = TRUE;
                        else if (!GDALJP2Metadata::IsUUID_XMP(oBox.GetUUID()))
                            bUnsupportedConfiguration = TRUE;
                    }
                    else if (!EQUAL(oBox.GetType(), "jP  ") &&
                             !EQUAL(oBox.GetType(), "ftyp") &&
                             !EQUAL(oBox.GetType(), "rreq") &&
                             !EQUAL(oBox.GetType(), "jp2h") &&
                             !EQUAL(oBox.GetType(), "jp2i"))
                    {
                        bUnsupportedConfiguration = TRUE;
                    }

                    if (bUnsupportedConfiguration || !oBox.ReadNext())
                        break;
                }
            }

            const char *pszGMLJP2;
            int bGeoreferencingCompatOfGMLJP2 =
                (!m_oSRS.IsEmpty() && bGeoTransformValid && nGCPCount == 0);
            if (bGeoreferencingCompatOfGMLJP2 &&
                ((bHasGeoreferencingAtOpening && bGMLData) ||
                 (!bHasGeoreferencingAtOpening)))
                pszGMLJP2 = "GMLJP2=YES";
            else
                pszGMLJP2 = "GMLJP2=NO";

            const char *pszGeoJP2;
            int bGeoreferencingCompatOfGeoJP2 =
                (!m_oSRS.IsEmpty() || nGCPCount != 0 || bGeoTransformValid);
            if (bGeoreferencingCompatOfGeoJP2 &&
                ((bHasGeoreferencingAtOpening && bMSIBox) ||
                 (!bHasGeoreferencingAtOpening) || nGCPCount > 0))
                pszGeoJP2 = "GeoJP2=YES";
            else
                pszGeoJP2 = "GeoJP2=NO";

            /* Test that the length of the JP2C box is not 0 */
            int bJP2CBoxOKForRewriteInPlace = TRUE;
            if (nOffsetJP2C > 16 && !bUnsupportedConfiguration)
            {
                VSIFSeekL(fp, nOffsetJP2C - 8, SEEK_SET);
                GByte abyBuffer[8];
                VSIFReadL(abyBuffer, 1, 8, fp);
                if (STARTS_WITH_CI((const char *)abyBuffer + 4, "jp2c") &&
                    abyBuffer[0] == 0 && abyBuffer[1] == 0 &&
                    abyBuffer[2] == 0 && abyBuffer[3] == 0)
                {
                    if ((vsi_l_offset)(GUInt32)(nLengthJP2C + 8) ==
                        (nLengthJP2C + 8))
                    {
                        CPLDebug(
                            "OPENJPEG",
                            "Patching length of JP2C box with real length");
                        VSIFSeekL(fp, nOffsetJP2C - 8, SEEK_SET);
                        GUInt32 nLength = (GUInt32)nLengthJP2C + 8;
                        CPL_MSBPTR32(&nLength);
                        VSIFWriteL(&nLength, 1, 4, fp);
                    }
                    else
                        bJP2CBoxOKForRewriteInPlace = FALSE;
                }
            }

            if (nOffsetJP2C == 0 || bUnsupportedConfiguration)
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Cannot rewrite file due to unsupported JP2 box "
                         "configuration");
                VSIFCloseL(fp);
            }
            else if (bJP2CBoxOKForRewriteInPlace &&
                     (nOffsetXML == 0 || nOffsetXML > nOffsetJP2C) &&
                     (nOffsetASOC == 0 || nOffsetASOC > nOffsetJP2C) &&
                     (nOffsetUUID == 0 || nOffsetUUID > nOffsetJP2C))
            {
                CPLDebug("OPENJPEG", "Rewriting boxes after codestream");

                /* Update IPR flag */
                if (nLengthIHDR == 14)
                {
                    VSIFSeekL(fp, nOffsetIHDR + nLengthIHDR - 1, SEEK_SET);
                    GByte bIPR = GetMetadata("xml:IPR") != nullptr;
                    VSIFWriteL(&bIPR, 1, 1, fp);
                }

                VSIFSeekL(fp, nOffsetJP2C + nLengthJP2C, SEEK_SET);

                GDALJP2Metadata oJP2MD;
                if (GetGCPCount() > 0)
                {
                    oJP2MD.SetGCPs(GetGCPCount(), GetGCPs());
                    oJP2MD.SetSpatialRef(GetGCPSpatialRef());
                }
                else
                {
                    const OGRSpatialReference *poSRS = GetSpatialRef();
                    if (poSRS != nullptr)
                    {
                        oJP2MD.SetSpatialRef(poSRS);
                    }
                    if (bGeoTransformValid)
                    {
                        oJP2MD.SetGeoTransform(adfGeoTransform);
                    }
                }

                const char *pszAreaOrPoint =
                    GetMetadataItem(GDALMD_AREA_OR_POINT);
                oJP2MD.bPixelIsPoint = pszAreaOrPoint != nullptr &&
                                       EQUAL(pszAreaOrPoint, GDALMD_AOP_POINT);

                WriteIPRBox(fp, this, nullptr);

                if (bGeoreferencingCompatOfGMLJP2 &&
                    EQUAL(pszGMLJP2, "GMLJP2=YES"))
                {
                    GDALJP2Box *poBox =
                        oJP2MD.CreateGMLJP2(nRasterXSize, nRasterYSize);
                    WriteBox(fp, poBox);
                    delete poBox;
                }

                WriteXMLBoxes(fp, this, nullptr);
                WriteGDALMetadataBox(fp, this, nullptr);

                if (bGeoreferencingCompatOfGeoJP2 &&
                    EQUAL(pszGeoJP2, "GeoJP2=YES"))
                {
                    GDALJP2Box *poBox = oJP2MD.CreateJP2GeoTIFF();
                    WriteBox(fp, poBox);
                    delete poBox;
                }

                WriteXMPBox(fp, this, nullptr);

                VSIFTruncateL(fp, VSIFTellL(fp));

                VSIFCloseL(fp);
            }
            else
            {
                VSIFCloseL(fp);

                CPLDebug("OPENJPEG", "Rewriting whole file");

                const char *apszOptions[] = {"USE_SRC_CODESTREAM=YES",
                                             "CODEC=JP2",
                                             "WRITE_METADATA=YES",
                                             nullptr,
                                             nullptr,
                                             nullptr};
                apszOptions[3] = pszGMLJP2;
                apszOptions[4] = pszGeoJP2;
                CPLString osTmpFilename(CPLSPrintf("%s.tmp", GetDescription()));
                GDALDataset *poOutDS =
                    CreateCopy(osTmpFilename, this, FALSE, (char **)apszOptions,
                               GDALDummyProgress, nullptr);
                if (poOutDS)
                {
                    GDALClose(poOutDS);
                    VSIRename(osTmpFilename, GetDescription());
                }
                else
                    VSIUnlink(osTmpFilename);
                VSIUnlink(CPLSPrintf("%s.tmp.aux.xml", GetDescription()));
            }
        }
        else
            VSIFCloseL(fp);
    }

    JP2OpenJPEGDataset::CloseDependentDatasets();
}

/************************************************************************/
/*                      CloseDependentDatasets()                        */
/************************************************************************/

int JP2OpenJPEGDataset::CloseDependentDatasets()
{
    int bRet = GDALJP2AbstractDataset::CloseDependentDatasets();
    if (papoOverviewDS)
    {
        for (int i = 0; i < nOverviewCount; i++)
            delete papoOverviewDS[i];
        CPLFree(papoOverviewDS);
        papoOverviewDS = nullptr;
        bRet = TRUE;
    }
    return bRet;
}

/************************************************************************/
/*                           SetSpatialRef()                            */
/************************************************************************/

CPLErr JP2OpenJPEGDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
{
    if (eAccess == GA_Update)
    {
        bRewrite = TRUE;
        m_oSRS.Clear();
        if (poSRS)
            m_oSRS = *poSRS;
        return CE_None;
    }
    else
        return GDALJP2AbstractDataset::SetSpatialRef(poSRS);
}

/************************************************************************/
/*                           SetGeoTransform()                          */
/************************************************************************/

CPLErr JP2OpenJPEGDataset::SetGeoTransform(double *padfGeoTransform)
{
    if (eAccess == GA_Update)
    {
        bRewrite = TRUE;
        memcpy(adfGeoTransform, padfGeoTransform, 6 * sizeof(double));
        bGeoTransformValid =
            !(adfGeoTransform[0] == 0.0 && adfGeoTransform[1] == 1.0 &&
              adfGeoTransform[2] == 0.0 && adfGeoTransform[3] == 0.0 &&
              adfGeoTransform[4] == 0.0 && adfGeoTransform[5] == 1.0);
        return CE_None;
    }
    else
        return GDALJP2AbstractDataset::SetGeoTransform(padfGeoTransform);
}

/************************************************************************/
/*                           SetGCPs()                                  */
/************************************************************************/

CPLErr JP2OpenJPEGDataset::SetGCPs(int nGCPCountIn,
                                   const GDAL_GCP *pasGCPListIn,
                                   const OGRSpatialReference *poSRS)
{
    if (eAccess == GA_Update)
    {
        bRewrite = TRUE;
        if (nGCPCount > 0)
        {
            GDALDeinitGCPs(nGCPCount, pasGCPList);
            CPLFree(pasGCPList);
        }

        m_oSRS.Clear();
        if (poSRS)
            m_oSRS = *poSRS;

        nGCPCount = nGCPCountIn;
        pasGCPList = GDALDuplicateGCPs(nGCPCount, pasGCPListIn);

        return CE_None;
    }
    else
        return GDALJP2AbstractDataset::SetGCPs(nGCPCountIn, pasGCPListIn,
                                               poSRS);
}

/************************************************************************/
/*                            SetMetadata()                             */
/************************************************************************/

CPLErr JP2OpenJPEGDataset::SetMetadata(char **papszMetadata,
                                       const char *pszDomain)
{
    if (eAccess == GA_Update)
    {
        bRewrite = TRUE;
        if (pszDomain == nullptr || EQUAL(pszDomain, ""))
        {
            CSLDestroy(m_papszMainMD);
            m_papszMainMD = CSLDuplicate(papszMetadata);
        }
        return GDALDataset::SetMetadata(papszMetadata, pszDomain);
    }
    return GDALJP2AbstractDataset::SetMetadata(papszMetadata, pszDomain);
}

/************************************************************************/
/*                            SetMetadata()                             */
/************************************************************************/

CPLErr JP2OpenJPEGDataset::SetMetadataItem(const char *pszName,
                                           const char *pszValue,
                                           const char *pszDomain)
{
    if (eAccess == GA_Update)
    {
        bRewrite = TRUE;
        if (pszDomain == nullptr || EQUAL(pszDomain, ""))
        {
            m_papszMainMD = CSLSetNameValue(GetMetadata(), pszName, pszValue);
        }
        return GDALDataset::SetMetadataItem(pszName, pszValue, pszDomain);
    }
    return GDALJP2AbstractDataset::SetMetadataItem(pszName, pszValue,
                                                   pszDomain);
}

/************************************************************************/
/*                            Identify()                                */
/************************************************************************/

static const unsigned char jpc_header[] = {0xff, 0x4f, 0xff,
                                           0x51};  // SOC + RSIZ markers
static const unsigned char jp2_box_jp[] = {0x6a, 0x50, 0x20, 0x20}; /* 'jP  ' */

int JP2OpenJPEGDataset::Identify(GDALOpenInfo *poOpenInfo)

{
    if (poOpenInfo->nHeaderBytes >= 16 &&
        (memcmp(poOpenInfo->pabyHeader, jpc_header, sizeof(jpc_header)) == 0 ||
         memcmp(poOpenInfo->pabyHeader + 4, jp2_box_jp, sizeof(jp2_box_jp)) ==
             0))
        return TRUE;

    else
        return FALSE;
}

/************************************************************************/
/*                        JP2OpenJPEGFindCodeStream()                   */
/************************************************************************/

static vsi_l_offset JP2OpenJPEGFindCodeStream(VSILFILE *fp,
                                              vsi_l_offset *pnLength)
{
    vsi_l_offset nCodeStreamStart = 0;
    vsi_l_offset nCodeStreamLength = 0;

    VSIFSeekL(fp, 0, SEEK_SET);
    GByte abyHeader[16];
    VSIFReadL(abyHeader, 1, 16, fp);

    if (memcmp(abyHeader, jpc_header, sizeof(jpc_header)) == 0)
    {
        VSIFSeekL(fp, 0, SEEK_END);
        nCodeStreamLength = VSIFTellL(fp);
    }
    else if (memcmp(abyHeader + 4, jp2_box_jp, sizeof(jp2_box_jp)) == 0)
    {
        /* Find offset of first jp2c box */
        GDALJP2Box oBox(fp);
        if (oBox.ReadFirst())
        {
            while (strlen(oBox.GetType()) > 0)
            {
                if (EQUAL(oBox.GetType(), "jp2c"))
                {
                    nCodeStreamStart = VSIFTellL(fp);
                    nCodeStreamLength = oBox.GetDataLength();
                    break;
                }

                if (!oBox.ReadNext())
                    break;
            }
        }
    }
    *pnLength = nCodeStreamLength;
    return nCodeStreamStart;
}

/************************************************************************/
/*                                Open()                                */
/************************************************************************/

GDALDataset *JP2OpenJPEGDataset::Open(GDALOpenInfo *poOpenInfo)

{
    if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr)
        return nullptr;

    /* Detect which codec to use : J2K or JP2 ? */
    vsi_l_offset nCodeStreamLength = 0;
    vsi_l_offset nCodeStreamStart =
        JP2OpenJPEGFindCodeStream(poOpenInfo->fpL, &nCodeStreamLength);

    if (nCodeStreamStart == 0 && nCodeStreamLength == 0)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "No code-stream in JP2 file");
        return nullptr;
    }

    OPJ_CODEC_FORMAT eCodecFormat =
        (nCodeStreamStart == 0) ? OPJ_CODEC_J2K : OPJ_CODEC_JP2;

    opj_codec_t *pCodec = opj_create_decompress(OPJ_CODEC_J2K);
    if (pCodec == nullptr)
        return nullptr;

    opj_set_info_handler(pCodec, JP2OpenJPEGDataset_InfoCallback, nullptr);
    opj_set_warning_handler(pCodec, JP2OpenJPEGDataset_WarningCallback,
                            nullptr);
    opj_set_error_handler(pCodec, JP2OpenJPEGDataset_ErrorCallback, nullptr);

    opj_dparameters_t parameters;
    opj_set_default_decoder_parameters(&parameters);

    if (!opj_setup_decoder(pCodec, &parameters))
    {
        opj_destroy_codec(pCodec);
        return nullptr;
    }

#if IS_OPENJPEG_OR_LATER(2, 3, 0)
    if (getenv("OPJ_NUM_THREADS") == nullptr)
    {
        JP2OpenJPEGDataset oTmpDS;
        opj_codec_set_threads(pCodec, oTmpDS.GetNumThreads());
    }
#endif

    JP2OpenJPEGFile *psJP2OpenJPEGFile =
        static_cast<JP2OpenJPEGFile *>(CPLMalloc(sizeof(JP2OpenJPEGFile)));
    psJP2OpenJPEGFile->fp = poOpenInfo->fpL;
    psJP2OpenJPEGFile->nBaseOffset = nCodeStreamStart;
    opj_stream_t *pStream =
        JP2OpenJPEGCreateReadStream(psJP2OpenJPEGFile, nCodeStreamLength);

    opj_image_t *psImage = nullptr;

    if (pStream == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "JP2OpenJPEGCreateReadStream() failed");
        opj_stream_destroy(pStream);
        opj_destroy_codec(pCodec);
        CPLFree(psJP2OpenJPEGFile);
        return nullptr;
    }

    if (!opj_read_header(pStream, pCodec, &psImage))
    {
        CPLError(CE_Failure, CPLE_AppDefined, "opj_read_header() failed");
        opj_destroy_codec(pCodec);
        opj_stream_destroy(pStream);
        opj_image_destroy(psImage);
        CPLFree(psJP2OpenJPEGFile);
        return nullptr;
    }

    opj_codestream_info_v2_t *pCodeStreamInfo = opj_get_cstr_info(pCodec);
    OPJ_UINT32 nTileW, nTileH;
    nTileW = pCodeStreamInfo->tdx;
    nTileH = pCodeStreamInfo->tdy;
#ifdef DEBUG
    OPJ_UINT32 nX0, nY0;
    OPJ_UINT32 nTilesX, nTilesY;
    nX0 = pCodeStreamInfo->tx0;
    nY0 = pCodeStreamInfo->ty0;
    nTilesX = pCodeStreamInfo->tw;
    nTilesY = pCodeStreamInfo->th;
    int mct = pCodeStreamInfo->m_default_tile_info.mct;
#endif
    int numResolutions =
        pCodeStreamInfo->m_default_tile_info.tccp_info[0].numresolutions;
    opj_destroy_cstr_info(&pCodeStreamInfo);

    if (psImage == nullptr)
    {
        opj_destroy_codec(pCodec);
        opj_stream_destroy(pStream);
        opj_image_destroy(psImage);
        CPLFree(psJP2OpenJPEGFile);
        return nullptr;
    }

#ifdef DEBUG
    CPLDebug("OPENJPEG", "nX0 = %u", nX0);
    CPLDebug("OPENJPEG", "nY0 = %u", nY0);
    CPLDebug("OPENJPEG", "nTileW = %u", nTileW);
    CPLDebug("OPENJPEG", "nTileH = %u", nTileH);
    CPLDebug("OPENJPEG", "nTilesX = %u", nTilesX);
    CPLDebug("OPENJPEG", "nTilesY = %u", nTilesY);
    CPLDebug("OPENJPEG", "mct = %d", mct);
    CPLDebug("OPENJPEG", "psImage->x0 = %u", psImage->x0);
    CPLDebug("OPENJPEG", "psImage->y0 = %u", psImage->y0);
    CPLDebug("OPENJPEG", "psImage->x1 = %u", psImage->x1);
    CPLDebug("OPENJPEG", "psImage->y1 = %u", psImage->y1);
    CPLDebug("OPENJPEG", "psImage->numcomps = %d", psImage->numcomps);
    // CPLDebug("OPENJPEG", "psImage->color_space = %d", psImage->color_space);
    CPLDebug("OPENJPEG", "numResolutions = %d", numResolutions);
    for (int i = 0; i < (int)psImage->numcomps; i++)
    {
        CPLDebug("OPENJPEG", "psImage->comps[%d].dx = %u", i,
                 psImage->comps[i].dx);
        CPLDebug("OPENJPEG", "psImage->comps[%d].dy = %u", i,
                 psImage->comps[i].dy);
        CPLDebug("OPENJPEG", "psImage->comps[%d].x0 = %u", i,
                 psImage->comps[i].x0);
        CPLDebug("OPENJPEG", "psImage->comps[%d].y0 = %u", i,
                 psImage->comps[i].y0);
        CPLDebug("OPENJPEG", "psImage->comps[%d].w = %u", i,
                 psImage->comps[i].w);
        CPLDebug("OPENJPEG", "psImage->comps[%d].h = %u", i,
                 psImage->comps[i].h);
        CPLDebug("OPENJPEG", "psImage->comps[%d].resno_decoded = %d", i,
                 psImage->comps[i].resno_decoded);
        CPLDebug("OPENJPEG", "psImage->comps[%d].factor = %d", i,
                 psImage->comps[i].factor);
        CPLDebug("OPENJPEG", "psImage->comps[%d].prec = %d", i,
                 psImage->comps[i].prec);
        CPLDebug("OPENJPEG", "psImage->comps[%d].sgnd = %d", i,
                 psImage->comps[i].sgnd);
    }
#endif

    if (psImage->x1 <= psImage->x0 || psImage->y1 <= psImage->y0 ||
        psImage->numcomps == 0 || (psImage->comps[0].w >> 31) != 0 ||
        (psImage->comps[0].h >> 31) != 0 || (nTileW >> 31) != 0 ||
        (nTileH >> 31) != 0 ||
        psImage->comps[0].w != psImage->x1 - psImage->x0 ||
        psImage->comps[0].h != psImage->y1 - psImage->y0)
    {
        CPLDebug("OPENJPEG", "Unable to handle that image (1)");
        opj_destroy_codec(pCodec);
        opj_stream_destroy(pStream);
        opj_image_destroy(psImage);
        CPLFree(psJP2OpenJPEGFile);
        return nullptr;
    }

    GDALDataType eDataType = GDT_Byte;
    if (psImage->comps[0].prec > 16)
    {
        if (psImage->comps[0].sgnd)
            eDataType = GDT_Int32;
        else
            eDataType = GDT_UInt32;
    }
    else if (psImage->comps[0].prec > 8)
    {
        if (psImage->comps[0].sgnd)
            eDataType = GDT_Int16;
        else
            eDataType = GDT_UInt16;
    }

    int bIs420 =
        (psImage->color_space != OPJ_CLRSPC_SRGB && eDataType == GDT_Byte &&
         (psImage->numcomps == 3 || psImage->numcomps == 4) &&
         psImage->comps[1].w == psImage->comps[0].w / 2 &&
         psImage->comps[1].h == psImage->comps[0].h / 2 &&
         psImage->comps[2].w == psImage->comps[0].w / 2 &&
         psImage->comps[2].h == psImage->comps[0].h / 2) &&
        (psImage->numcomps == 3 ||
         (psImage->numcomps == 4 &&
          psImage->comps[3].w == psImage->comps[0].w &&
          psImage->comps[3].h == psImage->comps[0].h));

    if (bIs420)
    {
        CPLDebug("OPENJPEG", "420 format");
    }
    else
    {
        int iBand;
        for (iBand = 2; iBand <= (int)psImage->numcomps; iBand++)
        {
            if (psImage->comps[iBand - 1].w != psImage->comps[0].w ||
                psImage->comps[iBand - 1].h != psImage->comps[0].h)
            {
                CPLDebug("OPENJPEG", "Unable to handle that image (2)");
                opj_destroy_codec(pCodec);
                opj_stream_destroy(pStream);
                opj_image_destroy(psImage);
                CPLFree(psJP2OpenJPEGFile);
                return nullptr;
            }
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Create a corresponding GDALDataset.                             */
    /* -------------------------------------------------------------------- */
    JP2OpenJPEGDataset *poDS;
    int iBand;

    poDS = new JP2OpenJPEGDataset();
    poDS->m_osFilename = poOpenInfo->pszFilename;
    if (eCodecFormat == OPJ_CODEC_JP2)
        poDS->eAccess = poOpenInfo->eAccess;
    poDS->eColorSpace = psImage->color_space;
    poDS->nRasterXSize = psImage->x1 - psImage->x0;
    poDS->nRasterYSize = psImage->y1 - psImage->y0;
    poDS->nBands = psImage->numcomps;
    poDS->fp = poOpenInfo->fpL;
    poOpenInfo->fpL = nullptr;
    poDS->nCodeStreamStart = nCodeStreamStart;
    poDS->nCodeStreamLength = nCodeStreamLength;
    poDS->bIs420 = bIs420;
    poDS->bSingleTiled = (poDS->nRasterXSize == (int)nTileW &&
                          poDS->nRasterYSize == (int)nTileH);
    poDS->m_nX0 = psImage->x0;
    poDS->m_nY0 = psImage->y0;

    int nBlockXSize = (int)nTileW;
    int nBlockYSize = (int)nTileH;

    if (CPLFetchBool(poOpenInfo->papszOpenOptions, "USE_TILE_AS_BLOCK", false))
    {
        poDS->bUseSetDecodeArea = false;
    }

#if IS_OPENJPEG_OR_LATER(2, 5, 0)
    if (!CPLTestBool(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
                                          "STRICT", "YES")))
    {
        poDS->m_bStrict = false;
        opj_decoder_set_strict_mode(pCodec, false);
    }
#endif

    /* Some Sentinel2 preview datasets are 343x343 large, but with 8x8 blocks */
    /* Using the tile API for that is super slow, so expose a single block */
    else if (poDS->nRasterXSize <= 1024 && poDS->nRasterYSize <= 1024 &&
             nTileW < 32 && nTileH < 32)
    {
        poDS->bUseSetDecodeArea = true;
        nBlockXSize = poDS->nRasterXSize;
        nBlockYSize = poDS->nRasterYSize;
    }
    else
    {
        poDS->bUseSetDecodeArea =
            poDS->bSingleTiled &&
            (poDS->nRasterXSize > 1024 || poDS->nRasterYSize > 1024);

        /* Other Sentinel2 preview datasets are 343x343 and 60m are 1830x1830,
         * but they */
        /* are tiled with tile dimensions 2048x2048. It would be a waste of */
        /* memory to allocate such big blocks */
        if (poDS->nRasterXSize < (int)nTileW &&
            poDS->nRasterYSize < (int)nTileH)
        {
            poDS->bUseSetDecodeArea = TRUE;
            nBlockXSize = poDS->nRasterXSize;
            nBlockYSize = poDS->nRasterYSize;
            if (nBlockXSize > 2048)
                nBlockXSize = 2048;
            if (nBlockYSize > 2048)
                nBlockYSize = 2048;
        }
        else if (poDS->bUseSetDecodeArea)
        {
            // Arbitrary threshold... ~4 million at least needed for the GRIB2
            // images mentioned below.
            if (nTileH == 1 && nTileW < 20 * 1024 * 1024)
            {
                // Some GRIB2 JPEG2000 compressed images are a 2D image
                // organized as a single line image...
            }
            else
            {
                if (nBlockXSize > 1024)
                    nBlockXSize = 1024;
                if (nBlockYSize > 1024)
                    nBlockYSize = 1024;
            }
        }
    }

    GDALColorTable *poCT = nullptr;

    /* -------------------------------------------------------------------- */
    /*      Look for color table or cdef box                                */
    /* -------------------------------------------------------------------- */
    if (eCodecFormat == OPJ_CODEC_JP2)
    {
        vsi_l_offset nCurOffset = VSIFTellL(poDS->fp);

        GDALJP2Box oBox(poDS->fp);
        if (oBox.ReadFirst())
        {
            while (strlen(oBox.GetType()) > 0)
            {
                if (EQUAL(oBox.GetType(), "jp2h"))
                {
                    GDALJP2Box oSubBox(poDS->fp);

                    for (oSubBox.ReadFirstChild(&oBox);
                         strlen(oSubBox.GetType()) > 0;
                         oSubBox.ReadNextChild(&oBox))
                    {
                        GIntBig nDataLength = oSubBox.GetDataLength();
                        if (poCT == nullptr &&
                            EQUAL(oSubBox.GetType(), "pclr") &&
                            nDataLength >= 3 &&
                            nDataLength <= 2 + 1 + 4 + 4 * 256)
                        {
                            GByte *pabyCT = oSubBox.ReadBoxData();
                            if (pabyCT != nullptr)
                            {
                                int nEntries = (pabyCT[0] << 8) | pabyCT[1];
                                int nComponents = pabyCT[2];
                                /* CPLDebug("OPENJPEG", "Color table found"); */
                                if (nEntries <= 256 && nComponents == 3)
                                {
                                    /*CPLDebug("OPENJPEG", "resol[0] = %d",
                                    pabyCT[3]); CPLDebug("OPENJPEG", "resol[1] =
                                    %d", pabyCT[4]); CPLDebug("OPENJPEG",
                                    "resol[2] = %d", pabyCT[5]);*/
                                    if (pabyCT[3] == 7 && pabyCT[4] == 7 &&
                                        pabyCT[5] == 7 &&
                                        nDataLength == 2 + 1 + 3 + 3 * nEntries)
                                    {
                                        poCT = new GDALColorTable();
                                        for (int i = 0; i < nEntries; i++)
                                        {
                                            GDALColorEntry sEntry;
                                            sEntry.c1 = pabyCT[6 + 3 * i];
                                            sEntry.c2 = pabyCT[6 + 3 * i + 1];
                                            sEntry.c3 = pabyCT[6 + 3 * i + 2];
                                            sEntry.c4 = 255;
                                            poCT->SetColorEntry(i, &sEntry);
                                        }
                                    }
                                }
                                else if (nEntries <= 256 && nComponents == 4)
                                {
                                    if (pabyCT[3] == 7 && pabyCT[4] == 7 &&
                                        pabyCT[5] == 7 && pabyCT[6] == 7 &&
                                        nDataLength == 2 + 1 + 4 + 4 * nEntries)
                                    {
                                        poCT = new GDALColorTable();
                                        for (int i = 0; i < nEntries; i++)
                                        {
                                            GDALColorEntry sEntry;
                                            sEntry.c1 = pabyCT[7 + 4 * i];
                                            sEntry.c2 = pabyCT[7 + 4 * i + 1];
                                            sEntry.c3 = pabyCT[7 + 4 * i + 2];
                                            sEntry.c4 = pabyCT[7 + 4 * i + 3];
                                            poCT->SetColorEntry(i, &sEntry);
                                        }
                                    }
                                }
                                CPLFree(pabyCT);
                            }
                        }
                        /* There's a bug/misfeature in openjpeg: the color_space
                           only gets set at read tile time */
                        else if (EQUAL(oSubBox.GetType(), "colr") &&
                                 nDataLength == 7)
                        {
                            GByte *pabyContent = oSubBox.ReadBoxData();
                            if (pabyContent != nullptr)
                            {
                                if (pabyContent[0] ==
                                    1 /* enumerated colourspace */)
                                {
                                    GUInt32 enumcs = (pabyContent[3] << 24) |
                                                     (pabyContent[4] << 16) |
                                                     (pabyContent[5] << 8) |
                                                     (pabyContent[6]);
                                    if (enumcs == 16)
                                    {
                                        poDS->eColorSpace = OPJ_CLRSPC_SRGB;
                                        CPLDebug("OPENJPEG",
                                                 "SRGB color space");
                                    }
                                    else if (enumcs == 17)
                                    {
                                        poDS->eColorSpace = OPJ_CLRSPC_GRAY;
                                        CPLDebug("OPENJPEG",
                                                 "Grayscale color space");
                                    }
                                    else if (enumcs == 18)
                                    {
                                        poDS->eColorSpace = OPJ_CLRSPC_SYCC;
                                        CPLDebug("OPENJPEG",
                                                 "SYCC color space");
                                    }
                                    else if (enumcs == 20)
                                    {
                                        /* Used by
                                         * J2KP4files/testfiles_jp2/file7.jp2 */
                                        poDS->eColorSpace = OPJ_CLRSPC_SRGB;
                                        CPLDebug("OPENJPEG",
                                                 "e-sRGB color space");
                                    }
                                    else if (enumcs == 21)
                                    {
                                        /* Used by
                                         * J2KP4files/testfiles_jp2/file5.jp2 */
                                        poDS->eColorSpace = OPJ_CLRSPC_SRGB;
                                        CPLDebug("OPENJPEG",
                                                 "ROMM-RGB color space");
                                    }
                                    else
                                    {
                                        poDS->eColorSpace = OPJ_CLRSPC_UNKNOWN;
                                        CPLDebug("OPENJPEG",
                                                 "Unknown color space");
                                    }
                                }
                                CPLFree(pabyContent);
                            }
                        }
                        /* Check if there's an alpha channel or odd channel
                         * attribution */
                        else if (EQUAL(oSubBox.GetType(), "cdef") &&
                                 nDataLength == 2 + poDS->nBands * 6)
                        {
                            GByte *pabyContent = oSubBox.ReadBoxData();
                            if (pabyContent != nullptr)
                            {
                                int nEntries =
                                    (pabyContent[0] << 8) | pabyContent[1];
                                if (nEntries == poDS->nBands)
                                {
                                    poDS->nRedIndex = -1;
                                    poDS->nGreenIndex = -1;
                                    poDS->nBlueIndex = -1;
                                    for (int i = 0; i < poDS->nBands; i++)
                                    {
                                        int CNi =
                                            (pabyContent[2 + 6 * i] << 8) |
                                            pabyContent[2 + 6 * i + 1];
                                        int Typi =
                                            (pabyContent[2 + 6 * i + 2] << 8) |
                                            pabyContent[2 + 6 * i + 3];
                                        int Asoci =
                                            (pabyContent[2 + 6 * i + 4] << 8) |
                                            pabyContent[2 + 6 * i + 5];
                                        if (CNi < 0 || CNi >= poDS->nBands)
                                        {
                                            CPLError(CE_Failure,
                                                     CPLE_AppDefined,
                                                     "Wrong value of CN%d=%d",
                                                     i, CNi);
                                            break;
                                        }
                                        if (Typi == 0)
                                        {
                                            if (Asoci == 1)
                                                poDS->nRedIndex = CNi;
                                            else if (Asoci == 2)
                                                poDS->nGreenIndex = CNi;
                                            else if (Asoci == 3)
                                                poDS->nBlueIndex = CNi;
                                            else if (Asoci < 0 ||
                                                     (Asoci > poDS->nBands &&
                                                      Asoci != 65535))
                                            {
                                                CPLError(
                                                    CE_Failure, CPLE_AppDefined,
                                                    "Wrong value of Asoc%d=%d",
                                                    i, Asoci);
                                                break;
                                            }
                                        }
                                        else if (Typi == 1)
                                        {
                                            poDS->nAlphaIndex = CNi;
                                        }
                                    }
                                }
                                else
                                {
                                    CPLDebug("OPENJPEG",
                                             "Unsupported cdef content");
                                }
                                CPLFree(pabyContent);
                            }
                        }
                    }
                }

                if (!oBox.ReadNext())
                    break;
            }
        }

        VSIFSeekL(poDS->fp, nCurOffset, SEEK_SET);
    }

    /* -------------------------------------------------------------------- */
    /*      Create band information objects.                                */
    /* -------------------------------------------------------------------- */
    for (iBand = 1; iBand <= poDS->nBands; iBand++)
    {
        const bool bPromoteTo8Bit =
            iBand == poDS->nAlphaIndex + 1 &&
            psImage->comps[(poDS->nAlphaIndex == 0 && poDS->nBands > 1) ? 1 : 0]
                    .prec == 8 &&
            psImage->comps[poDS->nAlphaIndex].prec == 1 &&
            CPLFetchBool(poOpenInfo->papszOpenOptions, "1BIT_ALPHA_PROMOTION",
                         CPLTestBool(CPLGetConfigOption(
                             "JP2OPENJPEG_PROMOTE_1BIT_ALPHA_AS_8BIT", "YES")));
        if (bPromoteTo8Bit)
            CPLDebug("JP2OpenJPEG",
                     "Alpha band is promoted from 1 bit to 8 bit");

        JP2OpenJPEGRasterBand *poBand = new JP2OpenJPEGRasterBand(
            poDS, iBand, eDataType,
            bPromoteTo8Bit ? 8 : psImage->comps[iBand - 1].prec, bPromoteTo8Bit,
            nBlockXSize, nBlockYSize);
        if (iBand == 1 && poCT != nullptr)
            poBand->poCT = poCT;
        poDS->SetBand(iBand, poBand);
    }

    /* -------------------------------------------------------------------- */
    /*      Create overview datasets.                                       */
    /* -------------------------------------------------------------------- */
    int nW = poDS->nRasterXSize;
    int nH = poDS->nRasterYSize;
    poDS->nParentXSize = poDS->nRasterXSize;
    poDS->nParentYSize = poDS->nRasterYSize;

    /* Lower resolutions are not compatible with a color-table */
    if (poCT != nullptr)
        numResolutions = 0;

#if IS_OPENJPEG_OR_LATER(2, 3, 0)
    if (poDS->bSingleTiled && poDS->bUseSetDecodeArea)
    {
        poDS->m_ppCodec = new opj_codec_t *(pCodec);
        poDS->m_ppStream = new opj_stream_t *(pStream);
        poDS->m_ppsImage = new opj_image_t *(psImage);
        poDS->m_psJP2OpenJPEGFile = psJP2OpenJPEGFile;
    }
    poDS->m_pnLastLevel = new int(-1);
#endif

    while (
        poDS->nOverviewCount + 1 < numResolutions && (nW > 128 || nH > 128) &&
        (poDS->bUseSetDecodeArea || ((nTileW % 2) == 0 && (nTileH % 2) == 0)))
    {
        // This must be this exact formula per the JPEG2000 standard
        nW = (nW + 1) / 2;
        nH = (nH + 1) / 2;

        poDS->papoOverviewDS = (JP2OpenJPEGDataset **)CPLRealloc(
            poDS->papoOverviewDS,
            (poDS->nOverviewCount + 1) * sizeof(JP2OpenJPEGDataset *));
        JP2OpenJPEGDataset *poODS = new JP2OpenJPEGDataset();
        poODS->m_osFilename = poDS->m_osFilename;
        poODS->nParentXSize = poDS->nRasterXSize;
        poODS->nParentYSize = poDS->nRasterYSize;
        poODS->SetDescription(poOpenInfo->pszFilename);
        poODS->iLevel = poDS->nOverviewCount + 1;
        poODS->bSingleTiled = poDS->bSingleTiled;
        poODS->bUseSetDecodeArea = poDS->bUseSetDecodeArea;
        poODS->nRedIndex = poDS->nRedIndex;
        poODS->nGreenIndex = poDS->nGreenIndex;
        poODS->nBlueIndex = poDS->nBlueIndex;
        poODS->nAlphaIndex = poDS->nAlphaIndex;
        if (!poDS->bUseSetDecodeArea)
        {
            nTileW /= 2;
            nTileH /= 2;
            nBlockXSize = (int)nTileW;
            nBlockYSize = (int)nTileH;
        }
        else
        {
            nBlockXSize = std::min(nW, (int)nTileW);
            nBlockYSize = std::min(nH, (int)nTileH);
        }

        poODS->eColorSpace = poDS->eColorSpace;
        poODS->nRasterXSize = nW;
        poODS->nRasterYSize = nH;
        poODS->nBands = poDS->nBands;
        poODS->fp = poDS->fp;
        poODS->nCodeStreamStart = nCodeStreamStart;
        poODS->nCodeStreamLength = nCodeStreamLength;
        poODS->bIs420 = bIs420;

#if IS_OPENJPEG_OR_LATER(2, 3, 0)
        if (poODS->bSingleTiled && poODS->bUseSetDecodeArea)
        {
            poODS->m_ppCodec = poDS->m_ppCodec;
            poODS->m_ppStream = poDS->m_ppStream;
            poODS->m_ppsImage = poDS->m_ppsImage;
            poODS->m_psJP2OpenJPEGFile = poDS->m_psJP2OpenJPEGFile;
        }
        poODS->m_pnLastLevel = poDS->m_pnLastLevel;
#endif

#if IS_OPENJPEG_OR_LATER(2, 5, 0)
        poODS->m_bStrict = poDS->m_bStrict;
#endif

        poODS->m_nX0 = poDS->m_nX0;
        poODS->m_nY0 = poDS->m_nY0;

        for (iBand = 1; iBand <= poDS->nBands; iBand++)
        {
            const bool bPromoteTo8Bit =
                iBand == poDS->nAlphaIndex + 1 &&
                psImage->comps[(poDS->nAlphaIndex == 0 && poDS->nBands > 1) ? 1
                                                                            : 0]
                        .prec == 8 &&
                psImage->comps[poDS->nAlphaIndex].prec == 1 &&
                CPLFetchBool(
                    poOpenInfo->papszOpenOptions, "1BIT_ALPHA_PROMOTION",
                    CPLTestBool(CPLGetConfigOption(
                        "JP2OPENJPEG_PROMOTE_1BIT_ALPHA_AS_8BIT", "YES")));

            poODS->SetBand(
                iBand, new JP2OpenJPEGRasterBand(
                           poODS, iBand, eDataType,
                           bPromoteTo8Bit ? 8 : psImage->comps[iBand - 1].prec,
                           bPromoteTo8Bit, nBlockXSize, nBlockYSize));
        }

        poDS->papoOverviewDS[poDS->nOverviewCount++] = poODS;
    }

#if IS_OPENJPEG_OR_LATER(2, 3, 0)
    if (poDS->bSingleTiled && poDS->bUseSetDecodeArea)
    {
        // nothing
    }
    else
#endif
    {
        opj_destroy_codec(pCodec);
        opj_stream_destroy(pStream);
        opj_image_destroy(psImage);
        CPLFree(psJP2OpenJPEGFile);
        pCodec = nullptr;
        pStream = nullptr;
        psImage = nullptr;
    }

    /* -------------------------------------------------------------------- */
    /*      More metadata.                                                  */
    /* -------------------------------------------------------------------- */
    if (poDS->nBands > 1)
    {
        poDS->GDALDataset::SetMetadataItem("INTERLEAVE", "PIXEL",
                                           "IMAGE_STRUCTURE");
    }

    poOpenInfo->fpL = poDS->fp;
    vsi_l_offset nCurOffset = VSIFTellL(poDS->fp);
    poDS->LoadJP2Metadata(poOpenInfo);
    VSIFSeekL(poDS->fp, nCurOffset, SEEK_SET);
    poOpenInfo->fpL = nullptr;

    poDS->bHasGeoreferencingAtOpening =
        (!poDS->m_oSRS.IsEmpty() || poDS->nGCPCount != 0 ||
         poDS->bGeoTransformValid);

    /* -------------------------------------------------------------------- */
    /*      Vector layers                                                   */
    /* -------------------------------------------------------------------- */
    if (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR)
    {
        poDS->LoadVectorLayers(CPLFetchBool(poOpenInfo->papszOpenOptions,
                                            "OPEN_REMOTE_GML", false));

        // If file opened in vector-only mode and there's no vector,
        // return
        if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) == 0 &&
            poDS->GetLayerCount() == 0)
        {
            delete poDS;
            return nullptr;
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Initialize any PAM information.                                 */
    /* -------------------------------------------------------------------- */
    poDS->SetDescription(poOpenInfo->pszFilename);
    poDS->TryLoadXML();

    /* -------------------------------------------------------------------- */
    /*      Check for overviews.                                            */
    /* -------------------------------------------------------------------- */
    poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);

    return poDS;
}

/************************************************************************/
/*                           WriteBox()                                 */
/************************************************************************/

void JP2OpenJPEGDataset::WriteBox(VSILFILE *fp, GDALJP2Box *poBox)
{
    GUInt32 nLBox;
    GUInt32 nTBox;

    if (poBox == nullptr)
        return;

    nLBox = (int)poBox->GetDataLength() + 8;
    nLBox = CPL_MSBWORD32(nLBox);

    memcpy(&nTBox, poBox->GetType(), 4);

    VSIFWriteL(&nLBox, 4, 1, fp);
    VSIFWriteL(&nTBox, 4, 1, fp);
    VSIFWriteL(poBox->GetWritableData(), 1, (int)poBox->GetDataLength(), fp);
}

/************************************************************************/
/*                         WriteGDALMetadataBox()                       */
/************************************************************************/

void JP2OpenJPEGDataset::WriteGDALMetadataBox(VSILFILE *fp,
                                              GDALDataset *poSrcDS,
                                              char **papszOptions)
{
    GDALJP2Box *poBox = GDALJP2Metadata::CreateGDALMultiDomainMetadataXMLBox(
        poSrcDS, CPLFetchBool(papszOptions, "MAIN_MD_DOMAIN_ONLY", false));
    if (poBox)
        WriteBox(fp, poBox);
    delete poBox;
}

/************************************************************************/
/*                         WriteXMLBoxes()                              */
/************************************************************************/

void JP2OpenJPEGDataset::WriteXMLBoxes(VSILFILE *fp, GDALDataset *poSrcDS,
                                       CPL_UNUSED char **papszOptions)
{
    int nBoxes = 0;
    GDALJP2Box **papoBoxes = GDALJP2Metadata::CreateXMLBoxes(poSrcDS, &nBoxes);
    for (int i = 0; i < nBoxes; i++)
    {
        WriteBox(fp, papoBoxes[i]);
        delete papoBoxes[i];
    }
    CPLFree(papoBoxes);
}

/************************************************************************/
/*                           WriteXMPBox()                              */
/************************************************************************/

void JP2OpenJPEGDataset::WriteXMPBox(VSILFILE *fp, GDALDataset *poSrcDS,
                                     CPL_UNUSED char **papszOptions)
{
    GDALJP2Box *poBox = GDALJP2Metadata::CreateXMPBox(poSrcDS);
    if (poBox)
        WriteBox(fp, poBox);
    delete poBox;
}

/************************************************************************/
/*                           WriteIPRBox()                              */
/************************************************************************/

void JP2OpenJPEGDataset::WriteIPRBox(VSILFILE *fp, GDALDataset *poSrcDS,
                                     CPL_UNUSED char **papszOptions)
{
    GDALJP2Box *poBox = GDALJP2Metadata::CreateIPRBox(poSrcDS);
    if (poBox)
        WriteBox(fp, poBox);
    delete poBox;
}
/************************************************************************/
/*                         FloorPowerOfTwo()                            */
/************************************************************************/

static int FloorPowerOfTwo(int nVal)
{
    int nBits = 0;
    while (nVal > 1)
    {
        nBits++;
        nVal >>= 1;
    }
    return 1 << nBits;
}

/************************************************************************/
/*                          CreateCopy()                                */
/************************************************************************/

GDALDataset *JP2OpenJPEGDataset::CreateCopy(
    const char *pszFilename, GDALDataset *poSrcDS, CPL_UNUSED int bStrict,
    char **papszOptions, GDALProgressFunc pfnProgress, void *pProgressData)

{
    int nBands = poSrcDS->GetRasterCount();
    int nXSize = poSrcDS->GetRasterXSize();
    int nYSize = poSrcDS->GetRasterYSize();

    if (nBands == 0 || nBands > 16384)
    {
        CPLError(
            CE_Failure, CPLE_NotSupported,
            "Unable to export files with %d bands. Must be >= 1 and <= 16384",
            nBands);
        return nullptr;
    }

    GDALColorTable *poCT = poSrcDS->GetRasterBand(1)->GetColorTable();
    if (poCT != nullptr && nBands != 1)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "JP2OpenJPEG driver only supports a color table for a "
                 "single-band dataset");
        return nullptr;
    }

    GDALDataType eDataType = poSrcDS->GetRasterBand(1)->GetRasterDataType();
    int nDataTypeSize = (GDALGetDataTypeSize(eDataType) / 8);
    if (eDataType != GDT_Byte && eDataType != GDT_Int16 &&
        eDataType != GDT_UInt16 && eDataType != GDT_Int32 &&
        eDataType != GDT_UInt32)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "JP2OpenJPEG driver only supports creating Byte, GDT_Int16, "
                 "GDT_UInt16, GDT_Int32, GDT_UInt32");
        return nullptr;
    }

    const bool bInspireTG = CPLFetchBool(papszOptions, "INSPIRE_TG", false);

    /* -------------------------------------------------------------------- */
    /*      Analyze creation options.                                       */
    /* -------------------------------------------------------------------- */
    OPJ_CODEC_FORMAT eCodecFormat = OPJ_CODEC_J2K;
    const char *pszCodec = CSLFetchNameValueDef(papszOptions, "CODEC", nullptr);
    if (pszCodec)
    {
        if (EQUAL(pszCodec, "JP2"))
            eCodecFormat = OPJ_CODEC_JP2;
        else if (EQUAL(pszCodec, "J2K"))
            eCodecFormat = OPJ_CODEC_J2K;
        else
        {
            CPLError(CE_Warning, CPLE_NotSupported,
                     "Unsupported value for CODEC : %s. Defaulting to J2K",
                     pszCodec);
        }
    }
    else
    {
        if (strlen(pszFilename) > 4 &&
            EQUAL(pszFilename + strlen(pszFilename) - 4, ".JP2"))
        {
            eCodecFormat = OPJ_CODEC_JP2;
        }
    }
    if (eCodecFormat != OPJ_CODEC_JP2 && bInspireTG)
    {
        CPLError(CE_Warning, CPLE_NotSupported,
                 "INSPIRE_TG=YES mandates CODEC=JP2 (TG requirement 21)");
        return nullptr;
    }

    // NOTE: if changing the default block size, the logic in nitfdataset.cpp
    // CreateCopy() will have to be changed as well.
    int nBlockXSize =
        atoi(CSLFetchNameValueDef(papszOptions, "BLOCKXSIZE", "1024"));
    int nBlockYSize =
        atoi(CSLFetchNameValueDef(papszOptions, "BLOCKYSIZE", "1024"));
    if (nBlockXSize <= 0 || nBlockYSize <= 0)
    {
        CPLError(CE_Failure, CPLE_NotSupported, "Invalid block size");
        return nullptr;
    }

    // By default do not generate tile sizes larger than the dataset
    // dimensions
    if (!CPLFetchBool(papszOptions, "BLOCKSIZE_STRICT", false) &&
        !CPLFetchBool(papszOptions, "@BLOCKSIZE_STRICT", false))
    {
        if (nBlockXSize < 32 || nBlockYSize < 32)
        {
            CPLError(CE_Failure, CPLE_NotSupported, "Invalid block size");
            return nullptr;
        }

        if (nXSize < nBlockXSize)
        {
            CPLDebug("OPENJPEG", "Adjusting block width from %d to %d",
                     nBlockXSize, nXSize);
            nBlockXSize = nXSize;
        }
        if (nYSize < nBlockYSize)
        {
            CPLDebug("OPENJPEG", "Adjusting block width from %d to %d",
                     nBlockYSize, nYSize);
            nBlockYSize = nYSize;
        }
    }

    OPJ_PROG_ORDER eProgOrder = OPJ_LRCP;
    const char *pszPROGORDER =
        CSLFetchNameValueDef(papszOptions, "PROGRESSION", "LRCP");
    if (EQUAL(pszPROGORDER, "LRCP"))
        eProgOrder = OPJ_LRCP;
    else if (EQUAL(pszPROGORDER, "RLCP"))
        eProgOrder = OPJ_RLCP;
    else if (EQUAL(pszPROGORDER, "RPCL"))
        eProgOrder = OPJ_RPCL;
    else if (EQUAL(pszPROGORDER, "PCRL"))
        eProgOrder = OPJ_PCRL;
    else if (EQUAL(pszPROGORDER, "CPRL"))
        eProgOrder = OPJ_CPRL;
    else
    {
        CPLError(CE_Warning, CPLE_NotSupported,
                 "Unsupported value for PROGRESSION : %s. Defaulting to LRCP",
                 pszPROGORDER);
    }

    const bool bIsIrreversible =
        !CPLFetchBool(papszOptions, "REVERSIBLE", poCT != nullptr);

    std::vector<double> adfRates;
    const char *pszQuality =
        CSLFetchNameValueDef(papszOptions, "QUALITY", nullptr);
    double dfDefaultQuality = (poCT != nullptr) ? 100.0 : 25.0;
    if (pszQuality)
    {
        char **papszTokens =
            CSLTokenizeStringComplex(pszQuality, ",", FALSE, FALSE);
        for (int i = 0; papszTokens[i] != nullptr; i++)
        {
            double dfQuality = CPLAtof(papszTokens[i]);
            if (dfQuality > 0 && dfQuality <= 100)
            {
                double dfRate = 100 / dfQuality;
                adfRates.push_back(dfRate);
            }
            else
            {
                CPLError(CE_Warning, CPLE_NotSupported,
                         "Unsupported value for QUALITY: %s. Defaulting to "
                         "single-layer, with quality=%.0f",
                         papszTokens[i], dfDefaultQuality);
                adfRates.resize(0);
                break;
            }
        }
        if (papszTokens[0] == nullptr)
        {
            CPLError(CE_Warning, CPLE_NotSupported,
                     "Unsupported value for QUALITY: %s. Defaulting to "
                     "single-layer, with quality=%.0f",
                     pszQuality, dfDefaultQuality);
        }
        CSLDestroy(papszTokens);
    }
    if (adfRates.empty())
    {
        adfRates.push_back(100. / dfDefaultQuality);
        assert(!adfRates.empty());
    }

    if (poCT != nullptr && (bIsIrreversible || adfRates.back() != 1.0))
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "Encoding a dataset with a color table with REVERSIBLE != YES "
                 "or QUALITY != 100 will likely lead to bad visual results");
    }

    const int nMaxTileDim = std::max(nBlockXSize, nBlockYSize);
    int nNumResolutions = 1;
    /* Pickup a reasonable value compatible with PROFILE_1 requirements */
    while ((nMaxTileDim >> (nNumResolutions - 1)) > 128)
        nNumResolutions++;
    int nMinProfile1Resolutions = nNumResolutions;
    const char *pszResolutions =
        CSLFetchNameValueDef(papszOptions, "RESOLUTIONS", nullptr);
    if (pszResolutions)
    {
        nNumResolutions = atoi(pszResolutions);
        if (nNumResolutions <= 0 || nNumResolutions >= 32 ||
            (nMaxTileDim >> nNumResolutions) == 0)
        {
            CPLError(CE_Warning, CPLE_NotSupported,
                     "Unsupported value for RESOLUTIONS : %s. Defaulting to %d",
                     pszResolutions, nMinProfile1Resolutions);
            nNumResolutions = nMinProfile1Resolutions;
        }
    }

    int bSOP = CPLTestBool(CSLFetchNameValueDef(papszOptions, "SOP", "FALSE"));
    int bEPH = CPLTestBool(CSLFetchNameValueDef(papszOptions, "EPH", "FALSE"));

    int nRedBandIndex = -1;
    int nGreenBandIndex = -1;
    int nBlueBandIndex = -1;
    int nAlphaBandIndex = -1;
    for (int i = 0; i < nBands; i++)
    {
        GDALColorInterp eInterp =
            poSrcDS->GetRasterBand(i + 1)->GetColorInterpretation();
        if (eInterp == GCI_RedBand)
            nRedBandIndex = i;
        else if (eInterp == GCI_GreenBand)
            nGreenBandIndex = i;
        else if (eInterp == GCI_BlueBand)
            nBlueBandIndex = i;
        else if (eInterp == GCI_AlphaBand)
            nAlphaBandIndex = i;
    }
    const char *pszAlpha = CSLFetchNameValue(papszOptions, "ALPHA");
    if (nAlphaBandIndex < 0 && nBands > 1 && pszAlpha != nullptr &&
        CPLTestBool(pszAlpha))
    {
        nAlphaBandIndex = nBands - 1;
    }

    const char *pszYCBCR420 = CSLFetchNameValue(papszOptions, "YCBCR420");
    int bYCBCR420 = FALSE;
    if (pszYCBCR420 && CPLTestBool(pszYCBCR420))
    {
        if ((nBands == 3 || nBands == 4) && eDataType == GDT_Byte &&
            nRedBandIndex == 0 && nGreenBandIndex == 1 && nBlueBandIndex == 2)
        {
            if (((nXSize % 2) == 0 && (nYSize % 2) == 0 &&
                 (nBlockXSize % 2) == 0 && (nBlockYSize % 2) == 0))
            {
                bYCBCR420 = TRUE;
            }
            else
            {
                CPLError(CE_Warning, CPLE_NotSupported,
                         "YCBCR420 unsupported when image size and/or tile "
                         "size are not multiple of 2");
            }
        }
        else
        {
            CPLError(CE_Warning, CPLE_NotSupported,
                     "YCBCR420 unsupported with this image band count and/or "
                     "data byte");
        }
    }

    const char *pszYCC = CSLFetchNameValue(papszOptions, "YCC");
    int bYCC = ((nBands == 3 || nBands == 4) &&
                CPLTestBool(CSLFetchNameValueDef(papszOptions, "YCC", "TRUE")));

#if !(IS_OPENJPEG_OR_LATER(2, 2, 0))
    /* Depending on the way OpenJPEG <= r2950 is built, YCC with 4 bands might
     * work on Debug mode, but this relies on unreliable stack buffer overflows,
     * so better err on the safe side */
    if (bYCC && nBands > 3)
    {
        if (pszYCC != nullptr)
        {
            CPLError(
                CE_Warning, CPLE_AppDefined,
                "OpenJPEG r2950 and below can generate invalid output with "
                "MCT YCC transform and more than 3 bands. Disabling YCC");
        }
        bYCC = FALSE;
    }
#endif

    if (bYCBCR420 && bYCC)
    {
        if (pszYCC != nullptr)
        {
            CPLError(CE_Warning, CPLE_NotSupported,
                     "YCC unsupported when YCbCr requesting");
        }
        bYCC = FALSE;
    }

    /* -------------------------------------------------------------------- */
    /*      Deal with codeblocks size                                       */
    /* -------------------------------------------------------------------- */

    int nCblockW =
        atoi(CSLFetchNameValueDef(papszOptions, "CODEBLOCK_WIDTH", "64"));
    int nCblockH =
        atoi(CSLFetchNameValueDef(papszOptions, "CODEBLOCK_HEIGHT", "64"));
    if (nCblockW < 4 || nCblockW > 1024 || nCblockH < 4 || nCblockH > 1024)
    {
        CPLError(CE_Warning, CPLE_NotSupported,
                 "Invalid values for codeblock size. Defaulting to 64x64");
        nCblockW = 64;
        nCblockH = 64;
    }
    else if (nCblockW * nCblockH > 4096)
    {
        CPLError(CE_Warning, CPLE_NotSupported,
                 "Invalid values for codeblock size. "
                 "CODEBLOCK_WIDTH * CODEBLOCK_HEIGHT should be <= 4096. "
                 "Defaulting to 64x64");
        nCblockW = 64;
        nCblockH = 64;
    }
    int nCblockW_po2 = FloorPowerOfTwo(nCblockW);
    int nCblockH_po2 = FloorPowerOfTwo(nCblockH);
    if (nCblockW_po2 != nCblockW || nCblockH_po2 != nCblockH)
    {
        CPLError(CE_Warning, CPLE_NotSupported,
                 "Non power of two values used for codeblock size. "
                 "Using to %dx%d",
                 nCblockW_po2, nCblockH_po2);
    }
    nCblockW = nCblockW_po2;
    nCblockH = nCblockH_po2;

    /* -------------------------------------------------------------------- */
    /*      Deal with codestream PROFILE                                    */
    /* -------------------------------------------------------------------- */
    const char *pszProfile =
        CSLFetchNameValueDef(papszOptions, "PROFILE", "AUTO");
    int bProfile1 = FALSE;
    if (EQUAL(pszProfile, "UNRESTRICTED"))
    {
        bProfile1 = FALSE;
        if (bInspireTG)
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "INSPIRE_TG=YES mandates PROFILE=PROFILE_1 (TG "
                     "requirement 21)");
            return nullptr;
        }
    }
    else if (EQUAL(pszProfile, "UNRESTRICTED_FORCED"))
    {
        bProfile1 = FALSE;
    }
    else if (EQUAL(pszProfile,
                   "PROFILE_1_FORCED")) /* For debug only: can produce
                                           inconsistent codestream */
    {
        bProfile1 = TRUE;
    }
    else
    {
        if (!(EQUAL(pszProfile, "PROFILE_1") || EQUAL(pszProfile, "AUTO")))
        {
            CPLError(CE_Warning, CPLE_NotSupported,
                     "Unsupported value for PROFILE : %s. Defaulting to AUTO",
                     pszProfile);
            pszProfile = "AUTO";
        }

        bProfile1 = TRUE;
        const char *pszReq21OrEmpty = bInspireTG ? " (TG requirement 21)" : "";
        if ((nBlockXSize != nXSize || nBlockYSize != nYSize) &&
            (nBlockXSize != nBlockYSize || nBlockXSize > 1024 ||
             nBlockYSize > 1024))
        {
            bProfile1 = FALSE;
            if (bInspireTG || EQUAL(pszProfile, "PROFILE_1"))
            {
                CPLError(
                    CE_Failure, CPLE_NotSupported,
                    "Tile dimensions incompatible with PROFILE_1%s. "
                    "Should be whole image or square with dimension <= 1024.",
                    pszReq21OrEmpty);
                return nullptr;
            }
        }
        if ((nMaxTileDim >> (nNumResolutions - 1)) > 128)
        {
            bProfile1 = FALSE;
            if (bInspireTG || EQUAL(pszProfile, "PROFILE_1"))
            {
                CPLError(CE_Failure, CPLE_NotSupported,
                         "Number of resolutions incompatible with PROFILE_1%s. "
                         "Should be at least %d.",
                         pszReq21OrEmpty, nMinProfile1Resolutions);
                return nullptr;
            }
        }
        if (nCblockW > 64 || nCblockH > 64)
        {
            bProfile1 = FALSE;
            if (bInspireTG || EQUAL(pszProfile, "PROFILE_1"))
            {
                CPLError(CE_Failure, CPLE_NotSupported,
                         "Codeblock width incompatible with PROFILE_1%s. "
                         "Codeblock width or height should be <= 64.",
                         pszReq21OrEmpty);
                return nullptr;
            }
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Work out the precision.                                         */
    /* -------------------------------------------------------------------- */
    int nBits;
    if (CSLFetchNameValue(papszOptions, "NBITS") != nullptr)
    {
        nBits = atoi(CSLFetchNameValue(papszOptions, "NBITS"));
        if (bInspireTG &&
            !(nBits == 1 || nBits == 8 || nBits == 16 || nBits == 32))
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "INSPIRE_TG=YES mandates NBITS=1,8,16 or 32 (TG "
                     "requirement 24)");
            return nullptr;
        }
    }
    else if (poSrcDS->GetRasterBand(1)->GetMetadataItem(
                 "NBITS", "IMAGE_STRUCTURE") != nullptr)
    {
        nBits = atoi(poSrcDS->GetRasterBand(1)->GetMetadataItem(
            "NBITS", "IMAGE_STRUCTURE"));
        if (bInspireTG &&
            !(nBits == 1 || nBits == 8 || nBits == 16 || nBits == 32))
        {
            /* Implements "NOTE If the original data do not satisfy this "
               "requirement, they will be converted in a representation using "
               "the next higher power of 2" */
            nBits = GDALGetDataTypeSize(eDataType);
        }
    }
    else
    {
        nBits = GDALGetDataTypeSize(eDataType);
    }

    if ((GDALGetDataTypeSize(eDataType) == 8 && nBits > 8) ||
        (GDALGetDataTypeSize(eDataType) == 16 && (nBits <= 8 || nBits > 16)) ||
        (GDALGetDataTypeSize(eDataType) == 32 && (nBits <= 16 || nBits > 32)))
    {
        CPLError(CE_Warning, CPLE_NotSupported,
                 "Inconsistent NBITS value with data type. Using %d",
                 GDALGetDataTypeSize(eDataType));
    }

    /* -------------------------------------------------------------------- */
    /*      Georeferencing options                                          */
    /* -------------------------------------------------------------------- */

    bool bGMLJP2Option = CPLFetchBool(papszOptions, "GMLJP2", true);
    int nGMLJP2Version = 1;
    const char *pszGMLJP2V2Def =
        CSLFetchNameValue(papszOptions, "GMLJP2V2_DEF");
    if (pszGMLJP2V2Def != nullptr)
    {
        bGMLJP2Option = true;
        nGMLJP2Version = 2;
        if (bInspireTG)
        {
            CPLError(CE_Warning, CPLE_NotSupported,
                     "INSPIRE_TG=YES is only compatible with GMLJP2 v1");
            return nullptr;
        }
    }
    const bool bGeoJP2Option = CPLFetchBool(papszOptions, "GeoJP2", true);

    GDALJP2Metadata oJP2MD;

    int bGeoreferencingCompatOfGeoJP2 = FALSE;
    int bGeoreferencingCompatOfGMLJP2 = FALSE;
    if (eCodecFormat == OPJ_CODEC_JP2 && (bGMLJP2Option || bGeoJP2Option))
    {
        if (poSrcDS->GetGCPCount() > 0)
        {
            bGeoreferencingCompatOfGeoJP2 = TRUE;
            oJP2MD.SetGCPs(poSrcDS->GetGCPCount(), poSrcDS->GetGCPs());
            oJP2MD.SetSpatialRef(poSrcDS->GetGCPSpatialRef());
        }
        else
        {
            const OGRSpatialReference *poSRS = poSrcDS->GetSpatialRef();
            if (poSRS != nullptr)
            {
                bGeoreferencingCompatOfGeoJP2 = TRUE;
                oJP2MD.SetSpatialRef(poSRS);
            }
            double adfGeoTransform[6];
            if (poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None)
            {
                bGeoreferencingCompatOfGeoJP2 = TRUE;
                oJP2MD.SetGeoTransform(adfGeoTransform);
            }
            bGeoreferencingCompatOfGMLJP2 =
                poSRS != nullptr && !poSRS->IsEmpty() &&
                poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None;
        }
        if (poSrcDS->GetMetadata("RPC") != nullptr)
        {
            oJP2MD.SetRPCMD(poSrcDS->GetMetadata("RPC"));
            bGeoreferencingCompatOfGeoJP2 = TRUE;
        }

        const char *pszAreaOrPoint =
            poSrcDS->GetMetadataItem(GDALMD_AREA_OR_POINT);
        oJP2MD.bPixelIsPoint = pszAreaOrPoint != nullptr &&
                               EQUAL(pszAreaOrPoint, GDALMD_AOP_POINT);

        if (bGMLJP2Option &&
            CPLGetConfigOption("GMLJP2OVERRIDE", nullptr) != nullptr)
        {
            // Force V1 since this is the branch in which the hack is
            // implemented
            nGMLJP2Version = 1;
            bGeoreferencingCompatOfGMLJP2 = TRUE;
        }
    }

    if (CSLFetchNameValue(papszOptions, "GMLJP2") != nullptr && bGMLJP2Option &&
        !bGeoreferencingCompatOfGMLJP2)
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "GMLJP2 box was explicitly required but cannot be written due "
                 "to lack of georeferencing and/or unsupported georeferencing "
                 "for GMLJP2");
    }

    if (CSLFetchNameValue(papszOptions, "GeoJP2") != nullptr && bGeoJP2Option &&
        !bGeoreferencingCompatOfGeoJP2)
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "GeoJP2 box was explicitly required but cannot be written due "
                 "to lack of georeferencing");
    }
    const bool bGeoBoxesAfter =
        CPLFetchBool(papszOptions, "GEOBOXES_AFTER_JP2C", bInspireTG);
    GDALJP2Box *poGMLJP2Box = nullptr;
    if (eCodecFormat == OPJ_CODEC_JP2 && bGMLJP2Option &&
        bGeoreferencingCompatOfGMLJP2)
    {
        if (nGMLJP2Version == 1)
            poGMLJP2Box = oJP2MD.CreateGMLJP2(nXSize, nYSize);
        else
            poGMLJP2Box =
                oJP2MD.CreateGMLJP2V2(nXSize, nYSize, pszGMLJP2V2Def, poSrcDS);
        if (poGMLJP2Box == nullptr)
            return nullptr;
    }

    /* ---------------------------------------------------------------- */
    /* If the input driver is identifed as "GEORASTER" the following    */
    /* section will try to dump a ORACLE GeoRaster JP2 BLOB into a file */
    /* ---------------------------------------------------------------- */

    if (EQUAL(poSrcDS->GetDriverName(), "GEORASTER"))
    {
        const char *pszGEOR_compress =
            poSrcDS->GetMetadataItem("COMPRESSION", "IMAGE_STRUCTURE");

        if (pszGEOR_compress == nullptr)
        {
            pszGEOR_compress = "NONE";
        }

        /* Check if the JP2 BLOB needs re-shaping */

        bool bGEOR_reshape = false;

        const char *apszIgnoredOptions[] = {"BLOCKXSIZE",
                                            "BLOCKYSIZE",
                                            "QUALITY",
                                            "REVERSIBLE",
                                            "RESOLUTIONS",
                                            "PROGRESSION",
                                            "SOP",
                                            "EPH",
                                            "YCBCR420",
                                            "YCC",
                                            "NBITS",
                                            "1BIT_ALPHA",
                                            "PRECINCTS",
                                            "TILEPARTS",
                                            "CODEBLOCK_WIDTH",
                                            "CODEBLOCK_HEIGHT",
                                            "PLT",
                                            "TLM",
                                            nullptr};

        for (int i = 0; apszIgnoredOptions[i]; i++)
        {
            if (CSLFetchNameValue(papszOptions, apszIgnoredOptions[i]))
            {
                bGEOR_reshape = true;
            }
        }

        if (CSLFetchNameValue(papszOptions, "USE_SRC_CODESTREAM"))
        {
            bGEOR_reshape = false;
        }

        char **papszGEOR_files = poSrcDS->GetFileList();

        if (EQUAL(pszGEOR_compress, "JP2-F") && CSLCount(papszGEOR_files) > 0 &&
            bGEOR_reshape == false)
        {

            const char *pszVsiOciLob = papszGEOR_files[0];

            VSILFILE *fpBlob = VSIFOpenL(pszVsiOciLob, "r");
            if (fpBlob == nullptr)
            {
                CPLError(CE_Failure, CPLE_AppDefined, "Cannot open %s",
                         pszVsiOciLob);
                delete poGMLJP2Box;
                return nullptr;
            }
            VSILFILE *fp = VSIFOpenL(pszFilename, "w+b");
            if (fp == nullptr)
            {
                CPLError(CE_Failure, CPLE_AppDefined, "Cannot create %s",
                         pszFilename);
                delete poGMLJP2Box;
                VSIFCloseL(fpBlob);
                return nullptr;
            }

            VSIFSeekL(fpBlob, 0, SEEK_END);

            size_t nBlobSize = static_cast<size_t>(VSIFTellL(fpBlob));
            size_t nChunk = (size_t)(GDALGetCacheMax() * 0.25);
            size_t nSize = 0;
            size_t nCount = 0;

            void *pBuffer = (GByte *)VSI_MALLOC_VERBOSE(nChunk);
            if (pBuffer == nullptr)
            {
                delete poGMLJP2Box;
                VSIFCloseL(fpBlob);
                VSIFCloseL(fp);
                return nullptr;
            }

            VSIFSeekL(fpBlob, 0, SEEK_SET);

            while ((nSize = VSIFReadL(pBuffer, 1, nChunk, fpBlob)) > 0)
            {
                VSIFWriteL(pBuffer, 1, nSize, fp);
                nCount += nSize;
                pfnProgress((float)nCount / (float)nBlobSize, nullptr,
                            pProgressData);
            }

            CPLFree(pBuffer);
            VSIFCloseL(fpBlob);

            VSIFCloseL(fp);

            /* Return the GDALDaset object */

            GDALOpenInfo oOpenInfo(pszFilename, GA_Update);
            GDALDataset *poDS = JP2OpenJPEGDataset::Open(&oOpenInfo);

            /* Copy essential metadata */

            double adfGeoTransform[6];

            if (poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None)
            {
                poDS->SetGeoTransform(adfGeoTransform);
            }

            const OGRSpatialReference *poSRS = poSrcDS->GetSpatialRef();
            if (poSRS)
            {
                poDS->SetSpatialRef(poSRS);
            }

            delete poGMLJP2Box;
            return poDS;
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Setup encoder                                                  */
    /* -------------------------------------------------------------------- */

    opj_cparameters_t parameters;
    opj_set_default_encoder_parameters(&parameters);
    if (bSOP)
        parameters.csty |= 0x02;
    if (bEPH)
        parameters.csty |= 0x04;
    parameters.cp_disto_alloc = 1;
    parameters.tcp_numlayers = (int)adfRates.size();
    for (int i = 0; i < (int)adfRates.size(); i++)
        parameters.tcp_rates[i] = (float)adfRates[i];
    parameters.cp_tx0 = 0;
    parameters.cp_ty0 = 0;
    parameters.tile_size_on = TRUE;
    parameters.cp_tdx = nBlockXSize;
    parameters.cp_tdy = nBlockYSize;
    parameters.irreversible = bIsIrreversible;
    parameters.numresolution = nNumResolutions;
    parameters.prog_order = eProgOrder;
    parameters.tcp_mct = static_cast<char>(bYCC);
    parameters.cblockw_init = nCblockW;
    parameters.cblockh_init = nCblockH;
    parameters.mode = 0;

    std::string osComment;
    const char *pszCOM = CSLFetchNameValue(papszOptions, "COMMENT");
    if (pszCOM)
    {
        osComment = pszCOM;
        parameters.cp_comment = &osComment[0];
    }
    else if (!bIsIrreversible)
    {
        osComment = "Created by OpenJPEG version ";
        osComment += opj_version();
        if (adfRates.back() == 1.0 && !bYCBCR420)
        {
            osComment += ". LOSSLESS settings used";
        }
        else
        {
            osComment += ". LOSSY settings used";
        }
        parameters.cp_comment = &osComment[0];
    }

#if IS_OPENJPEG_OR_LATER(2, 3, 0)
    // Was buggy before for some of the options
    const char *pszCodeBlockStyle =
        CSLFetchNameValue(papszOptions, "CODEBLOCK_STYLE");
    if (pszCodeBlockStyle)
    {
        if (CPLGetValueType(pszCodeBlockStyle) == CPL_VALUE_INTEGER)
        {
            int nVal = atoi(pszCodeBlockStyle);
            if (nVal >= 0 && nVal <= 63)
            {
                parameters.mode = nVal;
            }
            else
            {
                CPLError(CE_Warning, CPLE_NotSupported,
                         "Invalid value for CODEBLOCK_STYLE: %s. "
                         "Should be >= 0 and <= 63",
                         pszCodeBlockStyle);
            }
        }
        else
        {
            char **papszTokens = CSLTokenizeString2(pszCodeBlockStyle, ", ", 0);
            for (char **papszIter = papszTokens; papszIter && *papszIter;
                 ++papszIter)
            {
                if (EQUAL(*papszIter, "BYPASS"))
                {
                    parameters.mode |= (1 << 0);
                }
                else if (EQUAL(*papszIter, "RESET"))
                {
                    parameters.mode |= (1 << 1);
                }
                else if (EQUAL(*papszIter, "TERMALL"))
                {
                    parameters.mode |= (1 << 2);
                }
                else if (EQUAL(*papszIter, "VSC"))
                {
                    parameters.mode |= (1 << 3);
                }
                else if (EQUAL(*papszIter, "PREDICTABLE"))
                {
                    parameters.mode |= (1 << 4);
                }
                else if (EQUAL(*papszIter, "SEGSYM"))
                {
                    parameters.mode |= (1 << 5);
                }
                else
                {
                    CPLError(CE_Warning, CPLE_NotSupported,
                             "Unrecognized option for CODEBLOCK_STYLE: %s",
                             *papszIter);
                }
            }
            CSLDestroy(papszTokens);
        }
    }
#endif

    /* Add precincts */
    const char *pszPrecincts =
        CSLFetchNameValueDef(papszOptions, "PRECINCTS",
                             "{512,512},{256,512},{128,512},{64,512},{32,512},{"
                             "16,512},{8,512},{4,512},{2,512}");
    char **papszTokens =
        CSLTokenizeStringComplex(pszPrecincts, "{},", FALSE, FALSE);
    int nPrecincts = CSLCount(papszTokens) / 2;
    for (int i = 0; i < nPrecincts && i < OPJ_J2K_MAXRLVLS; i++)
    {
        int nPCRW = atoi(papszTokens[2 * i]);
        int nPCRH = atoi(papszTokens[2 * i + 1]);
        if (nPCRW < 1 || nPCRH < 1)
            break;
        parameters.csty |= 0x01;
        parameters.res_spec++;
        parameters.prcw_init[i] = nPCRW;
        parameters.prch_init[i] = nPCRH;
    }
    CSLDestroy(papszTokens);

    /* Add tileparts setting */
    const char *pszTileParts =
        CSLFetchNameValueDef(papszOptions, "TILEPARTS", "DISABLED");
    if (EQUAL(pszTileParts, "RESOLUTIONS"))
    {
        parameters.tp_on = 1;
        parameters.tp_flag = 'R';
    }
    else if (EQUAL(pszTileParts, "LAYERS"))
    {
        if (parameters.tcp_numlayers == 1)
        {
            CPLError(CE_Warning, CPLE_AppDefined,
                     "TILEPARTS=LAYERS has no real interest with single-layer "
                     "codestream");
        }
        parameters.tp_on = 1;
        parameters.tp_flag = 'L';
    }
    else if (EQUAL(pszTileParts, "COMPONENTS"))
    {
        parameters.tp_on = 1;
        parameters.tp_flag = 'C';
    }
    else if (!EQUAL(pszTileParts, "DISABLED"))
    {
        CPLError(CE_Warning, CPLE_NotSupported, "Invalid value for TILEPARTS");
    }

    if (bProfile1)
    {
        parameters.rsiz = OPJ_PROFILE_1;
    }

    opj_image_cmptparm_t *pasBandParams = (opj_image_cmptparm_t *)CPLMalloc(
        nBands * sizeof(opj_image_cmptparm_t));
    int iBand;
    int bSamePrecision = TRUE;
    int b1BitAlpha = FALSE;
    for (iBand = 0; iBand < nBands; iBand++)
    {
        pasBandParams[iBand].x0 = 0;
        pasBandParams[iBand].y0 = 0;
        if (bYCBCR420 && (iBand == 1 || iBand == 2))
        {
            pasBandParams[iBand].dx = 2;
            pasBandParams[iBand].dy = 2;
            pasBandParams[iBand].w = nXSize / 2;
            pasBandParams[iBand].h = nYSize / 2;
        }
        else
        {
            pasBandParams[iBand].dx = 1;
            pasBandParams[iBand].dy = 1;
            pasBandParams[iBand].w = nXSize;
            pasBandParams[iBand].h = nYSize;
        }

        pasBandParams[iBand].sgnd =
            (eDataType == GDT_Int16 || eDataType == GDT_Int32);
        pasBandParams[iBand].prec = nBits;

        const char *pszNBits =
            poSrcDS->GetRasterBand(iBand + 1)->GetMetadataItem(
                "NBITS", "IMAGE_STRUCTURE");
        /* Recommendation 38 In the case of an opacity channel, the bit depth
         * should be 1-bit. */
        if (iBand == nAlphaBandIndex &&
            ((pszNBits != nullptr && EQUAL(pszNBits, "1")) ||
             CPLFetchBool(papszOptions, "1BIT_ALPHA", bInspireTG)))
        {
            if (iBand != nBands - 1 && nBits != 1)
            {
                /* Might be a bug in openjpeg, but it seems that if the alpha */
                /* band is the first one, it would select 1-bit for all
                 * channels... */
                CPLError(CE_Warning, CPLE_NotSupported,
                         "Cannot output 1-bit alpha channel if it is not the "
                         "last one");
            }
            else
            {
                CPLDebug("OPENJPEG", "Using 1-bit alpha channel");
                pasBandParams[iBand].sgnd = 0;
                pasBandParams[iBand].prec = 1;
                bSamePrecision = FALSE;
                b1BitAlpha = TRUE;
            }
        }
    }

    if (bInspireTG && nAlphaBandIndex >= 0 && !b1BitAlpha)
    {
        CPLError(
            CE_Warning, CPLE_NotSupported,
            "INSPIRE_TG=YES recommends 1BIT_ALPHA=YES (Recommendation 38)");
    }

    /* Always ask OpenJPEG to do codestream only. We will take care */
    /* of JP2 boxes */
    opj_codec_t *pCodec = opj_create_compress(OPJ_CODEC_J2K);
    if (pCodec == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "opj_create_compress() failed");
        CPLFree(pasBandParams);
        delete poGMLJP2Box;
        return nullptr;
    }

    opj_set_info_handler(pCodec, JP2OpenJPEGDataset_InfoCallback, nullptr);
    opj_set_warning_handler(pCodec, JP2OpenJPEGDataset_WarningCallback,
                            nullptr);
    opj_set_error_handler(pCodec, JP2OpenJPEGDataset_ErrorCallback, nullptr);

    OPJ_COLOR_SPACE eColorSpace = OPJ_CLRSPC_GRAY;

    if (bYCBCR420)
    {
        eColorSpace = OPJ_CLRSPC_SYCC;
    }
    else if ((nBands == 3 || nBands == 4) && nRedBandIndex >= 0 &&
             nGreenBandIndex >= 0 && nBlueBandIndex >= 0)
    {
        eColorSpace = OPJ_CLRSPC_SRGB;
    }
    else if (poCT != nullptr)
    {
        eColorSpace = OPJ_CLRSPC_SRGB;
    }

    opj_image_t *psImage =
        opj_image_tile_create(nBands, pasBandParams, eColorSpace);

    if (psImage == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "opj_image_tile_create() failed");
        opj_destroy_codec(pCodec);
        CPLFree(pasBandParams);
        pasBandParams = nullptr;
        delete poGMLJP2Box;
        return nullptr;
    }

    psImage->x0 = 0;
    psImage->y0 = 0;
    psImage->x1 = nXSize;
    psImage->y1 = nYSize;
    psImage->color_space = eColorSpace;
    psImage->numcomps = nBands;

    if (!opj_setup_encoder(pCodec, &parameters, psImage))
    {
        CPLError(CE_Failure, CPLE_AppDefined, "opj_setup_encoder() failed");
        opj_image_destroy(psImage);
        opj_destroy_codec(pCodec);
        CPLFree(pasBandParams);
        pasBandParams = nullptr;
        delete poGMLJP2Box;
        return nullptr;
    }

#if IS_OPENJPEG_OR_LATER(2, 4, 0)

    if (getenv("OPJ_NUM_THREADS") == nullptr)
    {
        JP2OpenJPEGDataset oTmpDS;
        opj_codec_set_threads(pCodec, oTmpDS.GetNumThreads());
    }

    CPLStringList aosOptions;
    if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "PLT", "FALSE")))
    {
        aosOptions.AddString("PLT=YES");
    }

#if IS_OPENJPEG_OR_LATER(2, 5, 0)
    if (CPLTestBool(CSLFetchNameValueDef(papszOptions, "TLM", "FALSE")))
    {
        aosOptions.AddString("TLM=YES");
    }
#endif

    if (!opj_encoder_set_extra_options(pCodec, aosOptions.List()))
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "opj_encoder_set_extra_options() failed");
        opj_image_destroy(psImage);
        opj_destroy_codec(pCodec);
        CPLFree(pasBandParams);
        pasBandParams = nullptr;
        delete poGMLJP2Box;
        return nullptr;
    }
#endif

    /* -------------------------------------------------------------------- */
    /*      Create the dataset.                                             */
    /* -------------------------------------------------------------------- */

    const char *pszAccess =
        STARTS_WITH_CI(pszFilename, "/vsisubfile/") ? "r+b" : "w+b";
    VSILFILE *fp = VSIFOpenL(pszFilename, pszAccess);
    if (fp == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot create file");
        opj_image_destroy(psImage);
        opj_destroy_codec(pCodec);
        CPLFree(pasBandParams);
        pasBandParams = nullptr;
        delete poGMLJP2Box;
        return nullptr;
    }

    /* -------------------------------------------------------------------- */
    /*      Add JP2 boxes.                                                  */
    /* -------------------------------------------------------------------- */
    vsi_l_offset nStartJP2C = 0;
    int bUseXLBoxes = FALSE;

    if (eCodecFormat == OPJ_CODEC_JP2)
    {
        GDALJP2Box jPBox(fp);
        jPBox.SetType("jP  ");
        jPBox.AppendWritableData(4, "\x0D\x0A\x87\x0A");
        WriteBox(fp, &jPBox);

        GDALJP2Box ftypBox(fp);
        ftypBox.SetType("ftyp");
        // http://docs.opengeospatial.org/is/08-085r5/08-085r5.html Req 19
        const bool bJPXOption = CPLFetchBool(papszOptions, "JPX", true);
        if (nGMLJP2Version == 2 && bJPXOption)
            ftypBox.AppendWritableData(4, "jpx "); /* Branding */
        else
            ftypBox.AppendWritableData(4, "jp2 "); /* Branding */
        ftypBox.AppendUInt32(0);                   /* minimum version */
        ftypBox.AppendWritableData(
            4, "jp2 "); /* Compatibility list: first value */

        if (bInspireTG && poGMLJP2Box != nullptr && !bJPXOption)
        {
            CPLError(
                CE_Warning, CPLE_AppDefined,
                "INSPIRE_TG=YES implies following GMLJP2 specification which "
                "recommends advertise reader requirement 67 feature, and thus "
                "JPX capability");
        }
        else if (poGMLJP2Box != nullptr && bJPXOption)
        {
            /* GMLJP2 uses lbl and asoc boxes, which are JPEG2000 Part II spec
             */
            /* advertizing jpx is required per 8.1 of 05-047r3 GMLJP2 */
            ftypBox.AppendWritableData(
                4, "jpx "); /* Compatibility list: second value */
        }
        WriteBox(fp, &ftypBox);

        const bool bIPR = poSrcDS->GetMetadata("xml:IPR") != nullptr &&
                          CPLFetchBool(papszOptions, "WRITE_METADATA", false);

        /* Reader requirement box */
        if (poGMLJP2Box != nullptr && bJPXOption)
        {
            GDALJP2Box rreqBox(fp);
            rreqBox.SetType("rreq");
            rreqBox.AppendUInt8(1); /* ML = 1 byte for mask length */

            rreqBox.AppendUInt8(0x80 | 0x40 | (bIPR ? 0x20 : 0)); /* FUAM */
            rreqBox.AppendUInt8(0x80);                            /* DCM */

            rreqBox.AppendUInt16(
                2 + (bIPR ? 1 : 0)); /* NSF: Number of standard features */

            rreqBox.AppendUInt16(
                (bProfile1) ? 4 : 5);  /* SF0 : PROFILE 1 or PROFILE 2 */
            rreqBox.AppendUInt8(0x80); /* SM0 */

            rreqBox.AppendUInt16(67);  /* SF1 : GMLJP2 box */
            rreqBox.AppendUInt8(0x40); /* SM1 */

            if (bIPR)
            {
                rreqBox.AppendUInt16(35);  /* SF2 : IPR metadata */
                rreqBox.AppendUInt8(0x20); /* SM2 */
            }
            rreqBox.AppendUInt16(0); /* NVF */
            WriteBox(fp, &rreqBox);
        }

        GDALJP2Box ihdrBox(fp);
        ihdrBox.SetType("ihdr");
        ihdrBox.AppendUInt32(nYSize);
        ihdrBox.AppendUInt32(nXSize);
        ihdrBox.AppendUInt16(static_cast<GUInt16>(nBands));
        GByte BPC;
        if (bSamePrecision)
            BPC = static_cast<GByte>((pasBandParams[0].prec - 1) |
                                     (pasBandParams[0].sgnd << 7));
        else
            BPC = 255;
        ihdrBox.AppendUInt8(BPC);
        ihdrBox.AppendUInt8(7); /* C=Compression type: fixed value */
        ihdrBox.AppendUInt8(0); /* UnkC: 0= colourspace of the image is known */
        /*and correctly specified in the Colourspace Specification boxes within
         * the file */
        ihdrBox.AppendUInt8(
            bIPR ? 1 : 0); /* IPR: 0=no intellectual property, 1=IPR box */

        GDALJP2Box bpccBox(fp);
        if (!bSamePrecision)
        {
            bpccBox.SetType("bpcc");
            for (int i = 0; i < nBands; i++)
                bpccBox.AppendUInt8(
                    static_cast<GByte>((pasBandParams[i].prec - 1) |
                                       (pasBandParams[i].sgnd << 7)));
        }

        GDALJP2Box colrBox(fp);
        colrBox.SetType("colr");
        colrBox.AppendUInt8(1); /* METHOD: 1=Enumerated Colourspace */
        colrBox.AppendUInt8(
            0); /* PREC: Precedence. 0=(field reserved for ISO use) */
        colrBox.AppendUInt8(0); /* APPROX: Colourspace approximation. */
        GUInt32 enumcs = 16;
        if (eColorSpace == OPJ_CLRSPC_SRGB)
            enumcs = 16;
        else if (eColorSpace == OPJ_CLRSPC_GRAY)
            enumcs = 17;
        else if (eColorSpace == OPJ_CLRSPC_SYCC)
            enumcs = 18;
        colrBox.AppendUInt32(enumcs); /* EnumCS: Enumerated colourspace */

        GDALJP2Box pclrBox(fp);
        GDALJP2Box cmapBox(fp);
        int nCTComponentCount = 0;
        if (poCT != nullptr)
        {
            pclrBox.SetType("pclr");
            const int nEntries = std::min(256, poCT->GetColorEntryCount());
            nCTComponentCount =
                atoi(CSLFetchNameValueDef(papszOptions, "CT_COMPONENTS", "0"));
            if (bInspireTG)
            {
                if (nCTComponentCount != 0 && nCTComponentCount != 3)
                    CPLError(
                        CE_Warning, CPLE_AppDefined,
                        "Inspire TG mandates 3 components for color table");
                else
                    nCTComponentCount = 3;
            }
            else if (nCTComponentCount != 3 && nCTComponentCount != 4)
            {
                nCTComponentCount = 3;
                for (int i = 0; i < nEntries; i++)
                {
                    const GDALColorEntry *psEntry = poCT->GetColorEntry(i);
                    if (psEntry->c4 != 255)
                    {
                        CPLDebug(
                            "OPENJPEG",
                            "Color table has at least one non-opaque value. "
                            "This may cause compatibility problems with some "
                            "readers. "
                            "In which case use CT_COMPONENTS=3 creation "
                            "option");
                        nCTComponentCount = 4;
                        break;
                    }
                }
            }
            nRedBandIndex = 0;
            nGreenBandIndex = 1;
            nBlueBandIndex = 2;
            nAlphaBandIndex = (nCTComponentCount == 4) ? 3 : -1;

            pclrBox.AppendUInt16(static_cast<GUInt16>(nEntries));
            pclrBox.AppendUInt8(static_cast<GByte>(
                nCTComponentCount)); /* NPC: Number of components */
            for (int i = 0; i < nCTComponentCount; i++)
            {
                pclrBox.AppendUInt8(7); /* Bi: unsigned 8 bits */
            }
            for (int i = 0; i < nEntries; i++)
            {
                const GDALColorEntry *psEntry = poCT->GetColorEntry(i);
                pclrBox.AppendUInt8((GByte)psEntry->c1);
                pclrBox.AppendUInt8((GByte)psEntry->c2);
                pclrBox.AppendUInt8((GByte)psEntry->c3);
                if (nCTComponentCount == 4)
                    pclrBox.AppendUInt8((GByte)psEntry->c4);
            }

            cmapBox.SetType("cmap");
            for (int i = 0; i < nCTComponentCount; i++)
            {
                cmapBox.AppendUInt16(0); /* CMPi: code stream component index */
                cmapBox.AppendUInt8(1);  /* MYTPi: 1=palette mapping */
                cmapBox.AppendUInt8(static_cast<GByte>(
                    i)); /* PCOLi: index component from the map */
            }
        }

        GDALJP2Box cdefBox(fp);
        if (((nBands == 3 || nBands == 4) &&
             (eColorSpace == OPJ_CLRSPC_SRGB ||
              eColorSpace == OPJ_CLRSPC_SYCC) &&
             (nRedBandIndex != 0 || nGreenBandIndex != 1 ||
              nBlueBandIndex != 2)) ||
            nAlphaBandIndex >= 0)
        {
            cdefBox.SetType("cdef");
            int nComponents = (nCTComponentCount == 4) ? 4 : nBands;
            cdefBox.AppendUInt16(static_cast<GUInt16>(nComponents));
            for (int i = 0; i < nComponents; i++)
            {
                cdefBox.AppendUInt16(
                    static_cast<GUInt16>(i)); /* Component number */
                if (i != nAlphaBandIndex)
                {
                    cdefBox.AppendUInt16(
                        0); /* Signification: This channel is the colour image
                               data for the associated colour */
                    if (eColorSpace == OPJ_CLRSPC_GRAY && nComponents == 2)
                        cdefBox.AppendUInt16(
                            1); /* Colour of the component: associated with a
                                   particular colour */
                    else if ((eColorSpace == OPJ_CLRSPC_SRGB ||
                              eColorSpace == OPJ_CLRSPC_SYCC) &&
                             (nComponents == 3 || nComponents == 4))
                    {
                        if (i == nRedBandIndex)
                            cdefBox.AppendUInt16(1);
                        else if (i == nGreenBandIndex)
                            cdefBox.AppendUInt16(2);
                        else if (i == nBlueBandIndex)
                            cdefBox.AppendUInt16(3);
                        else
                        {
                            CPLError(CE_Warning, CPLE_AppDefined,
                                     "Could not associate band %d with a "
                                     "red/green/blue channel",
                                     i + 1);
                            cdefBox.AppendUInt16(65535);
                        }
                    }
                    else
                        cdefBox.AppendUInt16(
                            65535); /* Colour of the component: not associated
                                       with any particular colour */
                }
                else
                {
                    cdefBox.AppendUInt16(
                        1); /* Signification: Non pre-multiplied alpha */
                    cdefBox.AppendUInt16(
                        0); /* Colour of the component: This channel is
                               associated as the image as a whole */
                }
            }
        }

        // Add res box if needed
        GDALJP2Box *poRes = nullptr;
        if (poSrcDS->GetMetadataItem("TIFFTAG_XRESOLUTION") != nullptr &&
            poSrcDS->GetMetadataItem("TIFFTAG_YRESOLUTION") != nullptr &&
            poSrcDS->GetMetadataItem("TIFFTAG_RESOLUTIONUNIT") != nullptr)
        {
            double dfXRes =
                CPLAtof(poSrcDS->GetMetadataItem("TIFFTAG_XRESOLUTION"));
            double dfYRes =
                CPLAtof(poSrcDS->GetMetadataItem("TIFFTAG_YRESOLUTION"));
            int nResUnit =
                atoi(poSrcDS->GetMetadataItem("TIFFTAG_RESOLUTIONUNIT"));
#define PIXELS_PER_INCH 2
#define PIXELS_PER_CM 3

            if (nResUnit == PIXELS_PER_INCH)
            {
                // convert pixels per inch to pixels per cm.
                dfXRes = dfXRes * 39.37 / 100.0;
                dfYRes = dfYRes * 39.37 / 100.0;
                nResUnit = PIXELS_PER_CM;
            }

            if (nResUnit == PIXELS_PER_CM && dfXRes > 0 && dfYRes > 0 &&
                dfXRes < 65535 && dfYRes < 65535)
            {
                /* Format a resd box and embed it inside a res box */
                GDALJP2Box oResd;
                oResd.SetType("resd");

                int nYDenom = 1;
                while (nYDenom < 32767 && dfYRes < 32767)
                {
                    dfYRes *= 2;
                    nYDenom *= 2;
                }
                int nXDenom = 1;
                while (nXDenom < 32767 && dfXRes < 32767)
                {
                    dfXRes *= 2;
                    nXDenom *= 2;
                }

                oResd.AppendUInt16((GUInt16)dfYRes);
                oResd.AppendUInt16((GUInt16)nYDenom);
                oResd.AppendUInt16((GUInt16)dfXRes);
                oResd.AppendUInt16((GUInt16)nXDenom);
                oResd.AppendUInt8(2); /* vertical exponent */
                oResd.AppendUInt8(2); /* horizontal exponent */

                GDALJP2Box *poResd = &oResd;
                poRes = GDALJP2Box::CreateAsocBox(1, &poResd);
                poRes->SetType("res ");
            }
        }

        /* Build and write jp2h super box now */
        GDALJP2Box *apoBoxes[7];
        int nBoxes = 1;
        apoBoxes[0] = &ihdrBox;
        if (bpccBox.GetDataLength())
            apoBoxes[nBoxes++] = &bpccBox;
        apoBoxes[nBoxes++] = &colrBox;
        if (pclrBox.GetDataLength())
            apoBoxes[nBoxes++] = &pclrBox;
        if (cmapBox.GetDataLength())
            apoBoxes[nBoxes++] = &cmapBox;
        if (cdefBox.GetDataLength())
            apoBoxes[nBoxes++] = &cdefBox;
        if (poRes)
            apoBoxes[nBoxes++] = poRes;
        GDALJP2Box *psJP2HBox =
            GDALJP2Box::CreateSuperBox("jp2h", nBoxes, apoBoxes);
        WriteBox(fp, psJP2HBox);
        delete psJP2HBox;
        delete poRes;

        if (!bGeoBoxesAfter)
        {
            if (bGeoJP2Option && bGeoreferencingCompatOfGeoJP2)
            {
                GDALJP2Box *poBox = oJP2MD.CreateJP2GeoTIFF();
                WriteBox(fp, poBox);
                delete poBox;
            }

            if (CPLFetchBool(papszOptions, "WRITE_METADATA", false) &&
                !CPLFetchBool(papszOptions, "MAIN_MD_DOMAIN_ONLY", false))
            {
                WriteXMPBox(fp, poSrcDS, papszOptions);
            }

            if (CPLFetchBool(papszOptions, "WRITE_METADATA", false))
            {
                if (!CPLFetchBool(papszOptions, "MAIN_MD_DOMAIN_ONLY", false))
                    WriteXMLBoxes(fp, poSrcDS, papszOptions);
                WriteGDALMetadataBox(fp, poSrcDS, papszOptions);
            }

            if (poGMLJP2Box != nullptr)
            {
                WriteBox(fp, poGMLJP2Box);
            }
        }
    }
    CPLFree(pasBandParams);
    pasBandParams = nullptr;

    /* -------------------------------------------------------------------- */
    /*      Try lossless reuse of an existing JPEG2000 codestream           */
    /* -------------------------------------------------------------------- */
    vsi_l_offset nCodeStreamLength = 0;
    vsi_l_offset nCodeStreamStart = 0;
    VSILFILE *fpSrc = nullptr;
    if (CPLFetchBool(papszOptions, "USE_SRC_CODESTREAM", false))
    {
        CPLString osSrcFilename(poSrcDS->GetDescription());
        if (poSrcDS->GetDriver() != nullptr &&
            poSrcDS->GetDriver() == GDALGetDriverByName("VRT"))
        {
            VRTDataset *poVRTDS = (VRTDataset *)poSrcDS;
            GDALDataset *poSimpleSourceDS = poVRTDS->GetSingleSimpleSource();
            if (poSimpleSourceDS)
                osSrcFilename = poSimpleSourceDS->GetDescription();
        }

        fpSrc = VSIFOpenL(osSrcFilename, "rb");
        if (fpSrc)
        {
            nCodeStreamStart =
                JP2OpenJPEGFindCodeStream(fpSrc, &nCodeStreamLength);
        }
        if (nCodeStreamLength == 0)
        {
            CPLError(
                CE_Warning, CPLE_AppDefined,
                "USE_SRC_CODESTREAM=YES specified, but no codestream found");
        }
    }

    if (eCodecFormat == OPJ_CODEC_JP2)
    {
        // Start codestream box
        nStartJP2C = VSIFTellL(fp);
        if (nCodeStreamLength)
            bUseXLBoxes =
                ((vsi_l_offset)(GUInt32)nCodeStreamLength != nCodeStreamLength);
        else
            bUseXLBoxes = CPLFetchBool(papszOptions, "JP2C_XLBOX",
                                       false) || /* For debugging */
                          (GIntBig)nXSize * nYSize * nBands * nDataTypeSize /
                                  adfRates.back() >
                              4e9;
        GUInt32 nLBox = (bUseXLBoxes) ? 1 : 0;
        CPL_MSBPTR32(&nLBox);
        VSIFWriteL(&nLBox, 1, 4, fp);
        VSIFWriteL("jp2c", 1, 4, fp);
        if (bUseXLBoxes)
        {
            GUIntBig nXLBox = 0;
            VSIFWriteL(&nXLBox, 1, 8, fp);
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Do lossless reuse of an existing JPEG2000 codestream            */
    /* -------------------------------------------------------------------- */
    if (fpSrc)
    {
        const char *apszIgnoredOptions[] = {"BLOCKXSIZE",
                                            "BLOCKYSIZE",
                                            "QUALITY",
                                            "REVERSIBLE",
                                            "RESOLUTIONS",
                                            "PROGRESSION",
                                            "SOP",
                                            "EPH",
                                            "YCBCR420",
                                            "YCC",
                                            "NBITS",
                                            "1BIT_ALPHA",
                                            "PRECINCTS",
                                            "TILEPARTS",
                                            "CODEBLOCK_WIDTH",
                                            "CODEBLOCK_HEIGHT",
                                            "PLT",
                                            nullptr};
        for (int i = 0; apszIgnoredOptions[i]; i++)
        {
            if (CSLFetchNameValue(papszOptions, apszIgnoredOptions[i]))
            {
                CPLError(CE_Warning, CPLE_NotSupported,
                         "Option %s ignored when USE_SRC_CODESTREAM=YES",
                         apszIgnoredOptions[i]);
            }
        }
        GByte abyBuffer[4096];
        VSIFSeekL(fpSrc, nCodeStreamStart, SEEK_SET);
        vsi_l_offset nRead = 0;
        /* coverity[tainted_data] */
        while (nRead < nCodeStreamLength)
        {
            int nToRead = (nCodeStreamLength - nRead > 4096)
                              ? 4096
                              : (int)(nCodeStreamLength - nRead);
            if ((int)VSIFReadL(abyBuffer, 1, nToRead, fpSrc) != nToRead)
            {
                VSIFCloseL(fp);
                VSIFCloseL(fpSrc);
                opj_image_destroy(psImage);
                opj_destroy_codec(pCodec);
                delete poGMLJP2Box;
                return nullptr;
            }
            if (nRead == 0 && (pszProfile || bInspireTG) &&
                abyBuffer[2] == 0xFF && abyBuffer[3] == 0x51)
            {
                if (EQUAL(pszProfile, "UNRESTRICTED"))
                {
                    abyBuffer[6] = 0;
                    abyBuffer[7] = 0;
                }
                else if (EQUAL(pszProfile, "PROFILE_1") || bInspireTG)
                {
                    // TODO: ultimately we should check that we can really set
                    // Profile 1
                    abyBuffer[6] = 0;
                    abyBuffer[7] = 2;
                }
            }
            if ((int)VSIFWriteL(abyBuffer, 1, nToRead, fp) != nToRead ||
                !pfnProgress((nRead + nToRead) * 1.0 / nCodeStreamLength,
                             nullptr, pProgressData))
            {
                VSIFCloseL(fp);
                VSIFCloseL(fpSrc);
                opj_image_destroy(psImage);
                opj_destroy_codec(pCodec);
                delete poGMLJP2Box;
                return nullptr;
            }
            nRead += nToRead;
        }

        VSIFCloseL(fpSrc);
    }
    else
    {
        JP2OpenJPEGFile sJP2OpenJPEGFile;
        sJP2OpenJPEGFile.fp = fp;
        sJP2OpenJPEGFile.nBaseOffset = VSIFTellL(fp);
        opj_stream_t *pStream = opj_stream_create(1024 * 1024, FALSE);
        opj_stream_set_write_function(pStream, JP2OpenJPEGDataset_Write);
        opj_stream_set_seek_function(pStream, JP2OpenJPEGDataset_Seek);
        opj_stream_set_skip_function(pStream, JP2OpenJPEGDataset_Skip);
        opj_stream_set_user_data(pStream, &sJP2OpenJPEGFile, nullptr);

        if (!opj_start_compress(pCodec, psImage, pStream))
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "opj_start_compress() failed");
            opj_stream_destroy(pStream);
            opj_image_destroy(psImage);
            opj_destroy_codec(pCodec);
            VSIFCloseL(fp);
            delete poGMLJP2Box;
            return nullptr;
        }

        const int nTilesX = DIV_ROUND_UP(nXSize, nBlockXSize);
        const int nTilesY = DIV_ROUND_UP(nYSize, nBlockYSize);

        const GUIntBig nTileSize =
            (GUIntBig)nBlockXSize * nBlockYSize * nBands * nDataTypeSize;
        GByte *pTempBuffer = nullptr;

        const bool bUseIOThread =
            (nTilesX > 1 || nTilesY > 1) && nTileSize < 10 * 1024 * 1024 &&
            strcmp(CPLGetThreadingModel(), "stub") != 0 &&
            CPLTestBool(
                CPLGetConfigOption("JP2OPENJPEG_USE_THREADED_IO", "YES"));

        if (nTileSize > UINT_MAX)
        {
            CPLError(CE_Failure, CPLE_NotSupported, "Tile size exceeds 4GB");
            pTempBuffer = nullptr;
        }
        else
        {
            // Double memory buffer when using threaded I/O
            const size_t nBufferSize =
                static_cast<size_t>(bUseIOThread ? nTileSize * 2 : nTileSize);
            pTempBuffer = (GByte *)VSIMalloc(nBufferSize);
        }
        if (pTempBuffer == nullptr)
        {
            opj_stream_destroy(pStream);
            opj_image_destroy(psImage);
            opj_destroy_codec(pCodec);
            VSIFCloseL(fp);
            delete poGMLJP2Box;
            return nullptr;
        }

        GByte *pYUV420Buffer = nullptr;
        if (bYCBCR420)
        {
            pYUV420Buffer = (GByte *)VSIMalloc(
                3 * nBlockXSize * nBlockYSize / 2 +
                ((nBands == 4) ? nBlockXSize * nBlockYSize : 0));
            if (pYUV420Buffer == nullptr)
            {
                opj_stream_destroy(pStream);
                opj_image_destroy(psImage);
                opj_destroy_codec(pCodec);
                CPLFree(pTempBuffer);
                VSIFCloseL(fp);
                delete poGMLJP2Box;
                return nullptr;
            }
        }

        /* --------------------------------------------------------------------
         */
        /*      Iterate over the tiles */
        /* --------------------------------------------------------------------
         */
        pfnProgress(0.0, nullptr, pProgressData);

        struct ReadRasterJob
        {
            GDALDataset *poSrcDS;
            int nXOff;
            int nYOff;
            int nWidthToRead;
            int nHeightToRead;
            GDALDataType eDataType;
            GByte *pBuffer;
            int nBands;
            CPLErr eErr;
        };

        const auto ReadRasterFunction = [](void *threadData)
        {
            ReadRasterJob *job = static_cast<ReadRasterJob *>(threadData);
            job->eErr = job->poSrcDS->RasterIO(
                GF_Read, job->nXOff, job->nYOff, job->nWidthToRead,
                job->nHeightToRead, job->pBuffer, job->nWidthToRead,
                job->nHeightToRead, job->eDataType, job->nBands, nullptr, 0, 0,
                0, nullptr);
        };

        CPLWorkerThreadPool oPool;
        if (bUseIOThread)
        {
            oPool.Setup(1, nullptr, nullptr);
        }

        GByte *pabyActiveBuffer = pTempBuffer;
        GByte *pabyBackgroundBuffer =
            pTempBuffer + static_cast<size_t>(nTileSize);

        CPLErr eErr = CE_None;
        int iTile = 0;

        ReadRasterJob job;
        job.eDataType = eDataType;
        job.pBuffer = pabyActiveBuffer;
        job.nBands = nBands;
        job.eErr = CE_Failure;
        job.poSrcDS = poSrcDS;

        if (bUseIOThread)
        {
            job.nXOff = 0;
            job.nYOff = 0;
            job.nWidthToRead = std::min(nBlockXSize, nXSize);
            job.nHeightToRead = std::min(nBlockYSize, nYSize);
            job.pBuffer = pabyBackgroundBuffer;
            ReadRasterFunction(&job);
            eErr = job.eErr;
        }

        for (int nBlockYOff = 0; eErr == CE_None && nBlockYOff < nTilesY;
             nBlockYOff++)
        {
            for (int nBlockXOff = 0; eErr == CE_None && nBlockXOff < nTilesX;
                 nBlockXOff++)
            {
                const int nWidthToRead =
                    std::min(nBlockXSize, nXSize - nBlockXOff * nBlockXSize);
                const int nHeightToRead =
                    std::min(nBlockYSize, nYSize - nBlockYOff * nBlockYSize);

                if (bUseIOThread)
                {
                    // Wait for previous background I/O task to be finished
                    oPool.WaitCompletion();
                    eErr = job.eErr;

                    // Swap buffers
                    std::swap(pabyBackgroundBuffer, pabyActiveBuffer);

                    // Prepare for next I/O task
                    int nNextBlockXOff = nBlockXOff + 1;
                    int nNextBlockYOff = nBlockYOff;
                    if (nNextBlockXOff == nTilesX)
                    {
                        nNextBlockXOff = 0;
                        nNextBlockYOff++;
                    }
                    if (nNextBlockYOff != nTilesY)
                    {
                        job.nXOff = nNextBlockXOff * nBlockXSize;
                        job.nYOff = nNextBlockYOff * nBlockYSize;
                        job.nWidthToRead =
                            std::min(nBlockXSize, nXSize - job.nXOff);
                        job.nHeightToRead =
                            std::min(nBlockYSize, nYSize - job.nYOff);
                        job.pBuffer = pabyBackgroundBuffer;

                        // Submit next job
                        oPool.SubmitJob(ReadRasterFunction, &job);
                    }
                }
                else
                {
                    job.nXOff = nBlockXOff * nBlockXSize;
                    job.nYOff = nBlockYOff * nBlockYSize;
                    job.nWidthToRead = nWidthToRead;
                    job.nHeightToRead = nHeightToRead;
                    ReadRasterFunction(&job);
                    eErr = job.eErr;
                }

                if (b1BitAlpha)
                {
                    for (int i = 0; i < nWidthToRead * nHeightToRead; i++)
                    {
                        if (pabyActiveBuffer[nAlphaBandIndex * nWidthToRead *
                                                 nHeightToRead +
                                             i])
                            pabyActiveBuffer[nAlphaBandIndex * nWidthToRead *
                                                 nHeightToRead +
                                             i] = 1;
                        else
                            pabyActiveBuffer[nAlphaBandIndex * nWidthToRead *
                                                 nHeightToRead +
                                             i] = 0;
                    }
                }
                if (eErr == CE_None)
                {
                    if (bYCBCR420)
                    {
                        int j, i;
                        for (j = 0; j < nHeightToRead; j++)
                        {
                            for (i = 0; i < nWidthToRead; i++)
                            {
                                int R = pabyActiveBuffer[j * nWidthToRead + i];
                                int G = pabyActiveBuffer[nHeightToRead *
                                                             nWidthToRead +
                                                         j * nWidthToRead + i];
                                int B = pabyActiveBuffer[2 * nHeightToRead *
                                                             nWidthToRead +
                                                         j * nWidthToRead + i];
                                int Y =
                                    (int)(0.299 * R + 0.587 * G + 0.114 * B);
                                int Cb =
                                    CLAMP_0_255((int)(-0.1687 * R - 0.3313 * G +
                                                      0.5 * B + 128));
                                int Cr =
                                    CLAMP_0_255((int)(0.5 * R - 0.4187 * G -
                                                      0.0813 * B + 128));
                                pYUV420Buffer[j * nWidthToRead + i] = (GByte)Y;
                                pYUV420Buffer[nHeightToRead * nWidthToRead +
                                              ((j / 2) * ((nWidthToRead) / 2) +
                                               i / 2)] = (GByte)Cb;
                                pYUV420Buffer[5 * nHeightToRead * nWidthToRead /
                                                  4 +
                                              ((j / 2) * ((nWidthToRead) / 2) +
                                               i / 2)] = (GByte)Cr;
                                if (nBands == 4)
                                {
                                    pYUV420Buffer[3 * nHeightToRead *
                                                      nWidthToRead / 2 +
                                                  j * nWidthToRead + i] =
                                        (GByte)pabyActiveBuffer
                                            [3 * nHeightToRead * nWidthToRead +
                                             j * nWidthToRead + i];
                                }
                            }
                        }

                        int nBytesToWrite =
                            3 * nWidthToRead * nHeightToRead / 2;
                        if (nBands == 4)
                            nBytesToWrite += nBlockXSize * nBlockYSize;

                        if (!opj_write_tile(pCodec, iTile, pYUV420Buffer,
                                            nBytesToWrite, pStream))
                        {
                            CPLError(CE_Failure, CPLE_AppDefined,
                                     "opj_write_tile() failed");
                            eErr = CE_Failure;
                        }
                    }
                    else
                    {
                        if (!opj_write_tile(pCodec, iTile, pabyActiveBuffer,
                                            nWidthToRead * nHeightToRead *
                                                nBands * nDataTypeSize,
                                            pStream))
                        {
                            CPLError(CE_Failure, CPLE_AppDefined,
                                     "opj_write_tile() failed");
                            eErr = CE_Failure;
                        }
                    }
                }

                if (!pfnProgress((iTile + 1) * 1.0 / (nTilesX * nTilesY),
                                 nullptr, pProgressData))
                    eErr = CE_Failure;

                iTile++;
            }
        }

        VSIFree(pTempBuffer);
        VSIFree(pYUV420Buffer);

        if (eErr != CE_None)
        {
            opj_stream_destroy(pStream);
            opj_image_destroy(psImage);
            opj_destroy_codec(pCodec);
            VSIFCloseL(fp);
            delete poGMLJP2Box;
            return nullptr;
        }

        if (!opj_end_compress(pCodec, pStream))
        {
            CPLError(CE_Failure, CPLE_AppDefined, "opj_end_compress() failed");
            opj_stream_destroy(pStream);
            opj_image_destroy(psImage);
            opj_destroy_codec(pCodec);
            VSIFCloseL(fp);
            delete poGMLJP2Box;
            return nullptr;
        }
        opj_stream_destroy(pStream);
    }

    opj_image_destroy(psImage);
    opj_destroy_codec(pCodec);

    /* -------------------------------------------------------------------- */
    /*      Patch JP2C box length and add trailing JP2 boxes                */
    /* -------------------------------------------------------------------- */
    if (eCodecFormat == OPJ_CODEC_JP2 &&
        !CPLFetchBool(papszOptions, "JP2C_LENGTH_ZERO",
                      false) /* debug option */)
    {
        vsi_l_offset nEndJP2C = VSIFTellL(fp);
        GUIntBig nBoxSize = nEndJP2C - nStartJP2C;
        if (bUseXLBoxes)
        {
            VSIFSeekL(fp, nStartJP2C + 8, SEEK_SET);
            CPL_MSBPTR64(&nBoxSize);
            VSIFWriteL(&nBoxSize, 8, 1, fp);
        }
        else
        {
            GUInt32 nBoxSize32 = (GUInt32)nBoxSize;
            if ((vsi_l_offset)nBoxSize32 != nBoxSize)
            {
                /*  Should not happen hopefully */
                if ((bGeoreferencingCompatOfGeoJP2 || poGMLJP2Box) &&
                    bGeoBoxesAfter)
                {
                    CPLError(CE_Warning, CPLE_AppDefined,
                             "Cannot write GMLJP2/GeoJP2 boxes as codestream "
                             "is unexpectedly > 4GB");
                    bGeoreferencingCompatOfGeoJP2 = FALSE;
                    delete poGMLJP2Box;
                    poGMLJP2Box = nullptr;
                }
            }
            else
            {
                VSIFSeekL(fp, nStartJP2C, SEEK_SET);
                CPL_MSBPTR32(&nBoxSize32);
                VSIFWriteL(&nBoxSize32, 4, 1, fp);
            }
        }
        VSIFSeekL(fp, 0, SEEK_END);

        if (CPLFetchBool(papszOptions, "WRITE_METADATA", false))
        {
            WriteIPRBox(fp, poSrcDS, papszOptions);
        }

        if (bGeoBoxesAfter)
        {
            if (poGMLJP2Box != nullptr)
            {
                WriteBox(fp, poGMLJP2Box);
            }

            if (CPLFetchBool(papszOptions, "WRITE_METADATA", false))
            {
                if (!CPLFetchBool(papszOptions, "MAIN_MD_DOMAIN_ONLY", false))
                    WriteXMLBoxes(fp, poSrcDS, papszOptions);
                WriteGDALMetadataBox(fp, poSrcDS, papszOptions);
            }

            if (bGeoJP2Option && bGeoreferencingCompatOfGeoJP2)
            {
                GDALJP2Box *poBox = oJP2MD.CreateJP2GeoTIFF();
                WriteBox(fp, poBox);
                delete poBox;
            }

            if (CPLFetchBool(papszOptions, "WRITE_METADATA", false) &&
                !CPLFetchBool(papszOptions, "MAIN_MD_DOMAIN_ONLY", false))
            {
                WriteXMPBox(fp, poSrcDS, papszOptions);
            }
        }
    }

    VSIFCloseL(fp);
    delete poGMLJP2Box;

    /* -------------------------------------------------------------------- */
    /*      Re-open dataset, and copy any auxiliary pam information.         */
    /* -------------------------------------------------------------------- */

    GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
    JP2OpenJPEGDataset *poDS =
        (JP2OpenJPEGDataset *)JP2OpenJPEGDataset::Open(&oOpenInfo);

    if (poDS)
    {
        poDS->CloneInfo(poSrcDS, GCIF_PAM_DEFAULT & (~GCIF_METADATA));

        /* Only write relevant metadata to PAM, and if needed */
        if (!CPLFetchBool(papszOptions, "WRITE_METADATA", false))
        {
            char **papszSrcMD = CSLDuplicate(poSrcDS->GetMetadata());
            papszSrcMD =
                CSLSetNameValue(papszSrcMD, GDALMD_AREA_OR_POINT, nullptr);
            papszSrcMD = CSLSetNameValue(papszSrcMD, "Corder", nullptr);
            for (char **papszSrcMDIter = papszSrcMD;
                 papszSrcMDIter && *papszSrcMDIter;)
            {
                /* Remove entries like KEY= (without value) */
                if ((*papszSrcMDIter)[0] &&
                    (*papszSrcMDIter)[strlen((*papszSrcMDIter)) - 1] == '=')
                {
                    CPLFree(*papszSrcMDIter);
                    memmove(papszSrcMDIter, papszSrcMDIter + 1,
                            sizeof(char *) *
                                (CSLCount(papszSrcMDIter + 1) + 1));
                }
                else
                    ++papszSrcMDIter;
            }
            char **papszMD = CSLDuplicate(poDS->GetMetadata());
            papszMD = CSLSetNameValue(papszMD, GDALMD_AREA_OR_POINT, nullptr);
            if (papszSrcMD && papszSrcMD[0] != nullptr &&
                CSLCount(papszSrcMD) != CSLCount(papszMD))
            {
                poDS->SetMetadata(papszSrcMD);
            }
            CSLDestroy(papszSrcMD);
            CSLDestroy(papszMD);
        }
    }

    return poDS;
}

/************************************************************************/
/*                      GDALRegister_JP2OpenJPEG()                      */
/************************************************************************/

void GDALRegister_JP2OpenJPEG()

{
    if (!GDAL_CHECK_VERSION("JP2OpenJPEG driver"))
        return;

    if (GDALGetDriverByName("JP2OpenJPEG") != nullptr)
        return;

    GDALDriver *poDriver = new GDALDriver();

    poDriver->SetDescription("JP2OpenJPEG");
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
                              "JPEG-2000 driver based on OpenJPEG library");
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
                              "drivers/raster/jp2openjpeg.html");
    poDriver->SetMetadataItem(GDAL_DMD_MIMETYPE, "image/jp2");
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "jp2");
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSIONS, "jp2 j2k");
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES,
                              "Byte Int16 UInt16 Int32 UInt32");

    poDriver->SetMetadataItem(
        GDAL_DMD_OPENOPTIONLIST,
        "<OpenOptionList>"
#if IS_OPENJPEG_OR_LATER(2, 5, 0)
        "   <Option name='STRICT' type='boolean' description='Whether "
        "strict/pedantic decoding should be adopted. Set to NO to allow "
        "decoding broken files' default='YES'/>"
#endif
        "   <Option name='1BIT_ALPHA_PROMOTION' type='boolean' "
        "description='Whether a 1-bit alpha channel should be promoted to "
        "8-bit' default='YES'/>"
        "   <Option name='OPEN_REMOTE_GML' type='boolean' description='Whether "
        "to load remote vector layers referenced by a link in a GMLJP2 v2 box' "
        "default='NO'/>"
        "   <Option name='GEOREF_SOURCES' type='string' description='Comma "
        "separated list made with values "
        "INTERNAL/GMLJP2/GEOJP2/WORLDFILE/PAM/NONE that describe the priority "
        "order for georeferencing' default='PAM,GEOJP2,GMLJP2,WORLDFILE'/>"
        "   <Option name='USE_TILE_AS_BLOCK' type='boolean' "
        "description='Whether to always use the JPEG-2000 block size as the "
        "GDAL block size' default='NO'/>"
        "</OpenOptionList>");

    poDriver->SetMetadataItem(
        GDAL_DMD_CREATIONOPTIONLIST,
        "<CreationOptionList>"
        "   <Option name='CODEC' type='string-select' default='according to "
        "file extension. If unknown, default to J2K'>"
        "       <Value>JP2</Value>"
        "       <Value>J2K</Value>"
        "   </Option>"
        "   <Option name='GeoJP2' type='boolean' description='Whether to emit "
        "a GeoJP2 box' default='YES'/>"
        "   <Option name='GMLJP2' type='boolean' description='Whether to emit "
        "a GMLJP2 v1 box' default='YES'/>"
        "   <Option name='GMLJP2V2_DEF' type='string' description='Definition "
        "file to describe how a GMLJP2 v2 box should be generated. If set to "
        "YES, a minimal instance will be created'/>"
        "   <Option name='QUALITY' type='string' description='Single quality "
        "value or comma separated list of increasing quality values for "
        "several layers, each in the 0-100 range' default='25'/>"
        "   <Option name='REVERSIBLE' type='boolean' description='True if the "
        "compression is reversible' default='false'/>"
        "   <Option name='RESOLUTIONS' type='int' description='Number of "
        "resolutions.' min='1' max='30'/>"
        "   <Option name='BLOCKXSIZE' type='int' description='Tile Width' "
        "default='1024'/>"
        "   <Option name='BLOCKYSIZE' type='int' description='Tile Height' "
        "default='1024'/>"
        "   <Option name='PROGRESSION' type='string-select' default='LRCP'>"
        "       <Value>LRCP</Value>"
        "       <Value>RLCP</Value>"
        "       <Value>RPCL</Value>"
        "       <Value>PCRL</Value>"
        "       <Value>CPRL</Value>"
        "   </Option>"
        "   <Option name='SOP' type='boolean' description='True to insert SOP "
        "markers' default='false'/>"
        "   <Option name='EPH' type='boolean' description='True to insert EPH "
        "markers' default='false'/>"
        "   <Option name='YCBCR420' type='boolean' description='if RGB must be "
        "resampled to YCbCr 4:2:0' default='false'/>"
        "   <Option name='YCC' type='boolean' description='if RGB must be "
        "transformed to YCC color space (lossless MCT transform)' "
        "default='YES'/>"
        "   <Option name='NBITS' type='int' description='Bits (precision) for "
        "sub-byte files (1-7), sub-uint16 (9-15), sub-uint32 (17-31)'/>"
        "   <Option name='1BIT_ALPHA' type='boolean' description='Whether to "
        "encode the alpha channel as a 1-bit channel' default='NO'/>"
        "   <Option name='ALPHA' type='boolean' description='Whether to force "
        "encoding last channel as alpha channel' default='NO'/>"
        "   <Option name='PROFILE' type='string-select' description='Which "
        "codestream profile to use' default='AUTO'>"
        "       <Value>AUTO</Value>"
        "       <Value>UNRESTRICTED</Value>"
        "       <Value>PROFILE_1</Value>"
        "   </Option>"
        "   <Option name='INSPIRE_TG' type='boolean' description='Whether to "
        "use features that comply with Inspire Orthoimagery Technical "
        "Guidelines' default='NO'/>"
        "   <Option name='JPX' type='boolean' description='Whether to "
        "advertise JPX features when a GMLJP2 box is written (or use JPX "
        "branding if GMLJP2 v2)' default='YES'/>"
        "   <Option name='GEOBOXES_AFTER_JP2C' type='boolean' "
        "description='Whether to place GeoJP2/GMLJP2 boxes after the "
        "code-stream' default='NO'/>"
        "   <Option name='PRECINCTS' type='string' description='Precincts size "
        "as a string of the form {w,h},{w,h},... with power-of-two values'/>"
        "   <Option name='TILEPARTS' type='string-select' description='Whether "
        "to generate tile-parts and according to which criterion' "
        "default='DISABLED'>"
        "       <Value>DISABLED</Value>"
        "       <Value>RESOLUTIONS</Value>"
        "       <Value>LAYERS</Value>"
        "       <Value>COMPONENTS</Value>"
        "   </Option>"
        "   <Option name='CODEBLOCK_WIDTH' type='int' description='Codeblock "
        "width' default='64' min='4' max='1024'/>"
        "   <Option name='CODEBLOCK_HEIGHT' type='int' description='Codeblock "
        "height' default='64' min='4' max='1024'/>"
        "   <Option name='CT_COMPONENTS' type='int' min='3' max='4' "
        "description='If there is one color table, number of color table "
        "components to write. Autodetected if not specified.'/>"
        "   <Option name='WRITE_METADATA' type='boolean' description='Whether "
        "metadata should be written, in a dedicated JP2 XML box' default='NO'/>"
        "   <Option name='MAIN_MD_DOMAIN_ONLY' type='boolean' "
        "description='(Only if WRITE_METADATA=YES) Whether only metadata from "
        "the main domain should be written' default='NO'/>"
        "   <Option name='USE_SRC_CODESTREAM' type='boolean' description='When "
        "source dataset is JPEG2000, whether to reuse the codestream of the "
        "source dataset unmodified' default='NO'/>"
#if IS_OPENJPEG_OR_LATER(2, 3, 0)
        "   <Option name='CODEBLOCK_STYLE' type='string' "
        "description='Comma-separated combination of BYPASS, RESET, TERMALL, "
        "VSC, PREDICTABLE, SEGSYM or value between 0 and 63'/>"
#endif
#if IS_OPENJPEG_OR_LATER(2, 4, 0)
        "   <Option name='PLT' type='boolean' description='True to insert PLT "
        "marker segments' default='false'/>"
#endif
#if IS_OPENJPEG_OR_LATER(2, 5, 0)
        "   <Option name='TLM' type='boolean' description='True to insert TLM "
        "marker segments' default='false'/>"
#endif
        "   <Option name='COMMENT' type='string' description='Content of the "
        "comment (COM) marker'/>"
        "</CreationOptionList>");

    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");

    poDriver->pfnIdentify = JP2OpenJPEGDataset::Identify;
    poDriver->pfnOpen = JP2OpenJPEGDataset::Open;
    poDriver->pfnCreateCopy = JP2OpenJPEGDataset::CreateCopy;

    GetGDALDriverManager()->RegisterDriver(poDriver);
}
