You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
345 lines
10 KiB
345 lines
10 KiB
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// FINDDUPPLICATEIMAGES.CPP
|
|
//
|
|
// Copyright (C) 2001 Richard Groult <rgroult at jalix.org> (from ShowImg project)
|
|
// Copyright (C) 2004 Gilles Caulier <caulier dot gilles at gmail dot com>
|
|
// Copyright (C) 2004 Richard Groult <rgroult at jalix.org>
|
|
//
|
|
// 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 <tqstringlist.h>
|
|
#include <tqapplication.h>
|
|
#include <kdebug.h>
|
|
#include "imagesimilaritydata.h"
|
|
#include <tqdatetime.h>
|
|
#include <tqfileinfo.h>
|
|
#include "finddupplicateimages.h"
|
|
#include <tqimage.h>
|
|
#include <kimageio.h>
|
|
#include <kimageeffect.h>
|
|
#include <kstandarddirs.h>
|
|
#include <math.h>
|
|
|
|
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<PAS*PAS ; i++) s >> sd->avg_r[i];
|
|
for(int i=0 ; i<PAS*PAS ; i++) s >> sd->avg_g[i];
|
|
for(int i=0 ; i<PAS*PAS ; i++) s >> 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");
|
|
KStandardDirs::makeDir(TQFileInfo(f).dirPath(true));
|
|
|
|
if ( f.open(IO_WriteOnly) )
|
|
{
|
|
TQDataStream s( &f );
|
|
s << sd->ratio;
|
|
for(int i=0 ; i<PAS*PAS ; i++) s << sd->avg_r[i];
|
|
for(int i=0 ; i<PAS*PAS ; i++) s << sd->avg_g[i];
|
|
for(int i=0 ; i<PAS*PAS ; i++) s << sd->avg_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));
|
|
}
|
|
|
|
|