Better handle thumbnail generation of extreme aspect ratio images (fixes #3794)

* Backport fix taken from ART
* Enforce minimal thumbnail size of 1x1 px in two places, prevents divison by zero and empty image generation
This commit is contained in:
Floessie
2020-10-25 18:24:30 +01:00
committed by GitHub
parent b4f68adb64
commit 1318935a87
11 changed files with 82 additions and 57 deletions

View File

@@ -205,11 +205,6 @@ Thumbnail* Thumbnail::loadFromImage (const Glib::ustring& fname, int &w, int &h,
ImageIO* img = imgSrc.getImageIO();
// agriggio -- hotfix for #3794, to be revised once a proper solution is implemented
if (std::max(img->getWidth(), img->getHeight()) / std::min(img->getWidth(), img->getHeight()) >= 10) {
return nullptr;
}
Thumbnail* tpp = new Thumbnail ();
unsigned char* data;
@@ -235,15 +230,29 @@ Thumbnail* Thumbnail::loadFromImage (const Glib::ustring& fname, int &w, int &h,
h = img->getHeight();
tpp->scale = 1.;
} else {
if (fixwh == 1) {
if (fixwh < 0 && w > 0 && h > 0) {
const int ww = h * img->getWidth() / img->getHeight();
const int hh = w * img->getHeight() / img->getWidth();
if (ww <= w) {
w = ww;
tpp->scale = static_cast<double>(img->getHeight()) / h;
} else {
h = hh;
tpp->scale = static_cast<double>(img->getWidth()) / w;
}
} else if (fixwh == 1) {
w = h * img->getWidth() / img->getHeight();
tpp->scale = (double)img->getHeight() / h;
tpp->scale = static_cast<double>(img->getHeight()) / h;
} else {
h = w * img->getHeight() / img->getWidth();
tpp->scale = (double)img->getWidth() / w;
tpp->scale = static_cast<double>(img->getWidth()) / w;
}
}
// Precaution to prevent division by zero later on
if (h < 1) h = 1;
if (w < 1) w = 1;
// bilinear interpolation
if (tpp->thumbImg) {
delete tpp->thumbImg;
@@ -1178,6 +1187,8 @@ IImage8* Thumbnail::processImage (const procparams::ProcParams& params, eSensorT
rwidth = int (size_t (thumbImg->getWidth()) * size_t (rheight) / size_t (thumbImg->getHeight()));
}
if (rwidth < 1) rwidth = 1;
if (rheight < 1) rheight = 1;
Imagefloat* baseImg = resizeTo<Imagefloat> (rwidth, rheight, interp, thumbImg);

View File

@@ -344,8 +344,9 @@ bool BatchQueue::loadBatchQueue ()
auto job = rtengine::ProcessingJob::create (source, thumb->getType () == FT_Raw, pparams, fast);
const auto prevh = getMaxThumbnailHeight ();
const auto prevw = thumb->getThumbnailWidth(prevh, &pparams);
auto prevh = getMaxThumbnailHeight();
auto prevw = prevh;
thumb->getThumbnailSize(prevw, prevh, &pparams);
auto entry = new BatchQueueEntry (job, pparams, source, prevw, prevh, thumb, options.overwriteOutputFile);
thumb->decreaseRef (); // Removing the refCount acquired by cacheMgr->getEntry

View File

@@ -106,8 +106,12 @@ void BatchQueueEntry::refreshThumbnailImage ()
void BatchQueueEntry::calcThumbnailSize ()
{
prew = preh * origpw / origph;
if (prew > options.maxThumbnailWidth) {
const float s = static_cast<float>(options.maxThumbnailWidth) / prew;
prew = options.maxThumbnailWidth;
preh = std::max<int>(preh * s, 1);
}
}
@@ -261,9 +265,8 @@ void BatchQueueEntry::_updateImage (guint8* img, int w, int h)
MYWRITERLOCK(l, lockRW);
prew = w;
assert (preview == nullptr);
preview = new guint8 [prew * preh * 3];
memcpy (preview, img, prew * preh * 3);
preview.resize(prew * preh * 3);
std::copy(img, img + preview.size(), preview.begin());
if (parent) {
parent->redrawNeeded (this);

View File

@@ -116,9 +116,12 @@ void FileBrowserEntry::refreshQuickThumbnailImage ()
void FileBrowserEntry::calcThumbnailSize ()
{
if (thumbnail) {
prew = thumbnail->getThumbnailWidth(preh);
int ow = prew, oh = preh;
thumbnail->getThumbnailSize(prew, preh);
if (ow != prew || oh != preh || preview.size() != static_cast<std::size_t>(prew * preh * 3)) {
preview.clear();
}
}
}
@@ -255,22 +258,16 @@ void FileBrowserEntry::_updateImage(rtengine::IImage8* img, double s, const rten
bool rotated = false;
if (preh == img->getHeight()) {
const bool resize = !preview || prew != img->getWidth();
prew = img->getWidth ();
// Check if image has been rotated since last time
rotated = preview && newLandscape != landscape;
rotated = !preview.empty() && newLandscape != landscape;
if (resize) {
if (preview) {
delete [] preview;
}
preview = new guint8 [prew * preh * 3];
}
memcpy(preview, img->getData(), prew * preh * 3);
preview.resize(prew * preh * 3);
std::copy(img->getData(), img->getData() + preview.size(), preview.begin());
{
GThreadLock lock;
updateBackBuffer ();
GThreadLock lock;
updateBackBuffer ();
}
}
@@ -601,7 +598,7 @@ bool FileBrowserEntry::onArea (CursorArea a, int x, int y)
{
MYREADERLOCK(l, lockRW);
if (!drawable || !preview) {
if (!drawable || preview.empty()) {
return false;
}

View File

@@ -1228,8 +1228,9 @@ void FileCatalog::developRequested(const std::vector<FileBrowserEntry*>& tbe, bo
rtengine::ProcessingJob* pjob = rtengine::ProcessingJob::create (fbe->filename, th->getType() == FT_Raw, params, fastmode && options.fastexport_use_fast_pipeline);
const int ph = BatchQueue::calcMaxThumbnailHeight();
const int pw = th->getThumbnailWidth(ph);
int pw;
int ph = BatchQueue::calcMaxThumbnailHeight();
th->getThumbnailSize (pw, ph);
// processThumbImage is the processing intensive part, but adding to queue must be ordered
//#pragma omp ordered

View File

@@ -400,6 +400,7 @@ void Options::setDefaults()
overwriteOutputFile = false; // if TRUE, existing output JPGs/PNGs are overwritten, instead of adding ..-1.jpg, -2.jpg etc.
theme = "RawTherapee";
maxThumbnailHeight = 250;
maxThumbnailWidth = 800;
maxCacheEntries = 20000;
thumbInterp = 1;
autoSuffix = true;
@@ -1019,6 +1020,10 @@ void Options::readFromFile(Glib::ustring fname)
maxThumbnailHeight = keyFile.get_integer("File Browser", "MaxPreviewHeight");
}
if (keyFile.has_key("File Browser", "MaxPreviewWidth")) {
maxThumbnailWidth = keyFile.get_integer("File Browser", "MaxPreviewWidth");
}
if (keyFile.has_key("File Browser", "MaxCacheEntries")) {
maxCacheEntries = keyFile.get_integer("File Browser", "MaxCacheEntries");
}
@@ -2114,6 +2119,7 @@ void Options::saveToFile(Glib::ustring fname)
keyFile.set_integer("File Browser", "ThumbnailSizeQueue", thumbSizeQueue);
keyFile.set_integer("File Browser", "SameThumbSize", sameThumbSize);
keyFile.set_integer("File Browser", "MaxPreviewHeight", maxThumbnailHeight);
keyFile.set_integer("File Browser", "MaxPreviewWidth", maxThumbnailWidth);
keyFile.set_integer("File Browser", "MaxCacheEntries", maxCacheEntries);
Glib::ArrayHandle<Glib::ustring> pext = parseExtensions;
keyFile.set_string_list("File Browser", "ParseExtensions", pext);

View File

@@ -281,6 +281,7 @@ public:
CPBKeyType CPBKeys; // Custom Profile Builder's key type
int editorToSendTo;
int maxThumbnailHeight;
int maxThumbnailWidth;
std::size_t maxCacheEntries;
int thumbInterp; // 0: nearest, 1: bilinear
std::vector<Glib::ustring> parseExtensions; // List containing all extensions type

View File

@@ -135,7 +135,6 @@ ThumbBrowserEntryBase::ThumbBrowserEntryBase (const Glib::ustring& fname) :
textGap(6),
sideMargin(8),
lowerMargin(8),
preview(nullptr),
dispname(Glib::path_get_basename(fname)),
buttonSet(nullptr),
width(0),
@@ -171,7 +170,6 @@ ThumbBrowserEntryBase::ThumbBrowserEntryBase (const Glib::ustring& fname) :
ThumbBrowserEntryBase::~ThumbBrowserEntryBase ()
{
delete[] preview;
delete buttonSet;
}
@@ -207,7 +205,7 @@ void ThumbBrowserEntryBase::updateBackBuffer ()
bbSelected = selected;
bbFramed = framed;
bbPreview = preview;
bbPreview = preview.data();
Cairo::RefPtr<Cairo::Context> cc = Cairo::Context::create(surface);
@@ -237,16 +235,20 @@ void ThumbBrowserEntryBase::updateBackBuffer ()
if (buttonSet) {
int tmp;
buttonSet->getAllocatedDimensions (tmp, bsHeight);
buttonSet->getAllocatedDimensions(tmp, bsHeight);
}
int infow, infoh;
getTextSizes(infow, infoh);
// draw preview frame
//backBuffer->draw_rectangle (cc, false, (exp_width-prew)/2, upperMargin+bsHeight, prew+1, preh+1);
// draw thumbnail image
if (preview) {
if (!preview.empty()) {
prex = borderWidth + (exp_width - prew) / 2;
prey = upperMargin + bsHeight + borderWidth;
backBuffer->copyRGBCharData(preview, 0, 0, prew, preh, prew * 3, prex, prey);
const int hh = exp_height - (upperMargin + bsHeight + borderWidth + infoh + lowerMargin);
prey = upperMargin + bsHeight + borderWidth + std::max((hh - preh) / 2, 0);
backBuffer->copyRGBCharData(preview.data(), 0, 0, prew, preh, prew * 3, prex, prey);
}
customBackBufferUpdate (cc);
@@ -255,9 +257,6 @@ void ThumbBrowserEntryBase::updateBackBuffer ()
bbIcons = getIconsOnImageArea ();
bbSpecificityIcons = getSpecificityIconsOnImageArea ();
int infow, infoh;
getTextSizes (infow, infoh);
int iofs_x = 4, iofs_y = 4;
int istartx = prex;
int istarty = prey;
@@ -356,7 +355,7 @@ void ThumbBrowserEntryBase::updateBackBuffer ()
textposx_dt = 0;
}
textposy = upperMargin + bsHeight + 2 * borderWidth + preh + borderWidth + textGap;
textposy = exp_height - lowerMargin - infoh;
textw = exp_width - 2 * textGap;
if (selected) {
@@ -556,10 +555,7 @@ void ThumbBrowserEntryBase::resize (int h)
}
if (preh != old_preh || prew != old_prew) { // if new thumbnail height or new orientation
if (preview) {
delete [] preview;
preview = nullptr;
}
preview.clear();
refreshThumbnailImage ();
} else if (backBuffer) {
backBuffer->setDirty(true); // This will force a backBuffer update on queue_draw
@@ -620,7 +616,7 @@ void ThumbBrowserEntryBase::draw (Cairo::RefPtr<Cairo::Context> cc)
bbHeight = backBuffer->getHeight();
}
if (!backBuffer || selected != bbSelected || framed != bbFramed || preview != bbPreview
if (!backBuffer || selected != bbSelected || framed != bbFramed || preview.data() != bbPreview
|| exp_width != bbWidth || exp_height != bbHeight || getIconsOnImageArea () != bbIcons
|| getSpecificityIconsOnImageArea() != bbSpecificityIcons || backBuffer->isDirty())
{
@@ -680,7 +676,7 @@ rtengine::Coord2D ThumbBrowserEntryBase::getPosInImgSpace (int x, int y) const
{
rtengine::Coord2D coord(-1., -1.);
if (preview) {
if (!preview.empty()) {
x -= ofsX + startx;
y -= ofsY + starty;

View File

@@ -59,7 +59,7 @@ protected:
MyRWMutex lockRW; // Locks access to all image thumb changing actions
guint8* preview; // holds the preview image. used in updateBackBuffer. TODO Olli: Make a cache to reduce mem significantly
std::vector<guint8> preview; // holds the preview image. used in updateBackBuffer.
Glib::ustring dispname;

View File

@@ -121,7 +121,7 @@ void Thumbnail::_generateThumbnailImage ()
tpp = nullptr;
delete [] lastImg;
lastImg = nullptr;
tw = -1;
tw = options.maxThumbnailWidth;
th = options.maxThumbnailHeight;
imgRatio = -1.;
@@ -138,20 +138,20 @@ void Thumbnail::_generateThumbnailImage ()
if (ext == "jpg" || ext == "jpeg") {
infoFromImage (fname);
tpp = rtengine::Thumbnail::loadFromImage (fname, tw, th, 1, pparams->wb.equal);
tpp = rtengine::Thumbnail::loadFromImage (fname, tw, th, -1, pparams->wb.equal);
if (tpp) {
cfs.format = FT_Jpeg;
}
} else if (ext == "png") {
tpp = rtengine::Thumbnail::loadFromImage (fname, tw, th, 1, pparams->wb.equal);
tpp = rtengine::Thumbnail::loadFromImage (fname, tw, th, -1, pparams->wb.equal);
if (tpp) {
cfs.format = FT_Png;
}
} else if (ext == "tif" || ext == "tiff") {
infoFromImage (fname);
tpp = rtengine::Thumbnail::loadFromImage (fname, tw, th, 1, pparams->wb.equal);
tpp = rtengine::Thumbnail::loadFromImage (fname, tw, th, -1, pparams->wb.equal);
if (tpp) {
cfs.format = FT_Tiff;
@@ -589,10 +589,13 @@ void Thumbnail::decreaseRef ()
cachemgr->closeThumbnail (this);
}
int Thumbnail::getThumbnailWidth (const int h, const rtengine::procparams::ProcParams *pparams) const
void Thumbnail::getThumbnailSize(int &w, int &h, const rtengine::procparams::ProcParams *pparams)
{
MyMutex::MyLock lock(mutex);
int tw_ = tw;
int th_ = th;
float imgRatio_ = imgRatio;
if (pparams) {
@@ -617,10 +620,16 @@ int Thumbnail::getThumbnailWidth (const int h, const rtengine::procparams::ProcP
}
}
if (imgRatio_ > 0.f) {
return imgRatio_ * h;
if (imgRatio_ > 0.) {
w = imgRatio_ * static_cast<float>(h);
} else {
return tw_ * h / th_;
w = tw_ * h / th_;
}
if (w > options.maxThumbnailWidth) {
const float s = static_cast<float>(options.maxThumbnailWidth) / w;
w = options.maxThumbnailWidth;
h = std::max<int>(h * s, 1);
}
}

View File

@@ -119,7 +119,7 @@ public:
// unsigned char* getThumbnailImage (int &w, int &h, int fixwh=1); // fixwh = 0: fix w and calculate h, =1: fix h and calculate w
rtengine::IImage8* processThumbImage (const rtengine::procparams::ProcParams& pparams, int h, double& scale);
rtengine::IImage8* upgradeThumbImage (const rtengine::procparams::ProcParams& pparams, int h, double& scale);
int getThumbnailWidth (int h, const rtengine::procparams::ProcParams *pparams = nullptr) const;
void getThumbnailSize (int &w, int &h, const rtengine::procparams::ProcParams *pparams = nullptr);
void getFinalSize (const rtengine::procparams::ProcParams& pparams, int& w, int& h);
void getOriginalSize (int& w, int& h);