SilentEye 0.4.1

modules/seformatjpegold/seformatjpeg/imagejpeg.cpp

Go to the documentation of this file.
00001 //  This file is part of SilentEye.
00002 //
00003 //  SilentEye is free software: you can redistribute it and/or modify
00004 //  it under the terms of the GNU General Public License as published by
00005 //  the Free Software Foundation, either version 3 of the License, or
00006 //  (at your option) any later version.
00007 //
00008 //  SilentEye is distributed in the hope that it will be useful,
00009 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
00010 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00011 //  GNU General Public License for more details.
00012 //
00013 //  You should have received a copy of the GNU General Public License
00014 //  along with SilentEye.  If not, see <http://www.gnu.org/licenses/>.
00015 
00016 #include "imagejpeg.h"
00017 #include "moduleexception.h"
00018 #include <math.h>
00019 
00020 #include "stegotable.h"
00021 #include "groupedimage.h"
00022 
00023 namespace SEFormatJPEG {
00024 
00025     ImageJPEG::ImageJPEG()
00026         : Image()
00027     {
00028         init();
00029     }
00030 
00031     ImageJPEG::ImageJPEG(const QString& filePath)
00032         : Image(filePath)
00033     {
00034         init();
00035     }
00036 
00037     ImageJPEG::ImageJPEG(const QPixmap& pixmap, QString filePath)
00038         : Image(pixmap, filePath)
00039     {
00040         init();
00041     }
00042 
00043     ImageJPEG::ImageJPEG(const Image& img)
00044         : Image(img)
00045     {
00046         init();
00047     }
00048 
00049     ImageJPEG::ImageJPEG(Image* img)
00050         : Image(img)
00051     {
00052         init();
00053     }
00054 
00055     ImageJPEG::~ImageJPEG()
00056     {
00057         delete m_logger;
00058     }
00059 
00060     void ImageJPEG::init()
00061     {
00062         computeNewFileName("jpg");
00063         setObjectName("ImageJPEG");
00064         m_logger = new Logger(this);
00065         m_nbBits = 1;
00066         m_k = 20;
00067         m_passphrase = "SilentEye";
00068         m_quality = 50;
00069         m_headerPosition = BOTTOM;
00070     }
00071 
00072     void ImageJPEG::setK(int k)
00073     {
00074         m_k = k;
00075     }
00076 
00077     void ImageJPEG::setPassphrase(QString pass)
00078     {
00079         m_passphrase = pass;
00080     }
00081 
00082     void ImageJPEG::setQuality(int quality)
00083     {
00084         m_quality = quality;
00085     }
00086 
00087     void ImageJPEG::setHeaderPosition(HeaderPosition pos)
00088     {
00089         m_headerPosition = pos;
00090     }
00091 
00092     quint32 ImageJPEG::capacity() const
00093     {
00094         int nbPixelAvailable = (imgWidth() * imgHeight());
00095         return floor((floor(nbPixelAvailable / 64.0) - 32) / 8.0);
00096     }
00097 
00098     bool ImageJPEG::loadData()
00099     {
00100         m_logger->debug("loading");
00101         m_nbBits = 1;
00102         m_isLoaded = false;
00103         EncodedData dataSize(Data::UINT32);
00104         dataSize.initialize(m_nbBits);
00105 
00106         if (m_data.isNull())
00107             m_data = new EncodedData();
00108         else
00109             m_data->clear();
00110 
00111         m_data->initialize(m_nbBits);
00112         int x=0, y=0;
00113 
00114         StegoTable steganoTable(m_passphrase, m_k, this);
00115 
00116         QImage image = this->toImage();
00117         GroupedImage* gimg = new GroupedImage(image, steganoTable.k(), this);
00118 
00119         QPoint* headerPos = computeHeaderPosition(gimg->width(), gimg->height());
00120         x = headerPos[0].x();
00121         y = headerPos[0].y();
00122 
00123         int nbBitsRead = 0;
00124         while(nbBitsRead<32 && x<gimg->width() && y<gimg->height())
00125         {
00126             QPointer<PixelGroup> pg = gimg->pixelGroup(x, y);
00127             float miv = pg->miv();
00128             if (miv >= 0 && miv < 256)
00129             {
00130                 m_logger->debug("pos: " + QString::number(x) + "," + QString::number(y)/* + ". loaded:" + pg->toString()*/ + " => " + QString::number(miv));
00131                 int val = steganoTable.computeValue(miv) ? 1 : 0;
00132                 dataSize.append(val);
00133                 nbBitsRead += m_nbBits;
00134             } else {
00135                 m_logger->warning("miv is negative, pos: " + QString::number(x) + "," + QString::number(y));
00136             }
00137 
00138             x += 1;
00139             if(x >= gimg->width())
00140             {
00141                 y++;
00142                 x=0;
00143             }
00144         }
00145 
00146         quint32 nbOctets = dataSize.toUInt32();
00147         m_logger->debug("loaded size: " + QString::number(nbOctets));
00148 
00149         if(m_headerPosition != TOP)
00150         {
00151             x = 0;
00152             y = 0;
00153         }
00154 
00155         if (nbOctets > capacity()) {
00156             m_logger->warning("loaded size > capacity");
00157             delete gimg;
00158             return false;
00159         }
00160 
00161         int step = computeDistributionStep(nbOctets, gimg->width(), gimg->height());
00162         QPoint newPos = computeNewPosition(QPoint(x, y), step, gimg->width(), gimg->height(), true);
00163         x = newPos.x();
00164         y = newPos.y();
00165 
00166         m_logger->debug("first loaded pixel(step: " + QString::number(step) + "): "
00167                         + QString::number(x) + ":" + QString::number(y));
00168 
00169         int nbBits = nbOctets*8;
00170         nbBitsRead = 0;
00171         while(nbBitsRead < nbBits && x<gimg->width() && y<gimg->height())
00172         {
00173             if(isBetweenPoint(QPoint(x, y), headerPos[0], headerPos[1]))
00174             {
00175                 // skip header block
00176                 m_logger->debug("skip: " + QString::number(x) + "," + QString::number(y));
00177                 QPoint pos = computeNewPosition(headerPos[1], 1, gimg->width(), gimg->height());
00178                 x = pos.x();
00179                 y = pos.y();
00180                 continue;
00181             }
00182             else
00183             {
00184                 QPointer<PixelGroup> pg = gimg->pixelGroup(x, y);
00185                 float miv = pg->miv();
00186                 if (miv >= 0 && miv < 256)
00187                 {
00188                     int val = steganoTable.computeValue(miv) ? 1 : 0;
00189                     m_data->append(val);
00190                     nbBitsRead += m_nbBits;
00191                 } else {
00192                     m_logger->warning("miv is -1, pos: " + QString::number(x) + "," + QString::number(y));
00193                 }
00194             }
00195 
00196             // change cursor to the next pixel
00197             if(nbBitsRead < nbBits)
00198             {
00199                 QPoint newPos = computeNewPosition(QPoint(x, y), step, gimg->width(), gimg->height());
00200                 x = newPos.x();
00201                 y = newPos.y();
00202             }
00203         }
00204 
00205         m_logger->debug("last loaded pixel: "
00206                         + QString::number(x) + ":" + QString::number(y));
00207         delete(headerPos);
00208 
00209         m_isLoaded = m_data->size()>0;
00210         delete gimg;
00211         return m_isLoaded;
00212     }
00213 
00214     bool ImageJPEG::saveToDir(QString& outputDirPath)
00215     {
00216         int x=0, y=0;
00217 
00218         if (m_data.isNull())
00219             throw ModuleException("Technical error during encoding process",
00220                                   "Cannot insert null data into image");
00221 
00222         m_filePath = outputDirPath+"/"+m_shortName;
00223 
00224         if(!this->toImage().save(m_filePath, "JPEG", m_quality))
00225         {
00226             m_logger->warning("Cannnot save image to " + m_filePath);
00227             return false;
00228         }
00229         load(m_filePath);
00230 
00231         StegoTable steganoTable(m_passphrase, m_k, this);
00232 
00233         QImage image = this->toImage();
00234         GroupedImage* gimg = new GroupedImage(image, steganoTable.k(), this);
00235 
00236         EncodedData sizeData(m_data->size());
00237         m_logger->debug("Setted size: " + QString::number(sizeData.toUInt32()) 
00238                         + "/" + QString::number(capacity()));
00239 
00240         QPoint* headerPos = computeHeaderPosition(gimg->width(), gimg->height());
00241         x = headerPos[0].x();
00242         y = headerPos[0].y();
00243 
00244         sizeData.initialize(m_nbBits);
00245         while(sizeData.hasNext() && x<gimg->width() && y<gimg->height())
00246         {
00247             QPointer<PixelGroup> pg = gimg->pixelGroup(x, y);
00248             float miv = pg->miv();
00249             if (miv >= 0 && miv < 256)
00250             {
00251                 int val = sizeData.read();
00252                 float miv2 = steganoTable.computeNewMiv(miv, val!=0);
00253                 pg->updateMivTo(miv2);
00254                 m_logger->debug("pos: " + QString::number(x) + "," + QString::number(y) /* + ". pg: " + pg->toString()*/
00255                                 + " => " + QString::number(miv) + " to " + QString::number(miv2) + ", "
00256                                 + "Result:" + QString::number(pg->miv()) );
00257             } else {
00258                 m_logger->warning("miv is negative, pos: " + QString::number(x) + "," + QString::number(y));
00259             }
00260             
00261             x += 1;
00262             if(x >= gimg->width())
00263             {
00264                 y++;
00265                 x=0;
00266             }
00267         }
00268 
00269         if(m_headerPosition != TOP)
00270         {
00271             x = 0;
00272             y = 0;
00273         }
00274 
00275         int step = computeDistributionStep(m_data->size(), gimg->width(), gimg->height());
00276         QPoint newPos = computeNewPosition(QPoint(x, y), step, gimg->width(), gimg->height(), true);
00277         x = newPos.x();
00278         y = newPos.y();
00279 
00280         m_logger->debug("first setted pixel(step: " + QString::number(step) + "): "
00281                         + QString::number(x) + ":" + QString::number(y));
00282 
00283         m_data->initialize(m_nbBits);
00284         while(m_data->hasNext() && x<gimg->width() && y<gimg->height())
00285         {
00286             if(isBetweenPoint(QPoint(x, y), headerPos[0], headerPos[1]))
00287             {
00288                 // skip header block
00289                 m_logger->debug("skip: " + QString::number(x) + "," + QString::number(y));
00290                 QPoint pos = computeNewPosition(headerPos[1], 1, gimg->width(), gimg->height());
00291                 x = pos.x();
00292                 y = pos.y();
00293                 continue;
00294             }
00295             else
00296             {
00297                 QPointer<PixelGroup> pg = gimg->pixelGroup(x, y);
00298                 float miv = pg->miv();
00299                 if (miv >= 0 && miv < 256)
00300                 {
00301                     int val = m_data->read();
00302                     miv = steganoTable.computeNewMiv(miv, val!=0);
00303                     pg->updateMivTo(miv);
00304                 } else {
00305                     m_logger->warning("miv is -1, pos: " + QString::number(x) + "," + QString::number(y));
00306                 }
00307             }
00308 
00309             if(m_data->hasNext())
00310             {
00311                 QPoint newPos = computeNewPosition(QPoint(x, y), step, gimg->width(), gimg->height());
00312                 x = newPos.x();
00313                 y = newPos.y();
00314             }
00315         }
00316         delete(headerPos);
00317 
00318         m_logger->debug("last setted pixel: "
00319                         + QString::number(x) + ":" + QString::number(y));
00320 
00321         QImage* img = gimg->toImage();
00322         m_logger->debug("saving image...");
00323         bool ok = img->save(m_filePath, "JPEG", m_quality);
00324         m_logger->debug("image saved");
00325         delete img;
00326         m_logger->debug("image deleted");
00327         delete gimg;
00328         m_logger->debug("gimg deleted");
00329 
00330         if (x>=gimg->width() || y>=gimg->height())
00331         {
00332             m_logger->warning("Data too large (" + QString::number(x) + ":" + QString::number(y) + ")");
00333             return false;
00334         }
00335 
00336         if (ok) {
00337             load(m_filePath);
00338             m_logger->debug("image loaded");
00339         }
00340         return ok;
00341 
00342     }
00343 
00344     int ImageJPEG::computeDistributionStep(quint32 size, int width, int height)
00345     {
00346         int step = 1;
00347         /*int sizeNbPixel = 32;
00348         int nbPixelAvailable = (width * height) - sizeNbPixel;
00349         int nbPixelData = size * 8;
00350 
00351         step = floor(((double)nbPixelAvailable) / nbPixelData);
00352 
00353         if(step <= 0)
00354         {
00355             m_logger->debug("computed step was 0 => set 1");
00356             step = 1;
00357         }
00358         else if(step >= EQUI_NB_STEP_MIN)
00359         {
00360             int squareLength = floor(sqrt(step));
00361             //double q = (height / ceil((sizeNbPixel*squareLength)/width))*squareLength;
00362 
00363             if (width < height)
00364             {
00365                 double ratio = ((double)width) / height;
00366                 // ratio *= 1/(ratio*2);
00367                 //double ratio = ((double)width) / (height-q);
00368                 //ratio *= 1/ratio;
00369                 m_blockWidth = ceil(squareLength*ratio);
00370                 m_blockHeight = floor(m_blockWidth/ratio);
00371             }
00372             else
00373             {
00374                 double ratio = ((double)(height)) / width;
00375                 //double ratio = ((double)(height-q)) / width;
00376                 //ratio *= 1/ratio;
00377                 m_blockHeight = ceil(squareLength*ratio);
00378                 m_blockWidth = floor(m_blockHeight/ratio);
00379             }
00380 
00381             int nbBlockMaxSquare = floor(floor((double)height/(m_blockHeight)) * floor((double)width/(m_blockWidth)));
00382             if(nbBlockMaxSquare < nbPixelData)
00383             {
00384                 m_logger->error("Distribution step failed ! (" + QString::number(nbPixelData) + "/" + QString::number(nbBlockMaxSquare) + ")");
00385             }
00386 
00387             int posX = floor(m_blockWidth/2.0);
00388             int posY = floor(m_blockHeight/2.0);
00389             if (posX <= posY)
00390             {
00391                 m_blockInnerPos = posX;
00392             }
00393             else
00394             {
00395                 m_blockInnerPos = posY;
00396             }
00397 
00398             m_logger->debug("BlockWidth: " + QString::number(m_blockWidth) + ", "
00399                             + "BlockHeight: " + QString::number(m_blockHeight) + ", "
00400                             + "BlockInnerPos: " + QString::number(m_blockInnerPos) + ", "
00401                             + "DataNbPixel: " + QString::number(nbPixelData) + ", "
00402                             + "NbBlockMax: " + QString::number(nbBlockMaxSquare));
00403         }*/
00404 
00405         return step;
00406     }
00407 
00408     QPoint* ImageJPEG::computeHeaderPosition(int width, int height)
00409     {
00410         int headNbPixel = 32;
00411 
00412         int headStartX = 0;
00413         int headStartY = 0;
00414 
00415         if(m_headerPosition == BOTTOM)
00416         {
00417             headStartX = (abs(headNbPixel - width)) % width;
00418             headStartY = height - ceil((headStartX + headNbPixel - 1) / (double)width);
00419         }
00420         else if(m_headerPosition == SIGNATURE)
00421         {
00422             headStartX = floor(width * 0.95);
00423             headStartY = floor(height * 0.95);
00424             if (((width - headStartX) + (height - headStartY)*width) < headNbPixel)
00425                 throw ModuleException("Technical error during loading process",
00426                                       "Image too small for use of signature mode (header position)");
00427         }
00428 
00429         int headEndX = (headStartX + headNbPixel - 1) % width;
00430         int headEndY = headStartY + floor((headStartX + headNbPixel - 1) / (double)width);
00431 
00432         m_logger->debug("headNbPixel: " + QString::number(headNbPixel)
00433                         + ", headStartX: " + QString::number(headStartX)
00434                         + ", headStartY: " + QString::number(headStartY)
00435                         + ", headEndX: " + QString::number(headEndX)
00436                         + ", headEndY: " + QString::number(headEndY));
00437 
00438         QPoint* tab = new QPoint[2];
00439         tab[0] = QPoint(headStartX, headStartY);
00440         tab[1] = QPoint(headEndX, headEndY);
00441 
00442         return tab;
00443     }
00444 
00445     bool ImageJPEG::isBetweenPoint(const QPoint& ref, const QPoint& start, const QPoint& end)
00446     {
00447         if(start.y() == end.y()) // singleline
00448         {
00449             return ref.y() == start.y() && ref.x() >= start.x() && ref.x() <= end.x();
00450         }
00451         else // multiline
00452         {
00453             return (ref.y() == start.y() && ref.x() >= start.x())
00454                     || (ref.y() == end.y() && ref.x() <= end.x())
00455                     || (ref.y() > start.y() && ref.y() < end.y());
00456         }
00457     }
00458 
00459     QPoint ImageJPEG::computeNewPosition(const QPoint& oldPos, int step, int width, int height, bool first)
00460     {
00461         QPoint pos(oldPos);
00462         if(first)
00463         {
00464             if (step >= EQUI_NB_STEP_MIN)
00465             {
00466                 pos.setX(m_blockInnerPos);
00467                 pos.setY(m_blockInnerPos);
00468             }
00469             return pos;
00470         }
00471 
00472         int newX = 0;
00473         if(step >= EQUI_NB_STEP_MIN)
00474         {
00475             newX = oldPos.x() + m_blockWidth;
00476         }
00477         else
00478         {
00479             newX = oldPos.x() + step;
00480         }
00481 
00482         if(newX >= width)
00483         {
00484             if(step >= EQUI_NB_STEP_MIN)
00485             {
00486                 pos.ry() += m_blockHeight;
00487             }
00488             else
00489             {
00490                 pos.ry() += floor(newX / (double)width);
00491             }
00492         }
00493         pos.setX(newX % width);
00494 
00495         //m_logger->debug("new position: " + QString::number(pos.x()) + ":" + QString::number(pos.y()));
00496 
00497         return pos;
00498     }
00499 
00500 }