QImage from datastream - c++

I'm using the Qt library, creating QImages.
I'm able to use this constructor:
QImage image("example.jpg");
But I'm having trouble with this static function:
char buffer[sizeOfFile];
ifstream inFile("example.jpg");
inFile.read(buffer, sizeOfFile);
QImage image = QImage::fromData(buffer); // error here
// but there's nothing wrong with the buffer
ofstream outFile("bufferOut.jpg");
outFile.write(buffer, sizeOfFile);
Where Qt spits out to console:
Corrupt JPEG data: 1 extraneous bytes before marker 0xd9
JPEG datastream contains no image
The above isn't exactly what I have, but it's the only important difference. (I need to be able to read from a buffer because I'm opening images that are inside a zip archive.)

Tnx to peppe from #qt on irc.freenode.net:
The solution is to explicitly include the buffer length. Ignoring a few unsigned char to char typecasting and other details, what I should have used is something akin to:
QImage image = QImage::fromData(buffer, sizeOfFile);

Related

Magick++ not loading TGA blob

I have a buffer containing the data for an RLE-compressed 8-bit RGB TGA image. I want to load this into a Magick++ Image but I keep getting
Magick: no decode delegate for this image format `' # error/blob.c/BlobToImage/353
Here is my code
#include <Magick++.h>
#include <fstream>
int main(int argc, char** argv)
{
std::ifstream file("window_borders.tga", std::ios::binary | std::ios::ate);
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
char* buffer = new char[size];
if (!file.read(buffer, size)) return 1;
Magick::Blob data_blob(buffer, size);
Magick::Image m_image(data_blob);
return 0;
}
If I identify it I get
window_borders.tga TGA 330x390 330x390+0+0 8-bit sRGB 33106B 0.000u 0:00.000
Annoyingly, if I specify this info, then it works just fine. I can even convert it:
Magick::Image m_image(data_blob, Magick::Geometry("330x390"), "TGA");
m_image.magick("JPEG");
m_image.write("test.jpg");
And indeed test.jpg and window_borders.tga look identical. Why can't it detect the format automatically?
Why can't it detect the format automatically?
The TGA format never really had a unique "magick-number" header, or some other
quick+reliable way to identify if a TGA exist within a blob.
If I remember correctly, later extended version of TGA introduced the string
TRUEVISION-XFILE as a magick identifier, but at the files footer table.
I'm not an expert, but I imagine some software designers would be shaking their
heads.
Now, not only are you responsible for knowing the file format ahead of time
(by given filename), but have to fully & correctly read the image-header
to determine where the image-data stops, and the image-footer starts.
I would guess that this would be a large contributing factor into why there's no
IsTGA method like we have IsPNG, IsTIFF, and so on...
As you've previously discovered one solution.
Magick::Image m_image(data_blob, Magick::Geometry("330x390"), "TGA");
// This should work too.
Magick::Image m_image(data_blob, Magick::Geometry("0x0"), "TGA");
But you can also do the following.
Magick::image m_image;
m_image.magick("TGA");
m_image.read(data_blob);

C++, OpenCV: Fastest way to read a file containing non-ASCII characters on windows

I am writing a program using OpenCV that shall work on Windows as well as on Linux. Now the problem with OpenCV is, that its cv::imread function can not handle filepaths that contain non-ASCII characters on Windows. A workaround is to first read the file into a buffer using other libraries (for example std-libraries or Qt) and then read the file from that buffer using the cv::imdecode function. This is what I currently do. However, it's not very fast and much slower than just using cv::imread. I have a TIF image that is about 1GB in size. Reading it with cv::imread takes approx. 1s, reading it with the buffer method takes about 14s. I assume that imread just reads those parts of the TIF that are necessary for displaying the image (no layers etc.). Either this, or my code for reading a file into a buffer is bad.
Now my question is if there is a better way to do it. Either a better way with regard to OpenCV or a better way with regard to reading a file into a buffer.
I tried two different methods for the buffering, one using the std libraries and one using Qt (actually they both use QT for some things). They both are equally slow.:
Method 1
std::shared_ptr<std::vector<char>> readFileIntoBuffer(QString const& path) {
#ifdef Q_OS_WIN
std::ifstream file(path.toStdWString(), std::iostream::binary);
#else
std::ifstream file(path.toStdString(), std::iostream::binary);
#endif
if (!file.good()) {
return std::shared_ptr<std::vector<char>>(new std::vector<char>());
}
file.exceptions(std::ifstream::badbit | std::ifstream::failbit | std::ifstream::eofbit);
file.seekg(0, std::ios::end);
std::streampos length(file.tellg());
std::shared_ptr<std::vector<char>> buffer(new std::vector<char>(static_cast<std::size_t>(length)));
if (static_cast<std::size_t>(length) == 0) {
return std::shared_ptr<std::vector<char>>(new std::vector<char>());
}
file.seekg(0, std::ios::beg);
try {
file.read(buffer->data(), static_cast<std::size_t>(length));
} catch (...) {
return std::shared_ptr<std::vector<char>>(new std::vector<char>());
}
file.close();
return buffer;
}
And then for reading the image from the buffer:
std::shared_ptr<std::vector<char>> buffer = utility::readFileIntoBuffer(path);
cv::Mat image = cv::imdecode(*buffer, cv::IMREAD_UNCHANGED);
Method 2
QByteArray readFileIntoBuffer(QString const & path) {
QFile file(path);
if (!file.open(QIODevice::ReadOnly)) {
return QByteArray();
}
return file.readAll();
}
And for decoding the image:
QByteArray buffer = utility::readFileIntoBuffer(path);
cv::Mat matBuffer(1, buffer.size(), CV_8U, buffer.data());
cv::Mat image = cv::imdecode(matBuffer, cv::IMREAD_UNCHANGED);
UPDATE
Method 3
This method maps the file into memory using QFileDevice::map and then uses cv::imdecode.
QFile file(path);
file.open(QIODevice::ReadOnly);
unsigned char * fileContent = file.map(0, file.size(), QFileDevice::MapPrivateOption);
cv::Mat matBuffer(1, file.size(), CV_8U, fileContent);
cv::Mat image = cv::imdecode(matBuffer, cv::IMREAD_UNCHANGED);
However, also this approach didn't result in a shorter time than the other two. I also did some time measurements and found that reading the file in the memory or mapping it to the memory is actually not the bottleneck. The operation that takes the majority of the time is the cv::imdecode. I don't know why this is the case, since using cv::imread with the same image only takes a fraction of the time.
Potential Workaround
I tried obtaining an 8.3 pathname on Windows for files that contain non-ascii characters using the following code:
QString getShortPathname(QString const & path) {
#ifndef Q_OS_WIN
return QString();
#else
long length = 0;
WCHAR* buffer = nullptr;
length = GetShortPathNameW(path.toStdWString().c_str(), nullptr, 0);
if (length == 0) return QString();
buffer = new WCHAR[length];
length = GetShortPathNameW(path.toStdWString().c_str(), buffer, length);
if (length == 0) {
delete[] buffer;
return QString();
}
QString result = QString::fromWCharArray(buffer);
delete[] buffer;
return result;
#endif
}
However, I had to find out that 8.3 pathname generation is disabled on my machine, so it potentially is on others as well. So I wasn't able to test this yet and it does not seem to provide a reliable workaround. I also have the problem that the function doesn't tell me that 8.3 pathname generation is disabled.
There is an open ticket on this in OpenCV GitHub: https://github.com/opencv/opencv/issues/4292
One of the comments there suggest a workaround without reading the whole file to memory by using memory-mapped file (with help from Boost):
mapped_file map(path(L"filename"), ios::in);
Mat file(1, numeric_cast<int>(map.size()), CV_8S, const_cast<char*>(map.const_data()), CV_AUTOSTEP);
Mat image(imdecode(file, 1));

Saving a QImage to QBuffer

I am doing something like this:
QImage image(width, height, QImage::Format_RGB32);
frame.fill(QColor(255, 255, 255).rgb());
QBuffer buffer;
buffer.open(QBuffer::ReadWrite);
QDataStream out(&buffer);
Option 1:
out << image;
Option 2:
out.writeRawData((char *) image.constBits(), image.byteCount()) ;
Option 1 is pretty slow and I am not sure if Option 2 is the correct way to do?
You can use QImage::save to write directly to a QIODevice, be it a buffer or a file.
image.save(buffer);
Option 2 looks pretty gross compared to Option 1; I would certainly prefer Option 1 aesthetically. But I would prefer the API I mentioned over both the options you give.
You can read more about image read/write here.
I have just done something similar
QByteArray byteArray;
QBuffer buffer(&byteArray);
image.save(&buffer, "PNG"); // writes the image in PNG format inside the buffer
To output the buffer I would use a QString, I used this for converting to base64
QString imgBase64 = QString::fromLatin1(byteArray.toBase64().data());

Create a QByteArray from a QImage and vice-versa

I need to encode an already-created QImage into a QByteArray to send to a socket, and decode it on the another side.
On the server side, I'm trying to do something like:
// The vector is mandatory. The class that creates the image line expects it.
QVector<unsigned char> msg;
QImage line(create_image_line(msg));
QByteArray ba((char*)line.bits(), line.numBytes());
for (int i = 0; i < ba.size(); ++i) {
msg.append(ba[i]);
}
send_msg(msg);
The create_image_line does something like:
... handle the msg and get the image properties...
QImage img(pixels_, 1, QImage::Format_RGB32);
... set the values ...
return(img);
And on the client side:
receive_msg(msg);
QByteArray ba;
for (int i = 0; i < msg.size(); ++i) {
ba.append(msg[i]);
}
QImage line(LINE_WIDTH, 1, QImage::Format_RGB32);
line.fromData(ba);
Something goes wrong, and the image is shown with a lot of noise (I know that the problem is located in this conversion, due another successful tests).
I'd like to know where is the problem.
Rgds.
QImage::fromData does not retain the format, it tries to probe for a file header. It's also a static function, so it does not modify line (which stays uninitialized), it returns an image (which you discard). And it's concent of format is things like PNG or JPG, not pixel formats like the constructor.
So to do the way you have it now, you need to loop through line.bits again copying in the pixels.
However, QDataStream can serialize most of the Qt value types, including QImage. If you're in control of both ends and open to changing the protocol, this is probably a much simpler and robust solution.

How to transfer larger objects through a socket in QT?

I would like to send/recieve image files and 2 ints as messages in a client server program.
I'm using QLocalSocket and QImage for this.
However I don't know how to read from the socket only after the image and the integers are fully written to the buffer, since the readyRead signal is already fired after the first couple of bytes.
Here's parts of my code:
// sending
QDataStream stream(socket);
stream << image << i << j;
// recieving
void MainWindow::readyRead() {
// ...
if (socket->bytesAvailable() > 400)
{
QByteArray b = socket->readAll();
QDataStream stream(&b, QIODevice::ReadOnly);
QImage image;
int i, j;
stream >> image >> i >> j;
// ...
}
}
I tried guessing the incoming file size, but since QImage is serialized to PNG the data size is variable and sometimes the end of the file doesn't get written to the buffer before I start to read it.
Is there an easy solution to this?
I would send a fixed size header first that describes the data being sent, specifically the type and size in bytes.
Then as you receive readReady events you pull whatever data is available into a buffer. Once you determine you have received all of the necessary data, you can stream it into a QImage object.
The BMP format has size information and PNG format has size information for each chunk. These are formats with what QImage serializes.
If you don't want to extract the information from raw data then serialize QImage first to QBuffer (so you know/control size and format better). Then stream that size and buffer.
Code example:
QBuffer buffer;
image.save(&buffer, "PNG", 100); //can change the compression level to suit the application - see http://qt-project.org/doc/qt-4.8/qimage.html#save
qint64 length = sizeof(quint32) + buffer.data().size(); //http://doc.qt.digia.com/4.7/datastreamformat.html
stream << length;
stream << buffer.data();
Then on the other end, first stream out the qint64 length so you know how big socket->bytesAvailable() has to be to stream out the full QByteArray. Then:
QByteArray ba;
stream >> ba;
QImage image = QImage::fromData(ba); // Get image from buffer data