Added windows build howto into README
[imgsort.git] / ibdata.cpp
1 #include "imagebuffer.h"
2
3 #include <algorithm> // max, min
4 using std::min;
5 using std::max;
6
7 static QImage * rotate90(const QImage * src) {
8     QImage * dst = new QImage(src->height(), src->width(), src->format());
9     for (int y=0;y<src->height();++y) {
10         const uint *srcLine = reinterpret_cast< const uint * >(src->scanLine(y));
11         for (int x=0;x<src->width();++x) {
12             dst->setPixel(src->height()-y-1, x, srcLine[x]);
13         }
14     }
15     return dst;
16 }
17 static QImage * rotate270(const QImage * src) {
18     QImage * dst = new QImage(src->height(), src->width(), src->format());
19     for (int y=0;y<src->height();++y) {
20         const uint *srcLine = reinterpret_cast< const uint * >(src->scanLine(y));
21         for (int x=0;x<src->width();++x) {
22             dst->setPixel(y, src->width()-x-1, srcLine[x]);
23         }
24     }
25     return dst;
26 }
27
28 static void saveExifModified(const QString & targetJpeg, QExifImageHeader * exif, const QSize & jpegSize) {
29   QExifValue backup_orientation = exif->value(QExifImageHeader::Orientation),
30              backup_pixelXdim = exif->value(QExifImageHeader::PixelXDimension),
31              backup_pixelYdim = exif->value(QExifImageHeader::PixelYDimension);
32
33   exif->setValue(QExifImageHeader::Orientation, QExifValue(quint32(1)));
34   exif->setValue(QExifImageHeader::PixelXDimension, QExifValue(quint32(jpegSize.width())));
35   exif->setValue(QExifImageHeader::PixelYDimension, QExifValue(quint32(jpegSize.height())));
36
37   exif->saveToJpeg(targetJpeg);
38
39   exif->setValue(QExifImageHeader::Orientation, backup_orientation);
40   exif->setValue(QExifImageHeader::PixelXDimension, backup_pixelXdim);
41   exif->setValue(QExifImageHeader::PixelYDimension, backup_pixelYdim);
42 }
43
44 static QImage * loadImageFromJpeg(QString jpegName, int rotate) {
45   QImage * qi = new QImage(jpegName);
46   QImage * qir;
47   switch ((rotate + 4) % 4) {
48     case 1:
49       qir = rotate90(qi);
50       delete qi;
51       break;
52     case 2:
53       qir = rotate270(qi);
54       delete qi;
55       break;
56     default:
57       qir = qi;
58   }
59   return qir;
60 }
61
62
63 static QImage * scaleCropImage(QFuture<QImage *> & futureOriginal, ScaleCropRule scr, bool fast) {
64   QImage * rotated = futureOriginal.result();
65   qDebug() << "Rotating: " << scr.ini_rot;
66   switch (scr.ini_rot) {
67     case 1:
68       rotated = rotate270(rotated);
69       break;
70     case 3:
71       rotated = rotate90(rotated);
72       break;
73     case 2: {
74         QImage * rot1 = rotate90(rotated);
75         rotated = rotate90(rot1);
76         delete rot1;
77       }
78       break;
79   }
80   QImage scaled = rotated->scaled(
81     scr.scaleSize(),
82     Qt::KeepAspectRatio,
83     (fast ? Qt::FastTransformation : Qt::SmoothTransformation)
84   );
85   if (scr.ini_rot != 0) delete rotated;
86   QRect basecr = scr.cropRect(), cr;
87   QSize scaleds = scaled.size();
88   cr.setX(max(basecr.x(), 0));
89   cr.setY(max(basecr.y(), 0));
90   cr.setWidth(min(basecr.width(), scaleds.width() - cr.x()));
91   cr.setHeight(min(basecr.height(), scaleds.height() - cr.y()));
92   QImage * cropped = new QImage(scaled.copy(cr));
93   // FIXME: copy only the existing subrect, not to fill with black...
94   return cropped;
95 }
96
97
98 static bool scaleCropToFile(QFuture<QImage *> & futureOriginal, ScaleCropRule scr, bool fast, QString target, QExifImageHeader * exif) {
99   QImage * sced = scaleCropImage(futureOriginal, scr, fast);
100   sced->save(target, 0, si_settings_output_jpeg_quality);
101   //QFile::copy(target, target + ".woExif.jpg"); // debug
102   saveExifModified(target, exif, sced->size());
103   delete sced;
104   return true; // just stuff
105 }
106
107
108
109
110 ImageBuffer::IBData::IBData(const QString & filename) : exifData(filename) {
111   int rotate = 0;
112   if (exifData.contains(QExifImageHeader::Orientation)) {
113     switch (exifData.value(QExifImageHeader::Orientation).toShort()) {
114       case 6: rotate = 1; break;
115       case 8: rotate = 3; break;
116     }
117   }
118
119   originalImage = QtConcurrent::run(loadImageFromJpeg, filename, rotate);
120 }
121
122
123 ImageBuffer::IBData::~IBData() {
124   QHashIterator<QString, QFuture<QImage *> > i(rescales);
125   while (i.hasNext()) {
126     QString k = i.next().key();
127     delete rescales[k].result();
128   }
129   waitForFileRescaling();
130 }
131
132
133 void ImageBuffer::IBData::prepareSC(ScaleCropRule scr, bool fast) {
134   QString scrs = scr.toString();
135   qDebug() << "prepare: " << scrs;
136   if (rescales.contains(scrs)) return;
137   rescales.insert(
138     scrs,
139     QtConcurrent::run(scaleCropImage, originalImage, scr, fast)
140   );
141 }
142
143 QImage * ImageBuffer::IBData::getSC(ScaleCropRule scr) {
144   QString scrs = scr.toString();
145   qDebug() << "get: " << scrs;
146   if (!rescales.contains(scrs)) {
147     prepareSC(scr); // keep slow. it actually shouldn't happen. we could provide fast but the question is
148                     // 1) the pointer 2) keeping crap buffered
149   }
150   return rescales[scrs].result();
151 }
152
153 void ImageBuffer::IBData::unprepareSC(ScaleCropRule scr) {
154   QString scrs = scr.toString();
155   if (!rescales.contains(scrs)) return;
156   delete rescales[scrs].result();
157   rescales.remove(scrs);
158 }
159
160
161
162 void ImageBuffer::IBData::fileSC(ScaleCropRule scr, const QString & targetFile) {
163   waitForFileRescaling();
164   fileRescalingRunning = true;
165   fileRescaling = QtConcurrent::run(scaleCropToFile, originalImage, scr, false, targetFile, &exifData);
166 }
167
168
169 void ImageBuffer::IBData::waitForFileRescaling() {
170   if (fileRescalingRunning) {
171     bool stuff = fileRescaling.result();
172     stuff = stuff; // we do nothing
173     fileRescalingRunning = false;
174   }
175 }
176
177 QSize ImageBuffer::IBData::getOriginalSize() {
178   int w, h;
179   if (exifData.contains(QExifImageHeader::PixelXDimension) && exifData.contains(QExifImageHeader::PixelYDimension)) {
180     w = (int) exifData.value(QExifImageHeader::PixelXDimension).toLong();
181     h = (int) exifData.value(QExifImageHeader::PixelYDimension).toLong();
182   }
183   else {
184     w = originalImage.result()->width();
185     h = originalImage.result()->height();
186   }
187   return QSize(w, h);
188 }