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 }