QZipReader对ZIP文件读取非常方便好用。即使在最新版的QT 6.6.1里,仍然存在一些问题:对于大于2G的zip文件不支持。
虽然有标准zlib可调用,但包装成一个易用且功能成熟的zip解压功能库,还是有很大的工作量,也需要有一定的经验。
于是,直接找到QT的QZipReader相关的源码文件,单独做成一个Compress工具包,方便BUG调试,其次,将来再整合进7z解压等功能。
把原来的两个头文件和一个Cpp文件,整合成两个文件:QZip.h和QZip.cpp。
把QZipReader改名为ZipReader,以避免与QT自带的头文件和DLL产生冲突。
代码工程结构如下图:
代码调试过程,就不截图了。主要修改了两个方法:
1、void ZipReaderPrivate::scanFiles()
把这些变量的类型由int纠正为正确的数据型,这就是不支持超过2G的zip文件原因。
qint64 i = 0;
uint start_of_directory = -1;
ushort num_dir_entries = 0;
说明原来代码还是有一些基本规范质量问题,也不知道是有意的,还是无意的,或者是发现问题了,但没有人手去分析与修改。
2、QByteArray ZipReader::fileData(const QString &fileName)
问题跟前一个方法一样,把一些变量的类型纠正为正确的数据型。
修改后的源代码:
void ZipReaderPrivate::scanFiles()
void ZipReaderPrivate::scanFiles()
{
if (!dirtyFileTree)
return;
if (! (device->isOpen() || device->open(QIODevice::ReadOnly))) {
status = ZipReader::FileOpenError;
return;
}
if ((device->openMode() & QIODevice::ReadOnly) == 0) { // only read the index from readable files.
status = ZipReader::FileReadError;
return;
}
dirtyFileTree = false;
uchar tmp[4];
device->read((char *)tmp, 4);
if (readUInt(tmp) != 0x04034b50) {
qWarning("QZip: not a zip file!");
return;
}
// find EndOfDirectory header
qint64 i = 0;
uint start_of_directory = -1;
ushort num_dir_entries = 0;
EndOfDirectory eod;
while (true) {
const qint64 pos = device->size() - qint64(sizeof(EndOfDirectory)) - i;
if (pos < 0 || i > 65535) {
debugx("Zip: EndOfDirectory not found。 ");
return;
}
device->seek(pos);
device->read((char *)&eod, sizeof(EndOfDirectory));
if (readUInt(eod.signature) == 0x06054b50)
break;
++i;
}
// have the eod
start_of_directory = readUInt(eod.dir_start_offset);
num_dir_entries = readUShort(eod.num_dir_entries);
debugx("start_of_directory at %u, num_dir_entries=%d", start_of_directory, num_dir_entries);
int comment_length = readUShort(eod.comment_length);
if (comment_length != i)
debugx("QZip: failed to parse zip file.");
comment = device->read(qMin(comment_length, i));
device->seek(start_of_directory);
for (i = 0; i < num_dir_entries; ++i) {
FileHeader header;
auto read = device->read((char *) &header.h, sizeof(CentralFileHeader));
if (read < (qint64)sizeof(CentralFileHeader)) {
debugx("QZip: Failed to read complete header, index may be incomplete,read:%lld,num_dir_entries:%d",read,num_dir_entries);
break;
}
if (readUInt(header.h.signature) != 0x02014b50) {
debugx("QZip: invalid header signature, index may be incomplete");
break;
}
int l = readUShort(header.h.file_name_length);
header.file_name = device->read(l);
if (header.file_name.size() != l) {
debugx("QZip: Failed to read filename from zip index, index may be incomplete");
break;
}
l = readUShort(header.h.extra_field_length);
header.extra_field = device->read(l);
if (header.extra_field.size() != l) {
debugx("QZip: Failed to read extra field in zip file, skipping file, index may be incomplete");
break;
}
l = readUShort(header.h.file_comment_length);
header.file_comment = device->read(l);
if (header.file_comment.size() != l) {
debugx("QZip: Failed to read read file comment, index may be incomplete");
break;
}
// debugx("found file '%s'", header.file_name.data());
fileHeaders.append(header);
}
}
QByteArray ZipReader::fileData(const QString &fileName)
QByteArray ZipReader::fileData(const QString &fileName) const
{
d->scanFiles();
int i;
for (i = 0; i < d->fileHeaders.size(); ++i) {
if (QString::fromLocal8Bit(d->fileHeaders.at(i).file_name) == fileName)
break;
}
if (i == d->fileHeaders.size())
return QByteArray();
FileHeader header = d->fileHeaders.at(i);
ushort version_needed = readUShort(header.h.version_needed);
if (version_needed > ZIP_VERSION) {
debugx("QZip: .ZIP specification version %d implementationis needed to extract the data.", version_needed);
return QByteArray();
}
ushort general_purpose_bits = readUShort(header.h.general_purpose_bits);
uint compressed_size = readUInt(header.h.compressed_size);
uint uncompressed_size = readUInt(header.h.uncompressed_size);
uint start = readUInt(header.h.offset_local_header);
//qDebug("uncompressing file %d: local header at %d", i, start);
d->device->seek(start);
LocalFileHeader lh;
d->device->read((char *)&lh, sizeof(LocalFileHeader));
uint skip = readUShort(lh.file_name_length) + readUShort(lh.extra_field_length);
d->device->seek(d->device->pos() + skip);
ushort compression_method = readUShort(lh.compression_method);
//qDebug("file=%s: compressed_size=%d, uncompressed_size=%d", fileName.toLocal8Bit().data(), compressed_size, uncompressed_size);
if ((general_purpose_bits & Encrypted) != 0) {
debugx("QZip: Unsupported encryption method is needed to extract the data.");
return QByteArray();
}
//qDebug("file at %lld", d->device->pos());
QByteArray compressed = d->device->read(compressed_size);
if (compression_method == CompressionMethodStored) {
// no compression
compressed.truncate(uncompressed_size);
return compressed;
} else if (compression_method == CompressionMethodDeflated) {
// Deflate
//qDebug("compressed=%d", compressed.size());
compressed.truncate(compressed_size);
QByteArray baunzip;
ulong len = qMax(uncompressed_size, 1u);
int res;
do {
baunzip.resize(len);
res = inflate((uchar*)baunzip.data(), &len,
(const uchar*)compressed.constData(), compressed_size);
switch (res) {
case Z_OK:
if ((int)len != baunzip.size())
baunzip.resize(len);
break;
case Z_MEM_ERROR:
debugx("QZip: Z_MEM_ERROR: Not enough memory");
break;
case Z_BUF_ERROR:
len *= 2;
break;
case Z_DATA_ERROR:
debugx("QZip: Z_DATA_ERROR: Input data is corrupted");
break;
}
} while (res == Z_BUF_ERROR);
return baunzip;
}
debugx("Zip: Unsupported compression method %d is needed to extract the data.", compression_method);
return QByteArray();
}
调用示例:(功能,直接读取zip包里的指定的图像文件的内容)
Mat readImage(QString filePath,bool isZip, int flags)
{
if(isZip)
{
auto index= filePath.indexOf(".zip");
QString zipFile=filePath.left(index+4);
QString imgName=filePath.right(filePath.length()-index-5);
// debugx2(zipFile<<"\n"<<imgName);
ZipReader zipreader(zipFile);
for(auto fileInfo : zipreader.fileInfoList()){
if(fileInfo.isFile){
if(fileInfo.filePath==imgName)
{
debugx2(fileInfo.filePath);
//注意编码问题
// QByteArray dt = fileInfo.filePath.toUtf8();
// QString strtemp = QString::fromLocal8Bit(dt);
QByteArray array = zipreader.fileData(fileInfo.filePath);
// debugx2(array.size());
// imshow("image",image);
zipreader.close();
if(array.size()>0)
return imdecode(std::vector<char>(array.constData(),array.constData()+array.size()),flags);
else
{
debugx2(zipFile<<" read fail: "<<imgName);
return Mat();
}
}
}
}
zipreader.close();
return Mat();
}
Mat src = cv::imread(filePath.toLocal8Bit().data(),flags);
return src;
}
插件运行效果:不用解压zip,就可以直接浏览zip包里的图片。