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