////////////////////////////////////////////////////////////////////////////// // // FINDDUPPLICATEIMAGES.CPP // // Copyright (C) 2001 Richard Groult (from ShowImg project) // Copyright (C) 2004 Gilles Caulier // Copyright (C) 2004 Richard Groult // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. // ////////////////////////////////////////////////////////////////////////////// #include "fuzzycompare.h" #include "actions.h" #include #include #include #include "imagesimilaritydata.h" #include #include #include "finddupplicateimages.h" #include #include #include #include #include KIPIFindDupplicateImagesPlugin::FuzzyCompare::FuzzyCompare( TQObject* parent, const TQString& cacheDir ) :m_parent( parent ), m_cacheDir( cacheDir ) { } TQDict < TQPtrVector < TQFile > > KIPIFindDupplicateImagesPlugin::FuzzyCompare::compare( const TQStringList& filesList ) { sendMessage( m_parent, KIPIFindDupplicateImagesPlugin::Progress, TQString(), filesList.count()*2, true, false ); kdDebug( 51000 ) << filesList.count() << " images to parse with Almost method..." << endl; TQDict < TQPtrVector < TQFile > > res; TQPtrVector < ImageSimilarityData > *listRatW = new TQPtrVector < ImageSimilarityData >; TQPtrVector < ImageSimilarityData > *listRatH = new TQPtrVector < ImageSimilarityData >; TQPtrVector < ImageSimilarityData > *list; listRatW->setAutoDelete(true); listRatH->setAutoDelete(true); TQTime debut=TQTime::currentTime (); ImageSimilarityData *is; for ( TQStringList::ConstIterator item = filesList.begin() ; item != filesList.end() ; ++item ) { if ( m_stopRequested ) return TQDict < TQPtrVector < TQFile > >(); TQString itemName(*item); TQFileInfo fi(itemName); TQString Temp = fi.dirPath(); TQString albumName = Temp.section('/', -1); sendMessage( m_parent, KIPIFindDupplicateImagesPlugin::Matrix, itemName, 0, true, false ); if( (is = image_sim_fill_data( itemName )) != NULL ) { if ( is->ratio > 1 ) list = listRatW; else list = listRatH; list->resize (list->size () + 1); list->insert (list->size () - 1, is ); // sendMessage( m_parent, KIPIFindDupplicateImagesPlugin::Matrix, itemName, 0, false, true ); } else sendMessage( m_parent, KIPIFindDupplicateImagesPlugin::Matrix, itemName, 0, false, false ); } kdDebug( 51000 ) << "Matrix creation time:" << debut.msecsTo(TQTime::currentTime()) << endl; debut = TQTime::currentTime (); TQDict < TQFile > *fait = new TQDict < TQFile >; list = listRatW; bool done = false; while( list != NULL ) { if (list->size () != 1) { for (unsigned int i = 0; i < list->size (); i++) { if ( m_stopRequested ) return TQDict < TQPtrVector < TQFile > >(); // Create the 'ImageSimilarityData' data for the first image. ImageSimilarityData *i1 = list->at(i); if (i1 && !fait->find(i1->filename)) { sendMessage( m_parent, KIPIFindDupplicateImagesPlugin::Similar, i1->filename, 0, true, false ); for (unsigned int j = i + 1; j < list->size (); j++) { // Create the 'ImageSimilarityData' data for the second image. ImageSimilarityData *i2 = list->at(j); // Real images file comparison calculation. float eq = image_sim_compare_fast(i1, i2, m_approximateLevel); if (eq >= m_approximateLevel) // the files are the same ! { TQPtrVector < TQFile > *vect; // Add file to the list. if (!res.find (i1->filename)) { vect = new TQPtrVector < TQFile >; vect->setAutoDelete(true); res.insert (i1->filename, vect); } else vect = (TQPtrVector < TQFile > *)res.find(i1->filename); vect->resize (vect->size () + 1); vect->insert (vect->size () - 1, new TQFile(i2->filename)); fait->insert(i2->filename, new TQFile(i2->filename)); } } } //sendMessage( m_parent, KIPIFindDupplicateImagesPlugin::Similar, i1->filename, 0, false, true ); } } if(!done) { list = listRatH; done = true; } else list = NULL; } kdDebug( 51000 ) << "Comparison time: " << debut.msecsTo(TQTime::currentTime()) << endl; // End of comparison process. delete(fait); delete(listRatH); delete(listRatW); return res; } ///////////////////////////////////////////////////////////////////////////////////////////// // Nota: original source code from ShowImg ! KIPIFindDupplicateImagesPlugin::ImageSimilarityData* KIPIFindDupplicateImagesPlugin::FuzzyCompare::image_sim_fill_data(TQString filename) { int w, h; uchar *pix; int has_alpha; int p_step; int i,j; int x_inc, y_inc; int xs, ys; const int INC=1; TQImage *pixbuf; ImageSimilarityData *sd = new ImageSimilarityData(); sd->filename=filename; TQFileInfo info(m_cacheDir + TQFileInfo(filename).absFilePath()+".dat"); if(info.exists()) { TQFile f(m_cacheDir+TQFileInfo(filename).absFilePath()+".dat"); if ( f.open(IO_ReadOnly) ) { TQDataStream s( &f ); s >> sd->ratio; for(int i=0 ; i> sd->avg_r[i]; for(int i=0 ; i> sd->avg_g[i]; for(int i=0 ; i> sd->avg_b[i]; f.close(); } sd->filled = true; return sd; } pixbuf = new TQImage(filename); if ( !sd || !pixbuf ) return 0L; KImageEffect::equalize(*pixbuf); w = pixbuf->width(); h = pixbuf->height(); pix = pixbuf->bits(); has_alpha = pixbuf->hasAlphaBuffer(); p_step = has_alpha ? 4 : 3; x_inc = w / PAS; y_inc = h / PAS; if ( x_inc < 1 || y_inc < 1 ) return 0L; j = 0; for (ys = 0; ys < PAS; ys++) { i = 0; for (xs = 0; xs < PAS; xs++) { int x, y; int r, g, b; r = g = b = 0; for (y = j; y < j + y_inc; y+=INC) { for (x = i; x < i + x_inc; x+=INC) { r += getRed(pixbuf, x, y); g += getGreen(pixbuf, x, y); b += getBlue(pixbuf, x, y); } } r /= x_inc * y_inc; g /= x_inc * y_inc; b /= x_inc * y_inc; sd->avg_r[ys * PAS + xs] = r; sd->avg_g[ys * PAS + xs] = g; sd->avg_b[ys * PAS + xs] = b; i += x_inc; } j += y_inc; } sd->filled = true; sd->ratio=((float)w)/h; delete(pixbuf); // Saving the data. TQFile f(m_cacheDir+TQFileInfo(filename).absFilePath()+".dat"); TDEStandardDirs::makeDir(TQFileInfo(f).dirPath(true)); if ( f.open(IO_WriteOnly) ) { TQDataStream s( &f ); s << sd->ratio; for(int i=0 ; iavg_r[i]; for(int i=0 ; iavg_g[i]; for(int i=0 ; iavg_b[i]; f.close(); } return sd; } ///////////////////////////////////////////////////////////////////////////////////////////// // Nota: original source code from ShowImg ! float KIPIFindDupplicateImagesPlugin::FuzzyCompare::image_sim_compare_fast(ImageSimilarityData *a, ImageSimilarityData *b, float min) { float sim; int i, j; if ( !a || !b || !a->filled || !b->filled ) return 0.0; if( fabs(a->ratio - b->ratio) > 0.1 ) return 0.0; min = 1.0 - min; sim = 0.0; for ( j = 0; j < PAS*PAS; j+= PAS ) { for ( i = j; i < j + PAS; i++ ) { sim += (float)abs(a->avg_r[i] - b->avg_r[i]) / 255.0; sim += (float)abs(a->avg_g[i] - b->avg_g[i]) / 255.0; sim += (float)abs(a->avg_b[i] - b->avg_b[i]) / 255.0; } // check for abort, if so return 0.0 if ( j > PAS*PAS/3 && 1-sim/((j+1) * 3.0) < min ) return 0.0; } sim /= (PAS*PAS * 3.0); return 1.0 - sim; } ///////////////////////////////////////////////////////////////////////////////////////////// // Nota: original source code from ShowImg ! char KIPIFindDupplicateImagesPlugin::FuzzyCompare::getRed(TQImage* im, int x, int y) { return tqRed(im->pixel(x, y)); } ///////////////////////////////////////////////////////////////////////////////////////////// // Nota: original source code from ShowImg ! char KIPIFindDupplicateImagesPlugin::FuzzyCompare::getGreen(TQImage* im, int x, int y) { return tqGreen(im->pixel(x, y)); } ///////////////////////////////////////////////////////////////////////////////////////////// // Nota: original source code from ShowImg ! char KIPIFindDupplicateImagesPlugin::FuzzyCompare::getBlue(TQImage* im, int x, int y) { return tqBlue(im->pixel(x, y)); }