/********************************************************************** * * Project: GDAL * Purpose: Dataset that modifies the orientation of an underlying dataset * Author: Even Rouault, * ********************************************************************** * Copyright (c) 2022, Even Rouault, * * 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. ****************************************************************************/ #include "gdalorienteddataset.h" #include "gdal_utils.h" #include //! @cond Doxygen_Suppress /************************************************************************/ /* GDALOrientedRasterBand */ /************************************************************************/ class GDALOrientedRasterBand : public GDALRasterBand { GDALRasterBand *m_poSrcBand; std::unique_ptr m_poCacheDS{}; GDALOrientedRasterBand(const GDALOrientedRasterBand &) = delete; GDALOrientedRasterBand &operator=(const GDALOrientedRasterBand &) = delete; protected: CPLErr IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage) override; public: GDALOrientedRasterBand(GDALOrientedDataset *poDSIn, int nBandIn); GDALColorInterp GetColorInterpretation() override { return m_poSrcBand->GetColorInterpretation(); } }; /************************************************************************/ /* GDALOrientedRasterBand() */ /************************************************************************/ GDALOrientedRasterBand::GDALOrientedRasterBand(GDALOrientedDataset *poDSIn, int nBandIn) : m_poSrcBand(poDSIn->m_poSrcDS->GetRasterBand(nBandIn)) { poDS = poDSIn; eDataType = m_poSrcBand->GetRasterDataType(); if (poDSIn->m_eOrigin == GDALOrientedDataset::Origin::TOP_LEFT) { m_poSrcBand->GetBlockSize(&nBlockXSize, &nBlockYSize); } else { nBlockXSize = poDS->GetRasterXSize(); nBlockYSize = 1; } } /************************************************************************/ /* FlipLineHorizontally() */ /************************************************************************/ static void FlipLineHorizontally(void *pLine, int nDTSize, int nBlockXSize) { switch (nDTSize) { case 1: { GByte *pabyLine = static_cast(pLine); for (int iX = 0; iX < nBlockXSize / 2; ++iX) { std::swap(pabyLine[iX], pabyLine[nBlockXSize - 1 - iX]); } break; } default: { GByte *pabyLine = static_cast(pLine); std::vector abyTemp(nDTSize); for (int iX = 0; iX < nBlockXSize / 2; ++iX) { memcpy(&abyTemp[0], pabyLine + iX * nDTSize, nDTSize); memcpy(pabyLine + iX * nDTSize, pabyLine + (nBlockXSize - 1 - iX) * nDTSize, nDTSize); memcpy(pabyLine + (nBlockXSize - 1 - iX) * nDTSize, &abyTemp[0], nDTSize); } break; } } } /************************************************************************/ /* IReadBlock() */ /************************************************************************/ CPLErr GDALOrientedRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage) { auto l_poDS = cpl::down_cast(poDS); const int nDTSize = GDALGetDataTypeSizeBytes(eDataType); if (m_poCacheDS == nullptr && l_poDS->m_eOrigin != GDALOrientedDataset::Origin::TOP_LEFT && l_poDS->m_eOrigin != GDALOrientedDataset::Origin::TOP_RIGHT) { auto poGTiffDrv = GetGDALDriverManager()->GetDriverByName("GTiff"); CPLStringList aosOptions; aosOptions.AddString("-f"); aosOptions.AddString(poGTiffDrv ? "GTiff" : "MEM"); aosOptions.AddString("-b"); aosOptions.AddString(CPLSPrintf("%d", nBand)); if (poGTiffDrv) { aosOptions.AddString("-co"); aosOptions.AddString("TILED=YES"); } std::string osTmpName; if (poGTiffDrv) { if (static_cast(nRasterXSize) * nRasterYSize * nDTSize > 10 * 1024 * 1024) { osTmpName = CPLGenerateTempFilename(nullptr); } else { osTmpName = CPLSPrintf("/vsimem/_gdalorienteddataset/%p.tif", this); } } GDALTranslateOptions *psOptions = GDALTranslateOptionsNew(aosOptions.List(), nullptr); if (psOptions == nullptr) return CE_Failure; GDALDatasetH hOutDS = GDALTranslate( osTmpName.c_str(), GDALDataset::ToHandle(l_poDS->m_poSrcDS), psOptions, nullptr); GDALTranslateOptionsFree(psOptions); if (hOutDS == nullptr) return CE_Failure; m_poCacheDS.reset(GDALDataset::FromHandle(hOutDS)); m_poCacheDS->MarkSuppressOnClose(); } CPLErr eErr = CE_None; switch (l_poDS->m_eOrigin) { case GDALOrientedDataset::Origin::TOP_LEFT: { eErr = m_poSrcBand->ReadBlock(nBlockXOff, nBlockYOff, pImage); break; } case GDALOrientedDataset::Origin::TOP_RIGHT: { CPLAssert(nBlockXSize == nRasterXSize); CPLAssert(nBlockYSize == 1); if (m_poSrcBand->RasterIO(GF_Read, 0, nBlockYOff, nRasterXSize, 1, pImage, nRasterXSize, 1, eDataType, 0, 0, nullptr) != CE_None) { return CE_Failure; } FlipLineHorizontally(pImage, nDTSize, nBlockXSize); break; } case GDALOrientedDataset::Origin::BOT_RIGHT: case GDALOrientedDataset::Origin::BOT_LEFT: { CPLAssert(nBlockXSize == nRasterXSize); CPLAssert(nBlockYSize == 1); if (m_poCacheDS->GetRasterBand(1)->RasterIO( GF_Read, 0, nRasterYSize - 1 - nBlockYOff, nRasterXSize, 1, pImage, nRasterXSize, 1, eDataType, 0, 0, nullptr) != CE_None) { return CE_Failure; } if (l_poDS->m_eOrigin == GDALOrientedDataset::Origin::BOT_RIGHT) FlipLineHorizontally(pImage, nDTSize, nBlockXSize); break; } case GDALOrientedDataset::Origin::LEFT_TOP: case GDALOrientedDataset::Origin::RIGHT_TOP: { CPLAssert(nBlockXSize == nRasterXSize); CPLAssert(nBlockYSize == 1); if (m_poCacheDS->GetRasterBand(1)->RasterIO( GF_Read, nBlockYOff, 0, 1, nRasterXSize, pImage, 1, nRasterXSize, eDataType, 0, 0, nullptr) != CE_None) { return CE_Failure; } if (l_poDS->m_eOrigin == GDALOrientedDataset::Origin::RIGHT_TOP) FlipLineHorizontally(pImage, nDTSize, nBlockXSize); break; } case GDALOrientedDataset::Origin::RIGHT_BOT: case GDALOrientedDataset::Origin::LEFT_BOT: { CPLAssert(nBlockXSize == nRasterXSize); CPLAssert(nBlockYSize == 1); if (m_poCacheDS->GetRasterBand(1)->RasterIO( GF_Read, nRasterYSize - 1 - nBlockYOff, 0, 1, nRasterXSize, pImage, 1, nRasterXSize, eDataType, 0, 0, nullptr) != CE_None) { return CE_Failure; } if (l_poDS->m_eOrigin == GDALOrientedDataset::Origin::RIGHT_BOT) FlipLineHorizontally(pImage, nDTSize, nBlockXSize); break; } } return eErr; } /************************************************************************/ /* GDALOrientedDataset() */ /************************************************************************/ GDALOrientedDataset::GDALOrientedDataset(GDALDataset *poSrcDataset, Origin eOrigin) : m_poSrcDS(poSrcDataset), m_eOrigin(eOrigin) { switch (eOrigin) { case GDALOrientedDataset::Origin::TOP_LEFT: case GDALOrientedDataset::Origin::TOP_RIGHT: case GDALOrientedDataset::Origin::BOT_RIGHT: case GDALOrientedDataset::Origin::BOT_LEFT: { nRasterXSize = poSrcDataset->GetRasterXSize(); nRasterYSize = poSrcDataset->GetRasterYSize(); break; } case GDALOrientedDataset::Origin::LEFT_TOP: case GDALOrientedDataset::Origin::RIGHT_TOP: case GDALOrientedDataset::Origin::RIGHT_BOT: case GDALOrientedDataset::Origin::LEFT_BOT: { // Permute (x, y) nRasterXSize = poSrcDataset->GetRasterYSize(); nRasterYSize = poSrcDataset->GetRasterXSize(); break; } } const int nSrcBands = poSrcDataset->GetRasterCount(); for (int i = 1; i <= nSrcBands; ++i) { SetBand(i, new GDALOrientedRasterBand(this, i)); } } /************************************************************************/ /* GDALOrientedDataset() */ /************************************************************************/ GDALOrientedDataset::GDALOrientedDataset( std::unique_ptr &&poSrcDataset, Origin eOrigin) : GDALOrientedDataset(poSrcDataset.get(), eOrigin) { // cppcheck-suppress useInitializationList m_poSrcDSHolder = std::move(poSrcDataset); } /************************************************************************/ /* GetMetadata() */ /************************************************************************/ char **GDALOrientedDataset::GetMetadata(const char *pszDomain) { if (pszDomain == nullptr || pszDomain[0] == '\0') { if (m_aosSrcMD.empty()) { m_aosSrcMD.Assign(CSLDuplicate(m_poSrcDS->GetMetadata(pszDomain))); const char *pszOrientation = m_aosSrcMD.FetchNameValue("EXIF_Orientation"); if (pszOrientation) { m_aosSrcMD.SetNameValue("original_EXIF_Orientation", pszOrientation); m_aosSrcMD.SetNameValue("EXIF_Orientation", nullptr); } } return m_aosSrcMD.List(); } if (EQUAL(pszDomain, "EXIF")) { if (m_aosSrcMD_EXIF.empty()) { m_aosSrcMD_EXIF.Assign( CSLDuplicate(m_poSrcDS->GetMetadata(pszDomain))); const char *pszOrientation = m_aosSrcMD_EXIF.FetchNameValue("EXIF_Orientation"); if (pszOrientation) { m_aosSrcMD_EXIF.SetNameValue("original_EXIF_Orientation", pszOrientation); m_aosSrcMD_EXIF.SetNameValue("EXIF_Orientation", nullptr); } } return m_aosSrcMD_EXIF.List(); } return m_poSrcDS->GetMetadata(pszDomain); } /************************************************************************/ /* GetMetadataItem() */ /************************************************************************/ const char *GDALOrientedDataset::GetMetadataItem(const char *pszName, const char *pszDomain) { return CSLFetchNameValue(GetMetadata(pszDomain), pszName); } //! @endcond