File indexing completed on 2025-01-12 10:34:21
0001 /* This file is part of the KDE project 0002 SPDX-FileCopyrightText: 2009 KO GmbH <jos.van.den.oever@kogmbh.com> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 #include "pictures.h" 0007 0008 #include <KoStore.h> 0009 #include <KoXmlWriter.h> 0010 0011 #include <zlib.h> 0012 #include <cstdio> 0013 #include <iostream> 0014 #include <QDebug> 0015 #include <QImage> 0016 #include <QBuffer> 0017 0018 //#define DEBUG_PICTURES 0019 0020 // Use anonymous namespace to cover following functions 0021 namespace 0022 { 0023 0024 static inline quint16 readU16(const void* p) 0025 { 0026 const unsigned char* ptr = (const unsigned char*) p; 0027 return ptr[0] + (ptr[1] << 8); 0028 } 0029 0030 static inline quint32 readU32(const void* p) 0031 { 0032 const unsigned char* ptr = (const unsigned char*) p; 0033 return ptr[0] + (ptr[1] << 8) + (ptr[2] << 16) + (ptr[3] << 24); 0034 } 0035 0036 void saveStream(POLE::Stream& stream, quint32 size, KoStore* out) 0037 { 0038 const quint16 bufferSize = 1024; 0039 unsigned char buffer[bufferSize]; 0040 unsigned long nread = stream.read(buffer, 0041 (bufferSize < size) ? bufferSize : size); 0042 while (nread > 0) { 0043 out->write((char*)buffer, nread); 0044 size -= nread; 0045 nread = stream.read(buffer, (bufferSize < size) ? bufferSize : size); 0046 } 0047 } 0048 0049 bool saveDecompressedStream(POLE::Stream& stream, quint32 size, KoStore* out) 0050 { 0051 const quint16 bufferSize = 1024; 0052 unsigned char bufin[bufferSize]; 0053 unsigned char bufout[bufferSize]; 0054 0055 // initialize for decompressing ZLIB format 0056 z_stream_s zstream; 0057 zstream.zalloc = Z_NULL; 0058 zstream.zfree = Z_NULL; 0059 zstream.opaque = Z_NULL; 0060 zstream.avail_in = 0; 0061 zstream.next_in = Z_NULL; 0062 int r = inflateInit(&zstream); 0063 if (r != Z_OK) { 0064 inflateEnd(&zstream); 0065 return false; 0066 } 0067 0068 unsigned long nread = stream.read(bufin, 0069 (bufferSize < size) ? bufferSize : size); 0070 while (nread > 0) { // loop over the available data 0071 size -= nread; 0072 zstream.next_in = (Bytef*)bufin; 0073 zstream.avail_in = nread; 0074 do { // loop until the data in bufin has all been decompressed 0075 zstream.next_out = (Bytef*)bufout; 0076 zstream.avail_out = bufferSize; 0077 int r = inflate(&zstream, Z_SYNC_FLUSH); 0078 qint32 nwritten = bufferSize - zstream.avail_out; 0079 if (r != Z_STREAM_END && r != Z_OK) { 0080 inflateEnd(&zstream); 0081 return false; 0082 } 0083 out->write((char*)bufout, nwritten); 0084 if (r == Z_STREAM_END) { 0085 inflateEnd(&zstream); 0086 return true; // successfully reached the end 0087 } 0088 } while (zstream.avail_in > 0); 0089 nread = stream.read(bufin, (bufferSize < size) ? bufferSize : size); 0090 } 0091 0092 inflateEnd(&zstream); 0093 return false; // the stream was incomplete 0094 } 0095 0096 const char* getMimetype(quint16 type) 0097 { 0098 switch (type) { 0099 case officeArtBlipEMF: return "image/x-emf"; 0100 case officeArtBlipWMF: return "image/x-wmf"; 0101 case officeArtBlipPICT: return "image/pict"; 0102 case officeArtBlipJPEG: return "image/jpeg"; 0103 case officeArtBlipPNG: return "image/png"; 0104 case officeArtBlipDIB: return "application/octet-stream"; 0105 case officeArtBlipTIFF: return "image/tiff"; 0106 case officeArtBlipJPEG2: return "image/jpeg"; 0107 } 0108 return ""; 0109 } 0110 0111 const char* getSuffix(quint16 type) 0112 { 0113 switch (type) { 0114 case officeArtBlipEMF: return ".emf"; 0115 case officeArtBlipWMF: return ".wmf"; 0116 case officeArtBlipPICT: return ".pict"; 0117 case officeArtBlipJPEG: return ".jpg"; 0118 case officeArtBlipPNG: return ".png"; 0119 case officeArtBlipDIB: return ".dib"; 0120 case officeArtBlipTIFF: return ".tiff"; 0121 case officeArtBlipJPEG2: return ".jpg"; 0122 } 0123 return ""; 0124 } 0125 0126 template<class T> void savePicture(PictureReference& ref, const T* a, KoStore* out) 0127 { 0128 if (!a) return; 0129 ref.uid = a->rgbUid1 + a->rgbUid2; 0130 ref.name.clear(); 0131 0132 QByteArray imagePixelBytes = a->BLIPFileData; 0133 if (a->rh.recType == officeArtBlipDIB) { 0134 // convert to QImage 0135 QImage image; 0136 bool result = dibToBmp(image, imagePixelBytes.data(), imagePixelBytes.size()); 0137 if (!result) { 0138 return; // empty name reports an error 0139 } 0140 0141 // writes image into ba in PNG format 0142 QByteArray ba; 0143 QBuffer buffer(&ba); 0144 buffer.open(QIODevice::WriteOnly); 0145 result = image.save(&buffer, "PNG"); 0146 if (!result) { 0147 return; // empty name reports an error 0148 } 0149 0150 imagePixelBytes = ba; 0151 // save as png 0152 ref.name = ref.uid.toHex() + getSuffix(officeArtBlipPNG); 0153 ref.mimetype = getMimetype(officeArtBlipPNG); 0154 } else { 0155 ref.name = ref.uid.toHex() + getSuffix(a->rh.recType); 0156 ref.mimetype = getMimetype(a->rh.recType); 0157 } 0158 0159 if (!out->open(ref.name.toLocal8Bit())) { 0160 ref.name.clear(); 0161 ref.uid.clear(); 0162 return; // empty name reports an error 0163 } 0164 0165 out->write(imagePixelBytes.data(), imagePixelBytes.size()); 0166 out->close(); 0167 } 0168 0169 template<class T> void saveDecompressedPicture(PictureReference& ref, const T* a, KoStore* store) 0170 { 0171 if (!a) return; 0172 0173 QByteArray buff = a->BLIPFileData; 0174 bool compressed = a->metafileHeader.compression == 0; 0175 0176 if (compressed) { 0177 quint32 cbSize = a->metafileHeader.cbSize; 0178 char tmp[4]; 0179 0180 //big-endian byte order required 0181 tmp[3] = (cbSize & 0x000000ff); 0182 tmp[2] = ((cbSize >> 8) & 0x0000ff); 0183 tmp[1] = ((cbSize >> 16) & 0x00ff); 0184 tmp[0] = (cbSize >> 24); 0185 buff.prepend((char*) tmp, 4); 0186 buff = qUncompress(buff); 0187 0188 if ((uint)buff.size() != cbSize) { 0189 qDebug() << "Warning: uncompressed size of the metafile differs"; 0190 } 0191 } 0192 //reuse the savePicture code 0193 ref.uid = a->rgbUid1 + a->rgbUid2; 0194 ref.name = ref.uid.toHex() + getSuffix(a->rh.recType); 0195 if (!store->open(ref.name.toLocal8Bit())) { 0196 ref.name.clear(); 0197 ref.uid.clear(); 0198 return; // empty name reports an error 0199 } 0200 store->write(buff.data(), buff.size()); 0201 ref.mimetype = getMimetype(a->rh.recType); 0202 store->close(); 0203 } 0204 0205 PictureReference savePicture(const MSO::OfficeArtBlip& a, KoStore* store) 0206 { 0207 PictureReference ref; 0208 store->setCompressionEnabled(true); 0209 // only one of these calls will actually save a picture 0210 saveDecompressedPicture(ref, a.anon.get<MSO::OfficeArtBlipEMF>(), store); 0211 saveDecompressedPicture(ref, a.anon.get<MSO::OfficeArtBlipWMF>(), store); 0212 saveDecompressedPicture(ref, a.anon.get<MSO::OfficeArtBlipPICT>(), store); 0213 // formats below are typically not very compressible 0214 store->setCompressionEnabled(false); 0215 savePicture(ref, a.anon.get<MSO::OfficeArtBlipJPEG>(), store); 0216 savePicture(ref, a.anon.get<MSO::OfficeArtBlipPNG>(), store); 0217 savePicture(ref, a.anon.get<MSO::OfficeArtBlipDIB>(), store); 0218 savePicture(ref, a.anon.get<MSO::OfficeArtBlipTIFF>(), store); 0219 return ref; 0220 } 0221 } //namespace 0222 0223 PictureReference savePicture(POLE::Stream& stream, KoStore* out) 0224 { 0225 PictureReference ref; 0226 const quint16 bufferSize = 1024; 0227 unsigned char buffer[bufferSize]; 0228 if (stream.read(buffer, 8) != 8) return ref; 0229 0230 quint16 instance = readU16(buffer) >> 4; 0231 quint16 type = readU16(buffer + 2); 0232 quint32 size = readU32(buffer + 4); 0233 0234 if (type == 0xF007) { // OfficeArtFBSE 0235 if (stream.read(buffer, 36) != 36) return ref; 0236 quint16 cbName = *(buffer + 33); 0237 if (cbName > bufferSize || stream.read(buffer, cbName) != cbName) { 0238 return ref; 0239 } 0240 size = size - 36 - cbName; 0241 // read embedded BLIP 0242 if (stream.read(buffer, 8) != 8) return ref; 0243 instance = readU16(buffer) >> 4; 0244 type = readU16(buffer + 2); 0245 size = readU32(buffer + 4); 0246 } 0247 0248 // Image data is stored raw in the Pictures stream 0249 // The offset to the data differs per image type. 0250 quint16 offset; 0251 switch (type) { 0252 case officeArtBlipEMF: offset = (instance == 0x3D4) ? 50 : 66; break; 0253 case officeArtBlipWMF: offset = (instance == 0x216) ? 50 : 66; break; 0254 case officeArtBlipPICT: offset = (instance == 0x542) ? 50 : 66; break; 0255 case officeArtBlipJPEG: offset = (instance == 0x46A) ? 17 : 33; break; 0256 case officeArtBlipPNG: offset = (instance == 0x6E0) ? 17 : 33; break; 0257 case officeArtBlipDIB: offset = (instance == 0x7A8) ? 17 : 33; break; 0258 case officeArtBlipTIFF: offset = (instance == 0x6E4) ? 17 : 33; break; 0259 case officeArtBlipJPEG2: offset = (instance == 0x46A) ? 17 : 33; break; 0260 default: return ref; 0261 } 0262 const char* namesuffix = getSuffix(type); 0263 ref.mimetype = getMimetype(type); 0264 0265 // skip offset 0266 if (offset != 0 && stream.read(buffer, offset) != offset) return ref; 0267 size -= offset; 0268 0269 bool compressed = false; 0270 if (type == officeArtBlipEMF || type == officeArtBlipWMF || type == officeArtBlipPICT) { 0271 // read the compressed field from the OfficeArtMetafileHeader 0272 compressed = buffer[offset-2] == 0; 0273 } 0274 ref.uid = QByteArray((const char*)buffer, 16); 0275 ref.name = ref.uid.toHex() + namesuffix; 0276 if (!out->open(ref.name.toLocal8Bit())) { 0277 ref.name.clear(); 0278 ref.uid.clear(); 0279 return ref; // empty name reports an error 0280 } 0281 unsigned long next = stream.tell() + size; 0282 if (compressed) { 0283 saveDecompressedStream(stream, size, out); 0284 } else { 0285 saveStream(stream, size, out); 0286 } 0287 stream.seek(next); 0288 out->close(); 0289 0290 return ref; 0291 } 0292 0293 PictureReference savePicture(const MSO::OfficeArtBStoreContainerFileBlock& a, KoStore* store) 0294 { 0295 const MSO::OfficeArtBlip* blip = a.anon.get<MSO::OfficeArtBlip>(); 0296 const MSO::OfficeArtFBSE* fbse = a.anon.get<MSO::OfficeArtFBSE>(); 0297 if (blip) { 0298 return savePicture(*blip, store); 0299 } 0300 if (fbse && fbse->embeddedBlip) { 0301 return savePicture(*fbse->embeddedBlip, store); 0302 } 0303 return PictureReference(); 0304 } 0305 0306 QByteArray getRgbUid(const MSO::OfficeArtDggContainer& dgg, quint32 pib, quint32& offset) 0307 { 0308 quint32 n = pib - 1; 0309 // return 16 byte rgbuid for this given blip id 0310 if (dgg.blipStore) { 0311 const MSO::OfficeArtBStoreContainer* b = dgg.blipStore.data(); 0312 if (n < (quint32) b->rgfb.size() && 0313 b->rgfb[n].anon.is<MSO::OfficeArtFBSE>()) 0314 { 0315 const MSO::OfficeArtFBSE* fbse = b->rgfb[n].anon.get<MSO::OfficeArtFBSE>(); 0316 #ifdef DEBUG_PICTURES 0317 qDebug() << "rgfb.size():" << b->rgfb.size(); 0318 qDebug() << "pib:" << pib; 0319 qDebug() << "OfficeArtFBSE: DEBUG"; 0320 qDebug() << "rgbUid:" << fbse->rgbUid.toHex(); 0321 qDebug() << "tag:" << fbse->tag; 0322 qDebug() << "cRef:" << fbse->cRef; 0323 qDebug() << "foDelay:" << fbse->foDelay; 0324 qDebug() << "embeddeBlip:" << fbse->embeddedBlip; 0325 #endif 0326 offset = fbse->foDelay; 0327 return fbse->rgbUid; 0328 } 0329 } 0330 if (pib != 0xFFFF && pib != 0) { 0331 #ifdef DEBUG_PICTURES 0332 qDebug() << "Could not find image for pib " << pib; 0333 #endif 0334 } 0335 return QByteArray(); 0336 } 0337 0338 QMap<QByteArray, QString> createPictures(KoStore* store, KoXmlWriter* manifest, const QList<MSO::OfficeArtBStoreContainerFileBlock>* rgfb) 0339 { 0340 PictureReference ref; 0341 QMap<QByteArray, QString> fileNames; 0342 0343 if (!rgfb) return fileNames; 0344 0345 foreach (const MSO::OfficeArtBStoreContainerFileBlock& block, *rgfb) { 0346 ref = savePicture(block, store); 0347 0348 if (ref.name.length() == 0) { 0349 #ifdef DEBUG_PICTURES 0350 qDebug() << "Empty picture reference, probably an empty slot"; 0351 #endif 0352 continue; 0353 } 0354 //check if the MD4 digest is up2date 0355 if (block.anon.is<MSO::OfficeArtFBSE>()) { 0356 const MSO::OfficeArtFBSE* fbse = block.anon.get<MSO::OfficeArtFBSE>(); 0357 if (fbse->rgbUid != ref.uid) { 0358 ref.uid = fbse->rgbUid; 0359 } 0360 } 0361 0362 if (manifest) { 0363 manifest->addManifestEntry("Pictures/" + ref.name, ref.mimetype); 0364 } 0365 0366 fileNames[ref.uid] = ref.name; 0367 } 0368 #ifdef DEBUG_PICTURES 0369 qDebug() << "fileNames: DEBUG"; 0370 QMap<QByteArray, QString>::const_iterator i = fileNames.constBegin(); 0371 while (i != fileNames.constEnd()) { 0372 qDebug() << i.key().toHex() << ": " << i.value(); 0373 ++i; 0374 } 0375 #endif 0376 return fileNames; 0377 } 0378 0379 0380 // NOTE: copied from qwmf.cc, patched to use QDataStream to prevent alignment issues 0381 0382 typedef struct { 0383 char bmType[2]; 0384 qint32 bmSize; 0385 qint16 bmReserved1; 0386 qint16 bmReserved2; 0387 qint32 bmOffBits; 0388 } BMPFILEHEADER; 0389 static const int BMPFILEHEADER_SIZE = 14; 0390 0391 static QDataStream &operator<<(QDataStream &s, const BMPFILEHEADER &header) 0392 { 0393 s.writeRawData(header.bmType, 2); 0394 s << header.bmSize << header.bmReserved1 << header.bmReserved2 << header.bmOffBits; 0395 return s; 0396 } 0397 0398 bool dibToBmp(QImage& bmp, const char* dib, long int size) 0399 { 0400 int sizeBmp = size + BMPFILEHEADER_SIZE; 0401 QByteArray pattern; // BMP header and DIB data 0402 pattern.reserve(sizeBmp); 0403 QDataStream stream(&pattern, QIODevice::WriteOnly); 0404 0405 // Build a BMP header 0406 BMPFILEHEADER bmpHeader; 0407 bmpHeader.bmType[0] = 'B'; 0408 bmpHeader.bmType[1] = 'M'; 0409 bmpHeader.bmSize = sizeBmp; 0410 0411 stream << bmpHeader; 0412 stream.writeRawData(dib, size); 0413 0414 if (!bmp.loadFromData(pattern, "BMP")) { 0415 qDebug() << "dibToBmp: invalid bitmap"; 0416 return false; 0417 } else { 0418 return true; 0419 } 0420 }