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