Converting images to 64base is too slow.
-
@IgKh
The number of images contained in one file can be larger. So file size is important. I confirmed that when using webp, the size is reduced to about 2/3 compared to jpeg.Thanks.
@MyNameIsQt so if the source file is a media container of some sort, that already contains WebP encoded frames, why round trip through
QImage
? You are decoding and then encoding for nothing, and paying through the nose for it. Just take the byte buffer you used to construct the QImage and pass it as is to the web frame.If the transcoding serves some purpose of doing image manipulation before display, that it was originally WebP doesn't mean it has to stay like that. The size benefit doesn't matter, it is all in RAM, and even a 100% quality PNG won't be significantly bigger than the fully expanded pixmap that is QImage.
-
Then cache the encoded images instead re-encoding them every time.
Also 2/3 sounds much but what's the actual size in bytes?@Christian-Ehrlicher Now I have 100 random images in jpg and webp format saved in one file.
The results are as follows:
jpg: 30.633.851 bytes
webp: 25.845.760 bytesFile compression rate varies depending on what image save.
-
So we really talk about 5MB more ram in contrast to slow loading speed. No need to discuss this further.
-
@MyNameIsQt so if the source file is a media container of some sort, that already contains WebP encoded frames, why round trip through
QImage
? You are decoding and then encoding for nothing, and paying through the nose for it. Just take the byte buffer you used to construct the QImage and pass it as is to the web frame.If the transcoding serves some purpose of doing image manipulation before display, that it was originally WebP doesn't mean it has to stay like that. The size benefit doesn't matter, it is all in RAM, and even a 100% quality PNG won't be significantly bigger than the fully expanded pixmap that is QImage.
@IgKh
I know what you mean and I tried it.
The following function is the original function.QString WPubManager::getImgObject() { if (m_pBkgnd && m_pBkgnd->loadImageData(m_file)){ const QImage& image = m_pBkgnd->getBkgndImg(); qDebug() << "3 size" << image.width(); if (image.isNull()) { qDebug() << "error: image is null"; return QString(); } else { qDebug() << "Image dimensions: " << image.width() << image.height(); } QByteArray byteArray; QBuffer buffer(&byteArray); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "WEBP"); QString base64ImageData = QString::fromLatin1(byteArray.toBase64()); qDebug() << "Base64 image data size: " << base64ImageData.size(); buffer.close(); return base64ImageData; } else { qDebug() << "error: m_pBkgnd is null"; return QString(); } }
The key here is tobase64: Images without this part processed will not be drawn on the canvas.
QString base64ImageData = QString::fromLatin1(byteArray.toBase64());
Only after that part is processed can the next statement be processed in js.
// js var img = new Image(); img.src = "data:image/webp;base64," + imageData;
As you said, I sent several types of image data to js without using QImage.
But in any case, the toBase64() function must be processed. To do that, I don't know any other way than to go through QImage. -
So we really talk about 5MB more ram in contrast to slow loading speed. No need to discuss this further.
@Christian-Ehrlicher
The saved image now contains many small thumbnails. For small thumbnails, there isn't much difference in compression rates between webp and jpg.Additionally, the results shown now show that webp has a 25% more efficient compression rate. 5MB is not a big thing, but if 10,000 users upload files simultaneously to the web server (that I created), the amount of traffic or the physical size that occupies the server will not be negligible.
( * I had a pleasant fantasy of 10,000 people using the web I created at the same time. ha ha ha...)
-
@IgKh
I know what you mean and I tried it.
The following function is the original function.QString WPubManager::getImgObject() { if (m_pBkgnd && m_pBkgnd->loadImageData(m_file)){ const QImage& image = m_pBkgnd->getBkgndImg(); qDebug() << "3 size" << image.width(); if (image.isNull()) { qDebug() << "error: image is null"; return QString(); } else { qDebug() << "Image dimensions: " << image.width() << image.height(); } QByteArray byteArray; QBuffer buffer(&byteArray); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "WEBP"); QString base64ImageData = QString::fromLatin1(byteArray.toBase64()); qDebug() << "Base64 image data size: " << base64ImageData.size(); buffer.close(); return base64ImageData; } else { qDebug() << "error: m_pBkgnd is null"; return QString(); } }
The key here is tobase64: Images without this part processed will not be drawn on the canvas.
QString base64ImageData = QString::fromLatin1(byteArray.toBase64());
Only after that part is processed can the next statement be processed in js.
// js var img = new Image(); img.src = "data:image/webp;base64," + imageData;
As you said, I sent several types of image data to js without using QImage.
But in any case, the toBase64() function must be processed. To do that, I don't know any other way than to go through QImage.@MyNameIsQt I'm not questioning the need to pass the data to Javascript as Base-64 encoded text. But you can Base-64 encode any
QByteArray
...Would you mind sharing how the
QImage
got created in yourgetBkgndImg()
method? It should have been read from a byte array / binary buffer of some kind, and if that encodes a valid WebP image, directly Base-64 printing it should be sufficient.To keep one step ahead, it is possible that the original image data is not actually WebP, or not valid enough in a way that Qt's image format plugin can handle but Chromium can't. You can double check by looking at the result of calling
QImageReader::imageFormat
on whatever you created the QImage from. -
@MyNameIsQt I'm not questioning the need to pass the data to Javascript as Base-64 encoded text. But you can Base-64 encode any
QByteArray
...Would you mind sharing how the
QImage
got created in yourgetBkgndImg()
method? It should have been read from a byte array / binary buffer of some kind, and if that encodes a valid WebP image, directly Base-64 printing it should be sufficient.To keep one step ahead, it is possible that the original image data is not actually WebP, or not valid enough in a way that Qt's image format plugin can handle but Chromium can't. You can double check by looking at the result of calling
QImageReader::imageFormat
on whatever you created the QImage from.@IgKh
getBkgndImg is just an inline function.inline const QImage &getBkgndImg() const { if (m_pBkgndImage) { return *m_pBkgndImage; } else { static const QImage emptyImage; return emptyImage; } }
The image data saved in the file is created using google's decodeWebp and encodeWebp functions. These work well. Checked in all browsers.
Base64 to Webp text
Webp
Thanks so much.. -
@MyNameIsQt I'm not questioning the need to pass the data to Javascript as Base-64 encoded text. But you can Base-64 encode any
QByteArray
...Would you mind sharing how the
QImage
got created in yourgetBkgndImg()
method? It should have been read from a byte array / binary buffer of some kind, and if that encodes a valid WebP image, directly Base-64 printing it should be sufficient.To keep one step ahead, it is possible that the original image data is not actually WebP, or not valid enough in a way that Qt's image format plugin can handle but Chromium can't. You can double check by looking at the result of calling
QImageReader::imageFormat
on whatever you created the QImage from.// CBackground.h ... QByteArray m_decodedArray; uint8_t* m_decodedData; size_t m_imageSize = 0; .... inline const uchar* getDecodedData() const { return m_decodedData; // QByteArray 변환 없이 원시 데이터 포인터를 반환 } // CBackground.cpp bool CBackground::decodeWebp(QFile *pFile) { long imageSize = m_bkgndHeader.ImageSize; long imageAddress = getImageAddress(); if (!pFile->seek(imageAddress)) { throw std::runtime_error("Failed to seek to image data"); return false; } QByteArray imageData(imageSize, 0); if (pFile->read(imageData.data(), imageSize) != imageSize) { throw std::runtime_error("Failed to read image data"); return false; } int width = 0, height = 0; m_decodedData = WebPDecodeRGBA(reinterpret_cast<uchar*>(imageData.data()), imageSize, &width, &height); if (!m_decodedData) { throw std::runtime_error("Failed to decode WebP image"); return false; } m_imageSize = width * height * 4; retirm trie; } // wemar.cpp QString Wemar::getImgObject() { if (m_pBkgnd && m_pBkgnd->loadImageData(m_file)){ const uchar* decodedData = m_pBkgnd->getDecodedData(); QByteArray webpData(reinterpret_cast<const char*>(decodedData), m_pBkgnd->getImageSize()); if (webpData.isEmpty()) { qDebug() << "error: image data is empty"; return QString(); } else { qDebug() << "Base64 before: " << webpData.size(); } QString base64ImageData = QString::fromLatin1(webpData.toBase64()); qDebug() << "Base64 After: " << base64ImageData.size(); return base64ImageData; } else { qDebug() << "error: m_pBkgnd is null"; return QString(); } } /// js new QWebChannel(qt.webChannelTransport, function(channel) { wpubManager = channel.objects.wpubManager; .... async function handleImage() { var bkgndImageData = await wpubManager.getImgObject(); // Promise 완료를 기다림 if (bkgndImageData) { console.error("image good"); drawCanvas(bkgndImageData); } else { console.error("Failed to get background image data."); } } .... // in drawCanvas function var img = new Image(); img.src = "data:image/webp;base64," + imageData; img.onerror = function(){ console.error("Image failed to load"); }; img.onload = function() { canvas.width = img.width; canvas.height = img.height; context.fillStyle = "#d4d4d4"; context.fillRect(0, 0, canvas.width, canvas.height); context.drawImage(img, 0, 0, img.width, img.height); };
-
// CBackground.h ... QByteArray m_decodedArray; uint8_t* m_decodedData; size_t m_imageSize = 0; .... inline const uchar* getDecodedData() const { return m_decodedData; // QByteArray 변환 없이 원시 데이터 포인터를 반환 } // CBackground.cpp bool CBackground::decodeWebp(QFile *pFile) { long imageSize = m_bkgndHeader.ImageSize; long imageAddress = getImageAddress(); if (!pFile->seek(imageAddress)) { throw std::runtime_error("Failed to seek to image data"); return false; } QByteArray imageData(imageSize, 0); if (pFile->read(imageData.data(), imageSize) != imageSize) { throw std::runtime_error("Failed to read image data"); return false; } int width = 0, height = 0; m_decodedData = WebPDecodeRGBA(reinterpret_cast<uchar*>(imageData.data()), imageSize, &width, &height); if (!m_decodedData) { throw std::runtime_error("Failed to decode WebP image"); return false; } m_imageSize = width * height * 4; retirm trie; } // wemar.cpp QString Wemar::getImgObject() { if (m_pBkgnd && m_pBkgnd->loadImageData(m_file)){ const uchar* decodedData = m_pBkgnd->getDecodedData(); QByteArray webpData(reinterpret_cast<const char*>(decodedData), m_pBkgnd->getImageSize()); if (webpData.isEmpty()) { qDebug() << "error: image data is empty"; return QString(); } else { qDebug() << "Base64 before: " << webpData.size(); } QString base64ImageData = QString::fromLatin1(webpData.toBase64()); qDebug() << "Base64 After: " << base64ImageData.size(); return base64ImageData; } else { qDebug() << "error: m_pBkgnd is null"; return QString(); } } /// js new QWebChannel(qt.webChannelTransport, function(channel) { wpubManager = channel.objects.wpubManager; .... async function handleImage() { var bkgndImageData = await wpubManager.getImgObject(); // Promise 완료를 기다림 if (bkgndImageData) { console.error("image good"); drawCanvas(bkgndImageData); } else { console.error("Failed to get background image data."); } } .... // in drawCanvas function var img = new Image(); img.src = "data:image/webp;base64," + imageData; img.onerror = function(){ console.error("Image failed to load"); }; img.onload = function() { canvas.width = img.width; canvas.height = img.height; context.fillStyle = "#d4d4d4"; context.fillRect(0, 0, canvas.width, canvas.height); context.drawImage(img, 0, 0, img.width, img.height); };
@MyNameIsQt Thanks!
In this bit:
QByteArray imageData(imageSize, 0); if (pFile->read(imageData.data(), imageSize) != imageSize) { throw std::runtime_error("Failed to read image data"); return false; }
Unless something funny is going on, it should sufficient to call
QString::fromLatin1(imageData.toBase64())
after that point and send the result over to the web frame.(P.S no need to return after throwing, the return is unreachable as exceptions propagate over an alternative control flow)
-
@MyNameIsQt Thanks!
In this bit:
QByteArray imageData(imageSize, 0); if (pFile->read(imageData.data(), imageSize) != imageSize) { throw std::runtime_error("Failed to read image data"); return false; }
Unless something funny is going on, it should sufficient to call
QString::fromLatin1(imageData.toBase64())
after that point and send the result over to the web frame.(P.S no need to return after throwing, the return is unreachable as exceptions propagate over an alternative control flow)
@IgKh You didn't show me a direct solution to the problem, but you showed me the right approach to solving the problem. Using qimage as you mentioned is unnecessary. I need to focus on the issue of base64 encoding binary data. thank you
-
C Christian Ehrlicher referenced this topic on