diff --git a/retroshare-gui/src/util/HandleRichText.cpp b/retroshare-gui/src/util/HandleRichText.cpp index 75b3c327d..558552d19 100644 --- a/retroshare-gui/src/util/HandleRichText.cpp +++ b/retroshare-gui/src/util/HandleRichText.cpp @@ -1123,7 +1123,8 @@ bool RsHtml::makeEmbeddedImage(const QImage &originalImage, QString &embeddedIma { RsScopeTimer s("Embed image"); QImage opt; - ImageUtil::optimizeSize(embeddedImage, originalImage, opt, maxPixels, 6000); + ImageUtil::optimizeSize(embeddedImage, originalImage, opt, maxPixels, 6000-500); + // -500 bytes: keep space for html stuff } QString RsHtml::plainText(const QString &text) diff --git a/retroshare-gui/src/util/imageutil.cpp b/retroshare-gui/src/util/imageutil.cpp index 5b5ab56d1..a926f770a 100644 --- a/retroshare-gui/src/util/imageutil.cpp +++ b/retroshare-gui/src/util/imageutil.cpp @@ -53,7 +53,7 @@ void ImageUtil::optimizeSize(QString &html, const QImage& original, QImage &opti //nothing to do if it fits into the limits optimized = original; if ((maxPixels <= 0) || (optimized.width()*optimized.height() <= maxPixels)) { - if(checkSize(html, optimized, maxBytes)) { + if(checkSize(html, optimized, maxBytes) <= maxBytes) { return; } } @@ -62,43 +62,67 @@ void ImageUtil::optimizeSize(QString &html, const QImage& original, QImage &opti quantization(original, ct); //Downscale the image to fit into maxPixels - qreal scale = qSqrt((qreal)(maxPixels) / original.width() / original.height()); - if(scale > 1.0) scale = 1.0; + double whratio = (qreal)original.width() / (qreal)original.height(); + int maxwidth; + if(maxPixels > 0) + maxwidth = qSqrt((qreal)(maxPixels) * whratio); + else + maxwidth = original.width(); - //Half the resolution until image fits into maxBytes, or width becomes 0 + int minwidth = qSqrt(100.0 * whratio); + + //Use binary search to find a suitable image size + linear regression to guess the file size + double maxsize = (double)checkSize(html, original.scaledToWidth(maxwidth, Qt::SmoothTransformation).convertToFormat(QImage::Format_Indexed8, ct), maxBytes); + if(maxsize <= maxBytes) return; //success + double minsize = (double)checkSize(html, original.scaledToWidth(minwidth, Qt::SmoothTransformation).convertToFormat(QImage::Format_Indexed8, ct), maxBytes); + if(minsize > maxBytes) return; //impossible + +// std::cout << "maxS: " << maxsize << " minS: " << minsize << std::endl; +// std::cout << "maxW: " << maxwidth << " minW: " << minwidth << std::endl; + int region = 500; bool success = false; do { - optimized = original.scaledToWidth((int)(original.width() * scale), Qt::SmoothTransformation).convertToFormat(QImage::Format_Indexed8, ct); - success = checkSize(html, optimized, maxBytes); - scale = scale / 2.0; - } while((!optimized.isNull()) && !success); + double m = (maxsize - minsize) / ((double)maxwidth * (double)maxwidth / whratio - (double)minwidth * (double)minwidth / whratio); + double b = maxsize - m * ((double)maxwidth * (double)maxwidth / whratio); + double a = ((double)(maxBytes - region/2) - b) / m; //maxBytes - region/2 target the center of the accepted region + int nextwidth = qSqrt((qreal)(a * whratio)); + double nextsize = (double)checkSize(html, original.scaledToWidth(nextwidth, Qt::SmoothTransformation).convertToFormat(QImage::Format_Indexed8, ct), maxBytes); + if(nextsize <= maxBytes) { + minsize = nextsize; + minwidth = nextwidth; + if(nextsize >= (maxBytes - region)) //the file size is close anough to the limit + success = true; + } else { + maxsize = nextsize; + maxwidth = nextwidth; + } +// std::cout << "maxS: " << maxsize << " minS: " << minsize << std::endl; +// std::cout << "maxW: " << maxwidth << " minW: " << minwidth << std::endl; + } while(!success); + + //html = html.arg(original.width()); + //std::cout << html.toStdString() << std::endl; } -bool ImageUtil::checkSize(QString &embeddedImage, const QImage &img, int maxBytes) +int ImageUtil::checkSize(QString &embeddedImage, const QImage &img, int maxBytes) { RsScopeTimer st("Check size"); - //0 means no limit - if(maxBytes > 500) - maxBytes -= 500; //make place for html stuff - else if(maxBytes > 0) { - std::cerr << QString("Limit is too small nothing will fit in, limit: %1 bytes\n").arg(maxBytes).toStdString(); - return false; - } QByteArray bytearray; QBuffer buffer(&bytearray); - bool success = false; + int size = 0; std::cout << QString("Trying image: format PNG, size %1x%2, colors %3\n").arg(img.width()).arg(img.height()).arg(img.colorCount()).toStdString(); if (buffer.open(QIODevice::WriteOnly)) { if (img.save(&buffer, "PNG", 0)) { - if((maxBytes > 0) && (bytearray.length() * 4/3 > maxBytes)) // *4/3 for base64 + size = bytearray.length() * 4/3; + if((maxBytes > 0) && (size > maxBytes)) // *4/3 for base64 { std::cout << QString("\tToo large, size: %1, limit: %2 bytes\n").arg(bytearray.length() * 4/3).arg(maxBytes).toStdString(); }else{ std::cout << QString("\tOK, size: %1, limit: %2 bytes\n").arg(bytearray.length() * 4/3).arg(maxBytes).toStdString(); - success = true; QByteArray encodedByteArray = bytearray.toBase64(); + //embeddedImage = ""); @@ -111,7 +135,7 @@ bool ImageUtil::checkSize(QString &embeddedImage, const QImage &img, int maxByte } else { std::cerr << "ImageUtil: buffer can't be opened" << std::endl; } - return success; + return size; } bool redLessThan(const QRgb &c1, const QRgb &c2) diff --git a/retroshare-gui/src/util/imageutil.h b/retroshare-gui/src/util/imageutil.h index f6ed4f30a..f84142e2d 100644 --- a/retroshare-gui/src/util/imageutil.h +++ b/retroshare-gui/src/util/imageutil.h @@ -14,7 +14,7 @@ public: static void optimizeSize(QString &html, const QImage& original, QImage &optimized, int maxPixels = -1, int maxBytes = -1); private: - static bool checkSize(QString& embeddedImage, const QImage& img, int maxBytes = -1); + static int checkSize(QString& embeddedImage, const QImage& img, int maxBytes = -1); static void quantization(const QImage& img, QVector& palette); static void quantization(QList::iterator begin, QList::iterator end, int depth, QVector& palette); static void avgbucket(QList::iterator begin, QList::iterator end, QVector& palette);