SilentEye 0.4.1

modules/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         int capacity = floor((floor(nbPixelAvailable / 64.0) - 32) / 8.0);
00096         return (capacity<0) ? 0 : capacity;
00097     }
00098 
00099     bool ImageJPEG::loadData()
00100     {
00101         m_logger->debug("loading");
00102         m_nbBits = 1;
00103         m_isLoaded = false;
00104         EncodedData dataSize(Data::UINT32);
00105         dataSize.initialize(m_nbBits);
00106 
00107         if (m_data.isNull())
00108             m_data = new EncodedData();
00109         else
00110             m_data->clear();
00111 
00112         m_data->initialize(m_nbBits);
00113         int x=0, y=0;
00114 
00115         StegoTable steganoTable(m_passphrase, m_k, this);
00116 
00117         QImage image = this->toImage();
00118         GroupedImage* gimg = new GroupedImage(image, steganoTable.k(), this);
00119 
00120         QPoint* headerPos = computeHeaderPosition(gimg->width(), gimg->height());
00121         x = headerPos[0].x();
00122         y = headerPos[0].y();
00123 
00124         int nbBitsRead = 0;
00125         while(nbBitsRead<32 && x<gimg->width() && y<gimg->height())
00126         {
00127             QPointer<PixelGroup> pg = gimg->pixelGroup(x, y);
00128             float miv = pg->miv();
00129             if (miv >= 0 && miv < 256)
00130             {
00131                 m_logger->debug("pos: " + QString::number(x) + "," + QString::number(y)/* + ". loaded:" + pg->toString()*/ + " => " + QString::number(miv));
00132                 int val = steganoTable.computeValue(miv) ? 1 : 0;
00133                 dataSize.append(val);
00134                 nbBitsRead += m_nbBits;
00135             } else {
00136                 m_logger->warning("miv is invalid, pos: " + QString::number(x) + "," + QString::number(y));
00137             }
00138 
00139             x += 1;
00140             if(x >= gimg->width())
00141             {
00142                 y++;
00143                 x=0;
00144             }
00145         }
00146 
00147         quint32 nbOctets = dataSize.toUInt32();
00148         m_logger->debug("loaded size: " + QString::number(nbOctets));
00149 
00150         if(m_headerPosition != TOP)
00151         {
00152             x = 0;
00153             y = 0;
00154         }
00155 
00156         if (nbOctets > capacity()) {
00157             m_logger->warning("data size > capacity ("
00158                               + QString::number(nbOctets)
00159                               + " > " + QString::number(capacity()) + ")");
00160             delete gimg;
00161             return false;
00162         }
00163 
00164         int step = computeDistributionStep(nbOctets, gimg->width(), gimg->height());
00165         QPoint newPos = computeNewPosition(QPoint(x, y), step, gimg->width(), gimg->height(), true);
00166         x = newPos.x();
00167         y = newPos.y();
00168 
00169         m_logger->debug("first loaded pixel(step: " + QString::number(step) + "): "
00170                         + QString::number(x) + ":" + QString::number(y));
00171 
00172         int nbBits = nbOctets*8;
00173         nbBitsRead = 0;
00174         while(nbBitsRead < nbBits && x<gimg->width() && y<gimg->height())
00175         {
00176             if(isBetweenPoint(QPoint(x, y), headerPos[0], headerPos[1]))
00177             {
00178                 // skip header block
00179                 m_logger->debug("skip: " + QString::number(x) + "," + QString::number(y));
00180                 QPoint pos = computeNewPosition(headerPos[1], 1, gimg->width(), gimg->height());
00181                 x = pos.x();
00182                 y = pos.y();
00183                 continue;
00184             }
00185             else
00186             {
00187                 QPointer<PixelGroup> pg = gimg->pixelGroup(x, y);
00188                 float miv = pg->miv();
00189                 if (miv >= 0 && miv < 256)
00190                 {
00191                     int val = steganoTable.computeValue(miv) ? 1 : 0;
00192                     m_data->append(val);
00193                     nbBitsRead += m_nbBits;
00194                 } else {
00195                     m_logger->warning("miv is invalid, pos: " + QString::number(x) + "," + QString::number(y));
00196                 }
00197             }
00198 
00199             // change cursor to the next pixel
00200             if(nbBitsRead < nbBits)
00201             {
00202                 QPoint newPos = computeNewPosition(QPoint(x, y), step, gimg->width(), gimg->height());
00203                 x = newPos.x();
00204                 y = newPos.y();
00205             }
00206         }
00207 
00208         m_logger->debug("last loaded pixel: "
00209                         + QString::number(x) + ":" + QString::number(y));
00210         delete(headerPos);
00211 
00212         m_isLoaded = m_data->size()>0;
00213         delete gimg;
00214         return m_isLoaded;
00215     }
00216 
00217     bool ImageJPEG::saveToDir(QString& outputDirPath)
00218     {
00219         int x=0, y=0;
00220 
00221         if (m_data.isNull())
00222             throw ModuleException("Technical error during encoding process",
00223                                   "Cannot insert null data into image");
00224 
00225         if (m_data->size() > capacity()) {
00226             m_logger->warning("data size > capacity ("
00227                               + QString::number(m_data->size())
00228                               + " > " + QString::number(capacity()) + ")");
00229             return false;
00230         }
00231 
00232         m_filePath = outputDirPath+"/"+m_shortName;
00233         StegoTable steganoTable(m_passphrase, m_k, this);
00234 
00235         QImage image = this->toImage();
00236         GroupedImage::compactImage(image, steganoTable.k());
00237 
00238         if(!image.save(m_filePath, "JPEG", m_quality))
00239         {
00240             m_logger->warning("Cannot save image to " + m_filePath);
00241             return false;
00242         }
00243         load(m_filePath);
00244 
00245         image = this->toImage();
00246         GroupedImage* gimg = new GroupedImage(image, steganoTable.k(), this);
00247 
00248         EncodedData sizeData(m_data->size());
00249         m_logger->debug("Setted size: " + QString::number(sizeData.toUInt32()) 
00250                         + "/" + QString::number(capacity()));
00251 
00252         QPoint* headerPos = computeHeaderPosition(gimg->width(), gimg->height());
00253         x = headerPos[0].x();
00254         y = headerPos[0].y();
00255 
00256         sizeData.initialize(m_nbBits);
00257         while(sizeData.hasNext() && x<gimg->width() && y<gimg->height())
00258         {
00259             QPointer<PixelGroup> pg = gimg->pixelGroup(x, y);
00260             float miv = pg->miv();
00261             if (miv >= 0 && miv < 256)
00262             {
00263                 int val = sizeData.read();
00264                 float miv2 = steganoTable.computeNewMiv(miv, val!=0);
00265                 pg->updateMivTo(miv2);
00266                 m_logger->debug("pos: " + QString::number(x) + "," + QString::number(y)
00267                                 + " => " + QString::number(miv) + " to " + QString::number(miv2) + ", "
00268                                 + "Result:" + QString::number(pg->miv()) );
00269             } else {
00270                 m_logger->warning("miv is invalid, pos: " + QString::number(x) + "," + QString::number(y));
00271             }
00272             
00273             x += 1;
00274             if(x >= gimg->width())
00275             {
00276                 y++;
00277                 x=0;
00278             }
00279         }
00280 
00281         if(m_headerPosition != TOP)
00282         {
00283             x = 0;
00284             y = 0;
00285         }
00286 
00287         int step = computeDistributionStep(m_data->size(), gimg->width(), gimg->height());
00288         QPoint newPos = computeNewPosition(QPoint(x, y), step, gimg->width(), gimg->height(), true);
00289         x = newPos.x();
00290         y = newPos.y();
00291 
00292         m_logger->debug("first setted pixel(step: " + QString::number(step) + "): "
00293                         + QString::number(x) + ":" + QString::number(y));
00294 
00295         m_data->initialize(m_nbBits);
00296         while(m_data->hasNext() && x<gimg->width() && y<gimg->height())
00297         {
00298             if(isBetweenPoint(QPoint(x, y), headerPos[0], headerPos[1]))
00299             {
00300                 // skip header block
00301                 m_logger->debug("skip: " + QString::number(x) + "," + QString::number(y));
00302                 QPoint pos = computeNewPosition(headerPos[1], 1, gimg->width(), gimg->height());
00303                 x = pos.x();
00304                 y = pos.y();
00305                 continue;
00306             }
00307             else
00308             {
00309                 QPointer<PixelGroup> pg = gimg->pixelGroup(x, y);
00310                 float miv = pg->miv();
00311                 if (miv >= 0 && miv < 256)
00312                 {
00313                     int val = m_data->read();
00314                     float miv2 = steganoTable.computeNewMiv(miv, val!=0);
00315                     pg->updateMivTo(miv2);
00316                     m_logger->debug("pos: " + QString::number(x) + "," + QString::number(y)
00317                                     + " => " + QString::number(miv) + " to " + QString::number(miv2) + ", "
00318                                     + "Result:" + QString::number(pg->miv()) );
00319                 } else {
00320                     m_logger->warning("miv is invalid, pos: " + QString::number(x) + "," + QString::number(y));
00321                 }
00322             }
00323 
00324             if(m_data->hasNext())
00325             {
00326                 QPoint newPos = computeNewPosition(QPoint(x, y), step, gimg->width(), gimg->height());
00327                 x = newPos.x();
00328                 y = newPos.y();
00329             }
00330         }
00331         delete(headerPos);
00332 
00333         m_logger->debug("last setted pixel: "
00334                         + QString::number(x) + ":" + QString::number(y));
00335 
00336         QImage* img = gimg->toImage();
00337         m_logger->debug("saving image...");
00338         bool ok = img->save(m_filePath, "JPEG", m_quality);
00339         m_logger->debug("image saved");
00340         delete img;
00341         m_logger->debug("image deleted");
00342         delete gimg;
00343         m_logger->debug("gimg deleted");
00344 
00345         if (x>=gimg->width() || y>=gimg->height())
00346         {
00347             m_logger->warning("Data too large (" + QString::number(x) + ":" + QString::number(y) + ")");
00348             return false;
00349         }
00350 
00351         if (ok) {
00352             load(m_filePath);
00353             m_logger->debug("image loaded");
00354         }
00355         return ok;
00356 
00357     }
00358 
00359     int ImageJPEG::computeDistributionStep(quint32 size, int width, int height)
00360     {
00361         int step = 1;
00362         int sizeNbPixel = 32;
00363         int nbPixelAvailable = (width * height) - sizeNbPixel;
00364         int nbPixelData = size * 8;
00365 
00366         step = floor(((double)nbPixelAvailable) / nbPixelData);
00367 
00368         if(step <= 0)
00369         {
00370             m_logger->debug("computed step was 0 => set 1");
00371             step = 1;
00372         }
00373         else if(step >= EQUI_NB_STEP_MIN)
00374         {
00375             int squareLength = floor(sqrt(step));
00376             //double q = (height / ceil((sizeNbPixel*squareLength)/width))*squareLength;
00377 
00378             if (width < height)
00379             {
00380                 double ratio = ((double)width) / height;
00381                 // ratio *= 1/(ratio*2);
00382                 //double ratio = ((double)width) / (height-q);
00383                 //ratio *= 1/ratio;
00384                 m_blockWidth = ceil(squareLength*ratio);
00385                 m_blockHeight = floor(m_blockWidth/ratio);
00386             }
00387             else
00388             {
00389                 double ratio = ((double)height) / width;
00390                 //double ratio = ((double)(height-q)) / width;
00391                 //ratio *= 1/ratio;
00392                 m_blockHeight = ceil(squareLength*ratio);
00393                 m_blockWidth = floor(m_blockHeight/ratio);
00394             }
00395 
00396             int nbBlockMaxSquare = floor(floor((double)height/(m_blockHeight)) * floor((double)width/(m_blockWidth)));
00397             if(nbBlockMaxSquare < nbPixelData)
00398             {
00399                 m_logger->error("Distribution step failed ! (" + QString::number(nbPixelData) + "/" + QString::number(nbBlockMaxSquare) + ")");
00400             }
00401 
00402             int posX = floor(m_blockWidth/2.0);
00403             int posY = floor(m_blockHeight/2.0);
00404             if (posX <= posY)
00405             {
00406                 m_blockInnerPos = posX;
00407             }
00408             else
00409             {
00410                 m_blockInnerPos = posY;
00411             }
00412 
00413             m_logger->debug("BlockWidth: " + QString::number(m_blockWidth) + ", "
00414                             + "BlockHeight: " + QString::number(m_blockHeight) + ", "
00415                             + "BlockInnerPos: " + QString::number(m_blockInnerPos) + ", "
00416                             + "DataNbPixel: " + QString::number(nbPixelData) + ", "
00417                             + "NbBlockMax: " + QString::number(nbBlockMaxSquare));
00418         } else {
00419             m_logger->debug("Keep original step: " + QString::number(step) + "<" + QString::number(EQUI_NB_STEP_MIN));
00420         }
00421 
00422         return step;
00423     }
00424 
00425     QPoint* ImageJPEG::computeHeaderPosition(int width, int height)
00426     {
00427         int headNbPixel = 32;
00428 
00429         int headStartX = 0;
00430         int headStartY = 0;
00431 
00432         if(m_headerPosition == BOTTOM)
00433         {
00434             headStartX = (abs(headNbPixel - width)) % width;
00435             headStartY = height - ceil((headStartX + headNbPixel - 1) / (double)width);
00436         }
00437         else if(m_headerPosition == SIGNATURE)
00438         {
00439             headStartX = floor(width * 0.95);
00440             headStartY = floor(height * 0.95);
00441             if (((width - headStartX) + (height - headStartY)*width) < headNbPixel)
00442                 throw ModuleException("Technical error during loading process",
00443                                       "Image too small for use of signature mode (header position)");
00444         }
00445 
00446         int headEndX = (headStartX + headNbPixel - 1) % width;
00447         int headEndY = headStartY + floor((headStartX + headNbPixel - 1) / (double)width);
00448 
00449         m_logger->debug("headNbPixel: " + QString::number(headNbPixel)
00450                         + ", headStartX: " + QString::number(headStartX)
00451                         + ", headStartY: " + QString::number(headStartY)
00452                         + ", headEndX: " + QString::number(headEndX)
00453                         + ", headEndY: " + QString::number(headEndY));
00454 
00455         QPoint* tab = new QPoint[2];
00456         tab[0] = QPoint(headStartX, headStartY);
00457         tab[1] = QPoint(headEndX, headEndY);
00458 
00459         return tab;
00460     }
00461 
00462     bool ImageJPEG::isBetweenPoint(const QPoint& ref, const QPoint& start, const QPoint& end)
00463     {
00464         if(start.y() == end.y()) // singleline
00465         {
00466             return ref.y() == start.y() && ref.x() >= start.x() && ref.x() <= end.x();
00467         }
00468         else // multiline
00469         {
00470             return (ref.y() == start.y() && ref.x() >= start.x())
00471                     || (ref.y() == end.y() && ref.x() <= end.x())
00472                     || (ref.y() > start.y() && ref.y() < end.y());
00473         }
00474     }
00475 
00476     QPoint ImageJPEG::computeNewPosition(const QPoint& oldPos, int step, int width, int height, bool first)
00477     {
00478         QPoint pos(oldPos);
00479         if(first)
00480         {
00481             if (step >= EQUI_NB_STEP_MIN)
00482             {
00483                 pos.setX(m_blockInnerPos);
00484                 pos.setY(m_blockInnerPos);
00485             }
00486             return pos;
00487         }
00488 
00489         int newX = 0;
00490         if(step >= EQUI_NB_STEP_MIN)
00491         {
00492             newX = oldPos.x() + m_blockWidth;
00493         }
00494         else
00495         {
00496             newX = oldPos.x() + step;
00497         }
00498 
00499         if(newX >= width)
00500         {
00501             if(step >= EQUI_NB_STEP_MIN)
00502             {
00503                 pos.ry() += m_blockHeight;
00504             }
00505             else
00506             {
00507                 pos.ry() += floor(newX / (double)width);
00508             }
00509         }
00510         pos.setX(newX % width);
00511 
00512         //m_logger->debug("new position: " + QString::number(pos.x()) + ":" + QString::number(pos.y()));
00513 
00514         return pos;
00515     }
00516 
00517 }