1 //***************************************************************
2 // CLass: %CLASS%
3 //
4 // Description:
5 //
6 //
7 // Author: Chris Browet <cbro@semperpax.com> (C) 2010
8 //
9 // Copyright: See COPYING file that comes with this distribution
10 //
11 //******************************************************************
12 
13 #include "WalkingPapersAdapter.h"
14 
15 #include <QCoreApplication>
16 #include <QtPlugin>
17 #include <QAction>
18 #include <QFileDialog>
19 #include <QPainter>
20 #include <QMessageBox>
21 #include <QInputDialog>
22 #include <QTimer>
23 #include <QImage>
24 
25 #include <QNetworkAccessManager>
26 #include <QNetworkRequest>
27 #include <QNetworkReply>
28 
29 #include <QDebug>
30 
31 #include <math.h>
32 
33 #ifdef USE_ZBAR
34 #include <zbar.h>
35 #include <zbar/QZBarImage.h>
36 #endif
37 
38 // from wikipedia
39 #define EQUATORIALRADIUS 6378137.0
40 #define POLARRADIUS      6356752.0
41 #define EQUATORIALMETERCIRCUMFERENCE  40075016.68
42 #define EQUATORIALMETERHALFCIRCUMFERENCE  20037508.34
43 #define EQUATORIALMETERPERDEGREE    222638.981555556
44 
45 static const QUuid theUid ("{c580b2bc-dd14-40b2-8bb6-241da2a1fdb3}");
46 static const QString theName("Walking Papers");
47 
getId() const48 QUuid WalkingPapersAdapterFactory::getId() const
49 {
50     return theUid;
51 }
52 
getName() const53 QString	WalkingPapersAdapterFactory::getName() const
54 {
55     return theName;
56 }
57 
58 /**************/
59 
60 
61 #define FILTER_OPEN_SUPPORTED \
62     tr("Supported formats")+" (*.jpg *.png *.bmp)\n" \
63     +tr("All Files (*)")
64 
angToRad(double a)65 double angToRad(double a)
66 {
67     return a*M_PI/180.;
68 }
69 
mercatorProject(const QPointF & c)70 QPointF mercatorProject(const QPointF& c)
71 {
72     double x = angToRad(c.x()) / M_PI * EQUATORIALMETERHALFCIRCUMFERENCE;
73     double y = log(tan(angToRad(c.y())) + 1/cos(angToRad(c.y()))) / M_PI * (EQUATORIALMETERHALFCIRCUMFERENCE);
74 
75     return QPointF(x, y);
76 }
77 
WalkingPapersAdapter()78 WalkingPapersAdapter::WalkingPapersAdapter()
79     : theImageManager(0)
80 {
81     QAction* loadImage = new QAction(tr("Load image..."), this);
82     loadImage->setData(theUid.toString());
83     connect(loadImage, SIGNAL(triggered()), SLOT(onLoadImage()));
84     theMenu = new QMenu();
85     theMenu->addAction(loadImage);
86 }
87 
88 
~WalkingPapersAdapter()89 WalkingPapersAdapter::~WalkingPapersAdapter()
90 {
91 }
92 
getId() const93 QUuid WalkingPapersAdapter::getId() const
94 {
95     return theUid;
96 }
97 
getName() const98 QString	WalkingPapersAdapter::getName() const
99 {
100     return theName;
101 }
102 
alreadyLoaded(QString fn) const103 bool WalkingPapersAdapter::alreadyLoaded(QString fn) const
104 {
105     for (int j=0; j<theImages.size(); ++j)
106         if (theImages[j].theFilename == fn)
107             return true;
108     return false;
109 }
110 
make_grayscale(QImage & in)111 void make_grayscale(QImage& in)
112 {
113     if(in.format()!=QImage::Format_Indexed8)
114         throw "format error";
115     QVector<int> transform_table(in.colorCount());
116     for(int i=0;i<in.colorCount();i++)
117     {
118         QRgb c1=in.color(i);
119         int avg=qGray(c1);
120         transform_table[i] = avg;
121     }
122     in.setColorCount(256);
123     for(int i=0;i<256;i++)
124         in.setColor(i,qRgb(i,i,i));
125     for(int i=0;i<in.byteCount();i++)
126     {
127         in.bits()[i]=transform_table[in.bits()[i]];
128     }
129 }
130 
getWalkingPapersDetails(const QUrl & reqUrl,QRectF & bbox) const131 bool WalkingPapersAdapter::getWalkingPapersDetails(const QUrl& reqUrl, QRectF& bbox) const
132 {
133     QNetworkAccessManager* manager = theImageManager->getNetworkManager();
134     QEventLoop q;
135     QTimer tT;
136 
137     if (!reqUrl.host().contains("walking-papers.org"))
138         return false;
139 
140     tT.setSingleShot(true);
141     connect(&tT, SIGNAL(timeout()), &q, SLOT(quit()));
142     connect(manager, SIGNAL(finished(QNetworkReply*)),
143             &q, SLOT(quit()));
144     QNetworkReply *reply = manager->get(QNetworkRequest(reqUrl));
145 
146     tT.start(theSets->value("Network/NetworkTimeout", 5000).toInt());
147     q.exec();
148     if(tT.isActive()) {
149         // download complete
150         tT.stop();
151     } else {
152         QMessageBox::warning(0, tr("Network timeout"), tr("Cannot read the photo's details from the Walking Papers server."), QMessageBox::Ok);
153         return false;
154     }
155 
156     QString center = QString::fromLatin1(reply->rawHeader("X-Print-Bounds"));
157     QStringList sl = center.split(" ");
158     if (sl.size() != 4)
159         return false;
160 
161     QPointF tl(sl[1].toDouble(), sl[0].toDouble());
162     QPointF br(sl[3].toDouble(), sl[2].toDouble());
163 
164     qDebug() << tl << "; " << br;
165 
166     bbox = QRectF(tl, br);
167 
168     return true;
169 }
170 
askAndgetWalkingPapersDetails(QRectF & bbox) const171 bool WalkingPapersAdapter::askAndgetWalkingPapersDetails(QRectF& bbox) const
172 {
173     bool ok;
174     QString text = QInputDialog::getText(0, tr("Please specify Walking Papers URL"),
175                                          tr("URL:"), QLineEdit::Normal, "", &ok);
176     if (ok && !text.isEmpty()) {
177         QUrl url(text);
178         return getWalkingPapersDetails(url, bbox);
179     } else
180         return false;
181 }
182 
loadImage(const QString & fn,QRectF theBbox,int theRotation)183 bool WalkingPapersAdapter::loadImage(const QString& fn, QRectF theBbox, int theRotation)
184 {
185     if (alreadyLoaded(fn))
186         return true;
187 
188     QImage img(fn);
189     WalkingPapersImage wimg;
190     if (theBbox.isNull()) {
191 #ifdef USE_ZBAR
192         zbar::QZBarImage image(img);
193 
194         // create a reader
195         zbar::ImageScanner scanner;
196 
197         // configure the reader
198         scanner.set_config(zbar::ZBAR_NONE, zbar::ZBAR_CFG_ENABLE, 1);
199 
200         // scan the image for barcodes
201         scanner.recycle_image(image);
202         zbar::Image tmp = image.convert(*(long*)"Y800");
203         int n = scanner.scan(tmp);
204         image.set_symbols(tmp.get_symbols());
205 
206         if (n <= 0) {
207             qDebug() << "WP scan error: " << n;
208             if (!askAndgetWalkingPapersDetails(theBbox))
209                 return false;
210         } else {
211             QUrl url;
212             // extract results
213             for(zbar::Image::SymbolIterator symbol = image.symbol_begin(); symbol != image.symbol_end(); ++symbol) {
214                 // do something useful with results
215                 qDebug() << "decoded " << QString::fromStdString(symbol->get_type_name())
216                         << " symbol \"" << QString::fromStdString(symbol->get_data()) << '"';
217                 qDebug() << "x;y: " << symbol->get_location_x(0) << ", " << symbol->get_location_y(0);
218 
219                 url = QUrl(QString::fromStdString(symbol->get_data()));
220                 getWalkingPapersDetails(url, theBbox);
221 
222                 int x = symbol->get_location_x(0);
223                 int y = symbol->get_location_y(0);
224                 QPoint mid = QPoint(img.width()/2, img.height()/2);
225                 if (x < mid.x() || y < mid.y()) {
226                     if (x < mid.x() && y < mid.y())
227                         theRotation = 180;
228                     else if (x > mid.x() && y < mid.y())
229                         theRotation = 90;
230                     else if (x < mid.x() && y > mid.y())
231                         theRotation = -90;
232                 }
233             }
234         }
235 
236         // clean up
237         image.set_data(NULL, 0);
238 #else
239         if (!askAndgetWalkingPapersDetails(theBbox))
240             return false;
241 #endif
242     }
243     if (theRotation) {
244         QMatrix mat;
245         mat.rotate(theRotation);
246         img = img.transformed(mat);
247     }
248     wimg.theFilename = fn;
249     wimg.theImg = QPixmap::fromImage(img);
250     wimg.theBBox = theBbox;
251     wimg.rotation = theRotation;
252     theImages.push_back(wimg);
253 
254     theCoordBbox |= theBbox;
255 
256     return true;
257 }
258 
onLoadImage()259 void WalkingPapersAdapter::onLoadImage()
260 {
261     int fileOk = 0;
262 
263     QStringList fileNames = QFileDialog::getOpenFileNames(
264                     NULL,
265                     tr("Open Walking Papers scan"),
266                     "", FILTER_OPEN_SUPPORTED);
267     if (fileNames.isEmpty())
268         return;
269 
270     QRectF theBbox = QRectF();
271     for (int i=0; i<fileNames.size(); i++) {
272         if (loadImage(fileNames[i], theBbox))
273             ++fileOk;
274     }
275 
276     if (!fileOk) {
277         QMessageBox::critical(0,QCoreApplication::translate("WalkingPapersBackground","No valid file"),QCoreApplication::translate("WalkingPapersBackground","Cannot load file."));
278     } else {
279         emit forceProjection();
280         emit forceZoom();
281         emit forceRefresh();
282     }
283 
284     return;
285 }
286 
getHost() const287 QString	WalkingPapersAdapter::getHost() const
288 {
289     return QString();
290 }
291 
getType() const292 IMapAdapter::Type WalkingPapersAdapter::getType() const
293 {
294     return IMapAdapter::DirectBackground;
295 }
296 
getMenu() const297 QMenu* WalkingPapersAdapter::getMenu() const
298 {
299     return theMenu;
300 }
301 
getBoundingbox() const302 QRectF WalkingPapersAdapter::getBoundingbox() const
303 {
304     QRectF projBBox;
305     projBBox = QRectF(mercatorProject(theCoordBbox.topLeft()), mercatorProject(theCoordBbox.bottomRight()));
306     return projBBox;
307 }
308 
projection() const309 QString WalkingPapersAdapter::projection() const
310 {
311     return "EPSG:900913";
312 }
313 
getPixmap(const QRectF & wgs84Bbox,const QRectF &,const QRect & src) const314 QPixmap WalkingPapersAdapter::getPixmap(const QRectF& wgs84Bbox, const QRectF& /*projBbox*/, const QRect& src) const
315 {
316     QPixmap pix(src.size());
317     pix.fill(Qt::transparent);
318     QPainter p(&pix);
319 
320     for (int i=0; i<theImages.size(); ++i) {
321         QPixmap theImg = theImages[i].theImg;
322 
323         double rx = wgs84Bbox.width() / src.width();
324         double ry = wgs84Bbox.height() / src.height();
325         qDebug() << "rx: " << rx << "; ry: " << ry;
326 
327         QSize sz(theImages[i].theBBox.width() / rx, theImages[i].theBBox.height() / ry);
328         if (sz.isNull())
329             return QPixmap();
330 
331         QPoint s((theImages[i].theBBox.bottomLeft().x() - wgs84Bbox.bottomLeft().x()) / rx, (wgs84Bbox.bottomLeft().y() - theImages[i].theBBox.bottomLeft().y()) / ry);
332 
333         qDebug() << "Viewport: " << wgs84Bbox;
334         qDebug() << "Pixmap Origin: " << s.x() << "," << s.y();
335         qDebug() << "Pixmap size: " << sz.width() << "," << sz.height();
336 
337         double rtx = theImg.width() / (double)sz.width();
338         double rty = theImg.height() / (double)sz.height();
339 
340         QRect mRect = QRect(s, sz);
341         QRect iRect = theImg.rect().intersected(mRect);
342         QRect sRect = QRect(iRect.topLeft() - mRect.topLeft(), iRect.size());
343         QRect fRect = QRect(sRect.x() * rtx, sRect.y() * rty, sRect.width() * rtx, sRect.height() * rty);
344 
345         qDebug() << "mrect: " << mRect;
346         qDebug() << "iRect: " << iRect;
347         qDebug() << "sRect: " << sRect;
348         qDebug() << "fRect: " << fRect;
349 
350         QPixmap img2 = theImg.copy(fRect).scaled(sRect.size());
351         p.drawPixmap(iRect.topLeft(), img2);
352     }
353 
354     p.end();
355     return pix;
356 }
357 
getImageManager()358 IImageManager* WalkingPapersAdapter::getImageManager()
359 {
360     return theImageManager;
361 }
362 
setImageManager(IImageManager * anImageManager)363 void WalkingPapersAdapter::setImageManager(IImageManager* anImageManager)
364 {
365     theImageManager = anImageManager;
366 }
367 
cleanup()368 void WalkingPapersAdapter::cleanup()
369 {
370     theImages.clear();
371     theCoordBbox = QRectF();
372 }
373 
toXML(QXmlStreamWriter & stream)374 bool WalkingPapersAdapter::toXML(QXmlStreamWriter& stream)
375 {
376     bool OK = true;
377 
378     stream.writeStartElement("Images");
379     for (int i=0; i<theImages.size(); ++i) {
380         stream.writeStartElement("Image");
381         stream.writeAttribute("filename", theImages[i].theFilename);
382         stream.writeAttribute("top", QString::number(theImages[i].theBBox.top()));
383         stream.writeAttribute("left", QString::number(theImages[i].theBBox.left()));
384         stream.writeAttribute("width", QString::number(theImages[i].theBBox.width()));
385         stream.writeAttribute("height", QString::number(theImages[i].theBBox.height()));
386         stream.writeAttribute("rotation", QString::number(theImages[i].rotation));
387         stream.writeEndElement();
388     }
389     stream.writeEndElement();
390 
391     return OK;
392 }
393 
fromXML(QXmlStreamReader & stream)394 void WalkingPapersAdapter::fromXML(QXmlStreamReader& stream)
395 {
396     theCoordBbox = QRectF();
397     theImages.clear();
398 
399     stream.readNext();
400     while(!stream.atEnd() && !stream.isEndElement()) {
401         if (stream.name() == "Images") {
402             stream.readNext();
403             while(!stream.atEnd() && !stream.isEndElement()) {
404                 if (stream.name() == "Image") {
405                     QString fn = stream.attributes().value("filename").toString();
406                     if (!fn.isEmpty()) {
407                         double x = stream.attributes().value("left").toString().toDouble();
408                         double y = stream.attributes().value("top").toString().toDouble();
409                         double w = stream.attributes().value("width").toString().toDouble();
410                         double h = stream.attributes().value("height").toString().toDouble();
411                         int r = stream.attributes().value("rotation").toString().toInt();
412                         QRectF bbox(x, y, w, h);
413                         loadImage(fn, bbox, r);
414                     }
415                     stream.readNext();
416                 } else if (!stream.isWhitespace()) {
417                     qDebug() << "wp: logic error: " << stream.name() << " : " << stream.tokenType() << " (" << stream.lineNumber() << ")";
418                     stream.skipCurrentElement();
419                 }
420                 stream.readNext();
421             }
422         } else if (!stream.isWhitespace()) {
423             qDebug() << "wp: logic error: " << stream.name() << " : " << stream.tokenType() << " (" << stream.lineNumber() << ")";
424             stream.skipCurrentElement();
425         }
426         stream.readNext();
427     }
428 }
429 
toPropertiesHtml()430 QString WalkingPapersAdapter::toPropertiesHtml()
431 {
432     QString h;
433 
434     QStringList fn;
435     for (int i=0; i<theImages.size(); ++i) {
436         fn << QDir::toNativeSeparators(theImages[i].theFilename);
437     }
438     h += "<i>" + tr("Filename(s)") + ": </i>" + fn.join("; ");
439 
440     return h;
441 }
442 
443 
444 #if !(QT_VERSION >= QT_VERSION_CHECK(5,0,0))
445 Q_EXPORT_PLUGIN2(MWalkingPapersBackgroundPlugin, WalkingPapersAdapterFactory)
446 #endif
447