File indexing completed on 2025-01-05 03:58:05

0001 /* ============================================================
0002  *
0003  * This file is a part of digiKam project
0004  * https://www.digikam.org
0005  *
0006  * Date        : 2011-01-07
0007  * Description : a command line tool to extract info from a DNG file
0008  *
0009  * SPDX-FileCopyrightText: 2011      by Jens Mueller <tschenser at gmx dot de>
0010  * SPDX-FileCopyrightText: 2008-2024 by Gilles Caulier <caulier dot gilles at gmail dot com>
0011  *
0012  * SPDX-License-Identifier: GPL-2.0-or-later
0013  *
0014  * ============================================================ */
0015 
0016 // Qt includes
0017 
0018 #include <QCoreApplication>
0019 #include <QFile>
0020 #include <QFileInfo>
0021 #include <QDataStream>
0022 
0023 // DNG SDK includes
0024 
0025 #include "dng_host.h"
0026 #include "dng_camera_profile.h"
0027 #include "dng_color_space.h"
0028 #include "dng_file_stream.h"
0029 #include "dng_image.h"
0030 #include "dng_image_writer.h"
0031 #include "dng_info.h"
0032 #include "dng_memory_stream.h"
0033 #include "dng_opcodes.h"
0034 #include "dng_opcode_list.h"
0035 #include "dng_parse_utils.h"
0036 #include "dng_string.h"
0037 #include "dng_render.h"
0038 #include "dng_xmp_sdk.h"
0039 
0040 // Local includes
0041 
0042 #include "digikam_debug.h"
0043 #include "digikam_globals.h"
0044 
0045 #define CHUNK 65536
0046 
0047 int main(int argc, char** argv)
0048 {
0049     try
0050     {
0051         QCoreApplication app(argc, argv);
0052 
0053         bool extractOriginal = false;
0054         bool extractIfd      = false;
0055 
0056         if (argc == 1)
0057         {
0058             qCDebug(DIGIKAM_TESTS_LOG) << "\n"
0059                         "dnginfo - DNG information tool\n"
0060                         "Usage: %s [options] dngfile\n"
0061                         "Valid options:\n"
0062                         "  -extractraw            extract embedded original\n"
0063                         "  -extractifd            extract IFD images\n"
0064                     << argv[0];
0065 
0066             return -1;
0067         }
0068 
0069         qint32 index;
0070 
0071         for (index = 1 ; index < argc && argv[index][0] == '-' ; ++index)
0072         {
0073             QString option = QString::fromUtf8(&argv[index][1]);
0074 
0075             if (option == QLatin1String("extractraw"))
0076             {
0077                 extractOriginal = true;
0078             }
0079 
0080             if (option == QLatin1String("extractifd"))
0081             {
0082                 extractIfd = true;
0083             }
0084         }
0085 
0086         if (index == argc)
0087         {
0088             qCCritical(DIGIKAM_TESTS_LOG) << "*** No file specified\n";
0089             return 1;
0090         }
0091 
0092         QFileInfo dngFileInfo(QString::fromUtf8(argv[index]));
0093 
0094         dng_xmp_sdk::InitializeSDK();
0095 
0096         dng_file_stream stream(QFile::encodeName(dngFileInfo.absoluteFilePath()).constData());
0097         dng_host        host;
0098         host.SetKeepOriginalFile(true);
0099 
0100         AutoPtr<dng_negative> negative;
0101         {
0102             dng_info info;
0103             info.Parse(host, stream);
0104             info.PostParse(host);
0105 
0106             if (!info.IsValidDNG())
0107             {
0108                 return dng_error_bad_format;
0109             }
0110 
0111             negative.Reset(host.Make_dng_negative());
0112             negative->Parse(host, stream, info);
0113             negative->PostParse(host, stream, info);
0114 
0115             qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("Model:           %1").arg(QString::fromLatin1(negative->ModelName().Get()));
0116             dng_rect defaultCropArea = negative->DefaultCropArea();
0117             dng_rect activeArea = negative->GetLinearizationInfo()->fActiveArea;
0118             qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("FinalImageSize:  %1 x %2").arg(negative->DefaultFinalWidth()).arg(negative->DefaultFinalHeight());
0119             qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("RawImageSize:    %1 x %2").arg(info.fIFD[info.fMainIndex]->fImageWidth).arg(info.fIFD[info.fMainIndex]->fImageLength);
0120             qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("ActiveArea:      %1, %2 : %3 x %4").arg(activeArea.t).arg(activeArea.l).arg(activeArea.W()).arg(activeArea.H());
0121             qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("DefaultCropArea: %1, %2 : %3 x %4").arg(defaultCropArea.t).arg(defaultCropArea.l).arg(defaultCropArea.W()).arg(defaultCropArea.H());
0122             qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("OriginalData:    %1 bytes").arg(negative->OriginalRawFileDataLength());
0123             qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("PrivateData:     %1 bytes").arg(negative->PrivateLength());
0124             qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("CameraProfiles:  %1").arg(negative->ProfileCount());
0125 
0126             qCDebug(DIGIKAM_TESTS_LOG) << QT_ENDL;
0127 
0128             for (uint32 i = 0 ; i < negative->ProfileCount() ; ++i)
0129             {
0130                 qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("  Profile: %1").arg(i);
0131                 dng_camera_profile dcp = negative->ProfileByIndex(i);
0132                 qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("    Name:      %1").arg(QString::fromLatin1(dcp.Name().Get()));
0133                 qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("    Copyright: %1").arg(QString::fromLatin1(dcp.Copyright().Get()));
0134             }
0135 
0136             qCDebug(DIGIKAM_TESTS_LOG) << QT_ENDL;
0137 
0138             qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("Opcodes(1):      %1").arg(info.fIFD[info.fMainIndex]->fOpcodeList1Count);
0139             qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("Opcodes(2):      %1").arg(info.fIFD[info.fMainIndex]->fOpcodeList2Count);
0140             qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("Opcodes(3):      %1").arg(info.fIFD[info.fMainIndex]->fOpcodeList3Count);
0141             qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("MainImage:       %1").arg(info.fMainIndex);
0142             qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("ChainedCount:    %1").arg(info.ChainedIFDCount());
0143 
0144             qCDebug(DIGIKAM_TESTS_LOG) << QT_ENDL;
0145 
0146             for (uint32 ifdIdx = 0 ; ifdIdx < info.IFDCount() ; ++ifdIdx)
0147             {
0148                 dng_ifd* const ifd = info.fIFD[ifdIdx];
0149 
0150                 qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("IFD: %1").arg(ifdIdx);
0151                 qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("  ImageWidth:    %1").arg(ifd->fImageWidth);
0152                 qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("  ImageLength:   %1").arg(ifd->fImageLength);
0153                 qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("  BitsPerSample:");
0154 
0155                 for (uint32 i = 0 ; i < ifd->fSamplesPerPixel ; ++i)
0156                 {
0157                     qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("     %1").arg(ifd->fBitsPerSample[i]);
0158                 }
0159 
0160                 qCDebug(DIGIKAM_TESTS_LOG) << QT_ENDL;
0161 
0162                 qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("  Compression:               %1").arg(QLatin1String(LookupCompression(ifd->fCompression)));
0163                 qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("  PhotometricInterpretation: %1").arg(QLatin1String(LookupPhotometricInterpretation(ifd->fPhotometricInterpretation)));
0164                 qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("  SamplesPerPixel:           %1").arg(ifd->fSamplesPerPixel);
0165                 qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("  PlanarConfiguration:       %1").arg(ifd->fPlanarConfiguration);
0166                 qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("  LinearizationTableCount:   %1").arg(ifd->fLinearizationTableCount);
0167                 qCDebug(DIGIKAM_TESTS_LOG).noquote() << QString::fromLatin1("  LinearizationTableType:    %1").arg(ifd->fLinearizationTableType);
0168 
0169                 qCDebug(DIGIKAM_TESTS_LOG) << QT_ENDL;
0170 
0171                 if (extractIfd)
0172                 {
0173                     if ((ifd->fPlanarConfiguration == pcInterleaved) &&
0174                         (ifd->fCompression         == ccJPEG)        &&
0175                         (ifd->fSamplesPerPixel     == 3)             &&
0176                         (ifd->fBitsPerSample[0]    == 8)             &&
0177                         (ifd->fBitsPerSample[1]    == 8)             &&
0178                         (ifd->fBitsPerSample[2]    == 8)             &&
0179                         (ifd->TilesAcross()        == 1)             &&
0180                         (ifd->TilesDown()          == 1))
0181                     {
0182                         uint64 tileOffset    = ifd->fTileOffset[0];
0183                         uint64 tileLength    = ifd->fTileByteCount[0];
0184                         uint8* const pBuffer = new uint8[tileLength];
0185                         stream.SetReadPosition(tileOffset);
0186                         stream.Get(pBuffer, tileLength);
0187 
0188                         QString outfn2 = QString::fromLatin1("%1-ifd%2.jpg")
0189                             .arg(dngFileInfo.absoluteFilePath())
0190                             .arg(ifdIdx);
0191 
0192                         dng_file_stream streamOF2(outfn2.toLatin1().constData(), true);
0193                         streamOF2.Put(pBuffer, tileLength);
0194 
0195                         delete [] pBuffer;
0196 
0197                         qCDebug(DIGIKAM_TESTS_LOG) << "Extracted IFD image as JPEG:" << outfn2;
0198                     }
0199                     else
0200                     {
0201                         AutoPtr<dng_image> image;
0202 
0203                         image.Reset(host.Make_dng_image(ifd->Bounds(), ifd->fSamplesPerPixel, ifd->PixelType()));
0204                         ifd->ReadImage(host, stream, *image.Get());
0205 
0206                         QString outfn = QString::fromLatin1("%1-ifd%2.tif")
0207                             .arg(dngFileInfo.absoluteFilePath())
0208                             .arg(ifdIdx);
0209 
0210                         dng_file_stream streamOF(outfn.toLatin1().constData(), true);
0211 
0212                         dng_image_writer writer;
0213                         writer.WriteTIFF(host, streamOF, *image.Get(), (image->Planes() >= 3) ? piRGB : piBlackIsZero, ccUncompressed);
0214 
0215                         qCDebug(DIGIKAM_TESTS_LOG) << "Extracted IFD image as TIF:" << outfn;
0216                     }
0217                 }
0218             }
0219 
0220             QString originalFileName(QString::fromUtf8(negative->OriginalRawFileName().Get()));
0221             quint32 originalDataLength     = negative->OriginalRawFileDataLength();
0222             const void* originalData       = negative->OriginalRawFileData();
0223 
0224             if (extractOriginal)
0225             {
0226                 if (originalDataLength > 0)
0227                 {
0228                     dng_memory_allocator memalloc(gDefaultDNGMemoryAllocator);
0229                     dng_memory_stream compressedDataStream(memalloc);
0230                     compressedDataStream.Put(originalData, originalDataLength);
0231                     compressedDataStream.SetReadPosition(0);
0232                     compressedDataStream.SetBigEndian(true);
0233                     quint32 forkLength = compressedDataStream.Get_uint32();
0234                     quint32 forkBlocks = (uint32)floor((forkLength + 65535.0) / 65536.0);
0235                     QVector<quint32> offsets;
0236 
0237                     for (quint32 block = 0 ; block <= forkBlocks ; ++block)
0238                     {
0239                         quint32 offset = compressedDataStream.Get_uint32();
0240                         offsets.push_back(offset);
0241                     }
0242 
0243                     QFile originalFile(dngFileInfo.absolutePath() + QLatin1Char('/') + originalFileName);
0244                     qCDebug(DIGIKAM_TESTS_LOG) << "extracting embedded original to " << dngFileInfo.fileName();
0245 
0246                     if (!originalFile.open(QIODevice::WriteOnly))
0247                     {
0248                         qCDebug(DIGIKAM_TESTS_LOG) << "Cannot open file. Aborted...";
0249                         return 1;
0250                     }
0251 
0252                     QDataStream originalDataStream(&originalFile);
0253 
0254                     for (quint32 block = 0 ; block < forkBlocks ; ++block)
0255                     {
0256                         QByteArray compressedDataBlock;
0257                         compressedDataBlock.resize(offsets[block + 1] - offsets[block]);
0258                         compressedDataStream.Get(compressedDataBlock.data(), compressedDataBlock.size());
0259                         quint32 uncompressedDataSize = qMin((quint32)CHUNK, forkLength);
0260 
0261                         compressedDataBlock.prepend(uncompressedDataSize         & 0xFF);
0262                         compressedDataBlock.prepend((uncompressedDataSize >>  8) & 0xFF);
0263                         compressedDataBlock.prepend((uncompressedDataSize >> 16) & 0xFF);
0264                         compressedDataBlock.prepend((uncompressedDataSize >> 24) & 0xFF);
0265                         forkLength -= uncompressedDataSize;
0266 
0267                         QByteArray originalDataBlock = qUncompress((const uchar*)compressedDataBlock.data(), compressedDataBlock.size());
0268 /*
0269                         qCDebug(DIGIKAM_TESTS_LOG) << "compressed data block " << compressedDataBlock.size() << " -> " << originalDataBlock.size();
0270 */
0271                         originalDataStream.writeRawData(originalDataBlock.data(), originalDataBlock.size());
0272                     }
0273 
0274                     originalFile.close();
0275                 }
0276                 else
0277                 {
0278                     qCWarning(DIGIKAM_TESTS_LOG) << "No embedded originals RAW data found";
0279                 }
0280             }
0281         }
0282 
0283         dng_xmp_sdk::TerminateSDK();
0284 
0285         return 0;
0286     }
0287 
0288     catch (const dng_exception& exception)
0289     {
0290         int ret = exception.ErrorCode();
0291         qCCritical(DIGIKAM_TESTS_LOG) << "DNGWriter: DNG SDK exception code (" << ret << ")";
0292 
0293         return (-1);
0294     }
0295 
0296     catch (...)
0297     {
0298         qCCritical(DIGIKAM_TESTS_LOG) << "DNGWriter: DNG SDK exception code unknow";
0299 
0300         return (-1);
0301     }
0302 }