SilentEye 0.4.1
|
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 }