From: dirk Date: Sat, 6 Sep 2014 21:59:07 +0000 (+0000) Subject: Added PerceptualHash to Magick++. X-Git-Tag: 7.0.1-0~2040 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=e316d154e7b050fab78dbf86b33f0797594939ca;p=imagemagick Added PerceptualHash to Magick++. --- diff --git a/Magick++/lib/Image.cpp b/Magick++/lib/Image.cpp index 67a633327..22e5fbc8f 100644 --- a/Magick++/lib/Image.cpp +++ b/Magick++/lib/Image.cpp @@ -3627,6 +3627,11 @@ void Magick::Image::perceptibleChannel(const ChannelType channel_, ThrowPPException; } + Magick::ImagePerceptualHash Magick::Image::perceptualHash() +{ + return(ImagePerceptualHash(constImage())); +} + void Magick::Image::ping(const std::string &imageSpec_) { MagickCore::Image diff --git a/Magick++/lib/Magick++/Image.h b/Magick++/lib/Magick++/Image.h index 0d3c5a7c6..e81afd12b 100644 --- a/Magick++/lib/Magick++/Image.h +++ b/Magick++/lib/Magick++/Image.h @@ -1070,6 +1070,9 @@ namespace Magick void perceptible(const double epsilon_); void perceptibleChannel(const ChannelType channel_,const double epsilon_); + // Returns the perceptual hash for this image. + Magick::ImagePerceptualHash perceptualHash(); + // Ping is similar to read except only enough of the image is read // to determine the image columns, rows, and filesize. Access the // columns(), rows(), and fileSize() attributes after invoking @@ -1290,6 +1293,7 @@ namespace Magick // Spread pixels randomly within image by specified ammount void spread(const size_t amount_=3); + // Returns the statistics for this image. Magick::ImageStatistics statistics(); // Add a digital watermark to the image (based on second image) diff --git a/Magick++/lib/Magick++/Include.h b/Magick++/lib/Magick++/Include.h index 0e35a6dc1..c8fbb7924 100644 --- a/Magick++/lib/Magick++/Include.h +++ b/Magick++/lib/Magick++/Include.h @@ -1247,6 +1247,7 @@ namespace Magick using MagickCore::GetImageInfo; using MagickCore::GetImageInfoFile; using MagickCore::GetImageOption; + using MagickCore::GetImagePerceptualHash; using MagickCore::GetAuthenticPixels; using MagickCore::GetImageProfile; using MagickCore::GetImageProperty; diff --git a/Magick++/lib/Magick++/Statistic.h b/Magick++/lib/Magick++/Statistic.h index 3be0734c5..dad76dffb 100644 --- a/Magick++/lib/Magick++/Statistic.h +++ b/Magick++/lib/Magick++/Statistic.h @@ -64,8 +64,8 @@ namespace Magick const MagickCore::ChannelMoments *channelMoments_); private: - std::vector _huInvariants; PixelChannel _channel; + std::vector _huInvariants; double _centroidX; double _centroidY; double _ellipseAxisX; @@ -75,6 +75,55 @@ namespace Magick double _ellipseIntensity; }; + class MagickPPExport ChannelPerceptualHash + { + public: + + // Default constructor + ChannelPerceptualHash(void); + + // Copy constructor + ChannelPerceptualHash(const ChannelPerceptualHash &channelPerceptualHash_); + + // Constructor using the specified hash string + ChannelPerceptualHash(const PixelChannel channel_, + const std::string &hash_); + + // Destroy channel perceptual hash + ~ChannelPerceptualHash(void); + + // Return hash string + operator std::string() const; + + // The channel + PixelChannel channel(void) const; + + // Does object contain valid channel perceptual hash? + bool isValid() const; + + // Returns the sum squared difference between this hash and the other hash + double sumSquaredDifferences( + const ChannelPerceptualHash &channelPerceptualHash_); + + // SRGB hu preceptual hash (valid range for index is 0-6) + double srgbHuPhash(const size_t index_) const; + + // HCLp hu preceptual hash (valid range for index is 0-6) + double hclpHuPhash(const size_t index_) const; + + // + // Implemementation methods + // + + ChannelPerceptualHash(const PixelChannel channel_, + const MagickCore::ChannelPerceptualHash *channelPerceptualHash_); + + private: + PixelChannel _channel; + std::vector _srgbHuPhash; + std::vector _hclpHuPhash; + }; + // Obtain image statistics. Statistics are normalized to the range // of 0.0 to 1.0 and are output to the specified ImageStatistics // structure. @@ -185,6 +234,44 @@ namespace Magick std::vector _channels; }; + class MagickPPExport ImagePerceptualHash + { + public: + + // Default constructor + ImagePerceptualHash(void); + + // Copy constructor + ImagePerceptualHash(const ImagePerceptualHash &imagePerceptualHash_); + + // Constructor using the specified hash string + ImagePerceptualHash(const std::string &hash_); + + // Destroy image perceptual hash + ~ImagePerceptualHash(void); + + // Return hash string + operator std::string() const; + + // Returns the perceptual hash for the specified channel + ChannelPerceptualHash channel(const PixelChannel channel_) const; + + // Does object contain valid perceptual hash? + bool isValid() const; + + // Returns the sum squared difference between this hash and the other hash + double sumSquaredDifferences( + const ImagePerceptualHash &channelPerceptualHash_); + + // + // Implemementation methods + // + ImagePerceptualHash(const MagickCore::Image *image_); + + private: + std::vector _channels; + }; + class MagickPPExport ImageStatistics { public: diff --git a/Magick++/lib/Statistic.cpp b/Magick++/lib/Statistic.cpp index e5d5dbb2b..8d76b7b8f 100644 --- a/Magick++/lib/Statistic.cpp +++ b/Magick++/lib/Statistic.cpp @@ -109,13 +109,172 @@ Magick::ChannelMoments::ChannelMoments(const PixelChannel channel_, _ellipseEccentricity(channelMoments_->ellipse_eccentricity), _ellipseIntensity(channelMoments_->ellipse_intensity) { - size_t + register ssize_t i; for (i=0; i<8; i++) _huInvariants.push_back(channelMoments_->I[i]); } +Magick::ChannelPerceptualHash::ChannelPerceptualHash(void) + : _channel(SyncPixelChannel), + _srgbHuPhash(7), + _hclpHuPhash(7) +{ +} + +Magick::ChannelPerceptualHash::ChannelPerceptualHash( + const ChannelPerceptualHash &channelPerceptualHash_) + : _channel(channelPerceptualHash_._channel), + _srgbHuPhash(channelPerceptualHash_._srgbHuPhash), + _hclpHuPhash(channelPerceptualHash_._hclpHuPhash) +{ +} + +Magick::ChannelPerceptualHash::ChannelPerceptualHash( + const PixelChannel channel_,const std::string &hash_) + : _channel(channel_), + _srgbHuPhash(7), + _hclpHuPhash(7) +{ + register ssize_t + i; + + if (hash_.length() != 70) + throw ErrorOption("Invalid hash length"); + + for (i=0; i<14; i++) + { + unsigned long + hex; + + double + value; + + if (sscanf(hash_.substr(i*5,5).c_str(),"%05x",&hex) != 1) + throw ErrorOption("Invalid hash value"); + + value=((unsigned short)hex) / pow(10, (hex >> 17)); + if (hex & (1 << 16)) + value=-value; + if (i < 7) + _srgbHuPhash[i]=value; + else + _hclpHuPhash[i-7]=value; + } +} + +Magick::ChannelPerceptualHash::~ChannelPerceptualHash(void) +{ +} + +Magick::ChannelPerceptualHash::operator std::string() const +{ + std::string + hash; + + register ssize_t + i; + + if (!isValid()) + return(std::string()); + + for (i=0; i<14; i++) + { + char + buffer[6]; + + double + value; + + unsigned long + hex; + + if (i < 7) + value=_srgbHuPhash[i]; + else + value=_hclpHuPhash[i-7]; + + hex=0; + while(hex < 7 && fabs(value*10) < 65536) + { + value=value*10; + hex++; + } + + hex=(hex<<1); + if (value < 0.0) + hex|=1; + hex=(hex<<16)+(unsigned long)(value < 0.0 ? -(value - 0.5) : value + 0.5); + (void) FormatLocaleString(buffer,6,"%05x",hex); + hash+=std::string(buffer); + } + return(hash); +} + +Magick::PixelChannel Magick::ChannelPerceptualHash::channel() const +{ + return(_channel); +} + +bool Magick::ChannelPerceptualHash::isValid() const +{ + return(_channel != SyncPixelChannel); +} + +double Magick::ChannelPerceptualHash::sumSquaredDifferences( + const ChannelPerceptualHash &channelPerceptualHash_) +{ + double + ssd; + + register ssize_t + i; + + ssd=0.0; + for (i=0; i<7; i++) + { + ssd+=((_srgbHuPhash[i]-channelPerceptualHash_._srgbHuPhash[i])* + (_srgbHuPhash[i]-channelPerceptualHash_._srgbHuPhash[i])); + ssd+=((_hclpHuPhash[i]-channelPerceptualHash_._hclpHuPhash[i])* + (_hclpHuPhash[i]-channelPerceptualHash_._hclpHuPhash[i])); + } + return(ssd); +} + +double Magick::ChannelPerceptualHash::srgbHuPhash(const size_t index_) const +{ + if (index_ > 6) + throw ErrorOption("Valid range for index is 0-6"); + + return(_srgbHuPhash.at(index_)); +} + +double Magick::ChannelPerceptualHash::hclpHuPhash(const size_t index_) const +{ + if (index_ > 6) + throw ErrorOption("Valid range for index is 0-6"); + + return(_hclpHuPhash.at(index_)); +} + +Magick::ChannelPerceptualHash::ChannelPerceptualHash( + const PixelChannel channel_, + const MagickCore::ChannelPerceptualHash *channelPerceptualHash_) + : _channel(channel_), + _srgbHuPhash(7), + _hclpHuPhash(7) +{ + register ssize_t + i; + + for (i=0; i<7; i++) + { + _srgbHuPhash[i]=channelPerceptualHash_->srgb_hu_phash[i]; + _hclpHuPhash[i]=channelPerceptualHash_->hclp_hu_phash[i]; + } +} + Magick::ChannelStatistics::ChannelStatistics(void) : _channel(SyncPixelChannel), _area(0.0), @@ -309,6 +468,128 @@ Magick::ImageMoments::ImageMoments(const MagickCore::Image *image) ThrowPPException; } +Magick::ImagePerceptualHash::ImagePerceptualHash(void) + : _channels() +{ +} + +Magick::ImagePerceptualHash::ImagePerceptualHash( + const ImagePerceptualHash &imagePerceptualHash_) + : _channels(imagePerceptualHash_._channels) +{ +} + +Magick::ImagePerceptualHash::ImagePerceptualHash(const std::string &hash_) + : _channels() +{ + if (hash_.length() != 210) + throw ErrorOption("Invalid hash length"); + + _channels.push_back(Magick::ChannelPerceptualHash(RedPixelChannel, + hash_.substr(0, 70))); + _channels.push_back(Magick::ChannelPerceptualHash(GreenPixelChannel, + hash_.substr(70, 70))); + _channels.push_back(Magick::ChannelPerceptualHash(BluePixelChannel, + hash_.substr(140, 70))); +} + +Magick::ImagePerceptualHash::~ImagePerceptualHash(void) +{ +} + +Magick::ImagePerceptualHash::operator std::string() const +{ + if (!isValid()) + return(std::string()); + + return static_cast(_channels[0]) + + static_cast(_channels[1]) + + static_cast(_channels[2]); +} + +Magick::ChannelPerceptualHash Magick::ImagePerceptualHash::channel( + const PixelChannel channel_) const +{ + for (std::vector::const_iterator it = + _channels.begin(); it != _channels.end(); ++it) + { + if (it->channel() == channel_) + return(*it); + } + return(ChannelPerceptualHash()); +} + +bool Magick::ImagePerceptualHash::isValid() const +{ + if (_channels.size() != 3) + return(false); + + if (_channels[0].channel() != RedPixelChannel) + return(false); + + if (_channels[1].channel() != GreenPixelChannel) + return(false); + + if (_channels[2].channel() != BluePixelChannel) + return(false); + + return(true); +} + +double Magick::ImagePerceptualHash::sumSquaredDifferences( + const ImagePerceptualHash &channelPerceptualHash_) +{ + double + ssd; + + register ssize_t + i; + + if (!isValid()) + throw ErrorOption("instance is not valid"); + if (!channelPerceptualHash_.isValid()) + throw ErrorOption("channelPerceptualHash_ is not valid"); + + ssd=0.0; + for (i=0; i<3; i++) + { + ssd+=_channels[i].sumSquaredDifferences(_channels[i]); + } + return(ssd); +} + +Magick::ImagePerceptualHash::ImagePerceptualHash( + const MagickCore::Image *image) + : _channels() +{ + MagickCore::ChannelPerceptualHash* + channel_perceptual_hash; + + PixelTrait + traits; + + GetPPException; + channel_perceptual_hash=GetImagePerceptualHash(image,exceptionInfo); + if (channel_perceptual_hash != (MagickCore::ChannelPerceptualHash *) NULL) + { + traits=GetPixelChannelTraits(image,RedPixelChannel); + if ((traits & UpdatePixelTrait) != 0) + _channels.push_back(Magick::ChannelPerceptualHash(RedPixelChannel, + &channel_perceptual_hash[RedPixelChannel])); + traits=GetPixelChannelTraits(image,GreenPixelChannel); + if ((traits & UpdatePixelTrait) != 0) + _channels.push_back(Magick::ChannelPerceptualHash(GreenPixelChannel, + &channel_perceptual_hash[GreenPixelChannel])); + traits=GetPixelChannelTraits(image,BluePixelChannel); + if ((traits & UpdatePixelTrait) != 0) + _channels.push_back(Magick::ChannelPerceptualHash(BluePixelChannel, + &channel_perceptual_hash[BluePixelChannel])); + channel_perceptual_hash=(MagickCore::ChannelPerceptualHash *) + RelinquishMagickMemory(channel_perceptual_hash); + } + ThrowPPException; +} + Magick::ImageStatistics::ImageStatistics(void) : _channels() {