Merge lp:~schwann/gallery-app/gallery-edit-thread into lp:gallery-app
- gallery-edit-thread
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Gustavo Pichorim Boiko |
Approved revision: | 788 |
Merged at revision: | 787 |
Proposed branch: | lp:~schwann/gallery-app/gallery-edit-thread |
Merge into: | lp:gallery-app |
Diff against target: |
894 lines (+457/-261) 7 files modified
src/media/media-source.h (+3/-1) src/photo/CMakeLists.txt (+2/-0) src/photo/photo-edit-thread.cpp (+285/-0) src/photo/photo-edit-thread.h (+63/-0) src/photo/photo.cpp (+82/-253) src/photo/photo.h (+13/-7) tests/unittests/stubs/photo_stub.cpp (+9/-0) |
To merge this branch: | bzr merge lp:~schwann/gallery-app/gallery-edit-thread |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gustavo Pichorim Boiko (community) | Approve | ||
PS Jenkins bot | continuous-integration | Approve | |
Review via email: mp+177165@code.launchpad.net |
Commit message
Run edit operations in a background task
Description of the change
Run edit operations in a background task. So now one can see the spinner running, and is able to use the UI, while the editing is performed.
- 788. By Günter Schwann
-
Properly block undo/redo
PS Jenkins bot (ps-jenkins) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:788
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Gustavo Pichorim Boiko (boiko) wrote : | # |
Looks good.
Preview Diff
1 | === modified file 'src/media/media-source.h' | |||
2 | --- src/media/media-source.h 2013-07-05 08:12:31 +0000 | |||
3 | +++ src/media/media-source.h 2013-07-26 16:08:26 +0000 | |||
4 | @@ -103,7 +103,6 @@ | |||
5 | 103 | void setFileTimestamp(const QDateTime& timestamp); | 103 | void setFileTimestamp(const QDateTime& timestamp); |
6 | 104 | 104 | ||
7 | 105 | const QSize& size(); | 105 | const QSize& size(); |
8 | 106 | void setSize(const QSize& size); | ||
9 | 107 | 106 | ||
10 | 108 | qint64 id() const; | 107 | qint64 id() const; |
11 | 109 | void setId(qint64 id); | 108 | void setId(qint64 id); |
12 | @@ -112,6 +111,9 @@ | |||
13 | 112 | 111 | ||
14 | 113 | void setMediaTable(MediaTable *mediaTable); | 112 | void setMediaTable(MediaTable *mediaTable); |
15 | 114 | 113 | ||
16 | 114 | public Q_SLOTS: | ||
17 | 115 | void setSize(const QSize& size); | ||
18 | 116 | |||
19 | 115 | protected: | 117 | protected: |
20 | 116 | bool isSizeSet() const; | 118 | bool isSizeSet() const; |
21 | 117 | 119 | ||
22 | 118 | 120 | ||
23 | === modified file 'src/photo/CMakeLists.txt' | |||
24 | --- src/photo/CMakeLists.txt 2013-07-03 11:53:48 +0000 | |||
25 | +++ src/photo/CMakeLists.txt 2013-07-26 16:08:26 +0000 | |||
26 | @@ -18,12 +18,14 @@ | |||
27 | 18 | photo.h | 18 | photo.h |
28 | 19 | photo-caches.h | 19 | photo-caches.h |
29 | 20 | photo-edit-state.h | 20 | photo-edit-state.h |
30 | 21 | photo-edit-thread.h | ||
31 | 21 | ) | 22 | ) |
32 | 22 | 23 | ||
33 | 23 | set(gallery_photo_SRCS | 24 | set(gallery_photo_SRCS |
34 | 24 | photo.cpp | 25 | photo.cpp |
35 | 25 | photo-caches.cpp | 26 | photo-caches.cpp |
36 | 26 | photo-edit-state.cpp | 27 | photo-edit-state.cpp |
37 | 28 | photo-edit-thread.cpp | ||
38 | 27 | ) | 29 | ) |
39 | 28 | 30 | ||
40 | 29 | add_library(${GALLERY_PHOTO_LIB} | 31 | add_library(${GALLERY_PHOTO_LIB} |
41 | 30 | 32 | ||
42 | === added file 'src/photo/photo-edit-thread.cpp' | |||
43 | --- src/photo/photo-edit-thread.cpp 1970-01-01 00:00:00 +0000 | |||
44 | +++ src/photo/photo-edit-thread.cpp 2013-07-26 16:08:26 +0000 | |||
45 | @@ -0,0 +1,285 @@ | |||
46 | 1 | /* | ||
47 | 2 | * Copyright (C) 2013 Canonical Ltd | ||
48 | 3 | * | ||
49 | 4 | * This program is free software: you can redistribute it and/or modify | ||
50 | 5 | * it under the terms of the GNU General Public License version 3 as | ||
51 | 6 | * published by the Free Software Foundation. | ||
52 | 7 | * | ||
53 | 8 | * This program is distributed in the hope that it will be useful, | ||
54 | 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
55 | 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
56 | 11 | * GNU General Public License for more details. | ||
57 | 12 | * | ||
58 | 13 | * You should have received a copy of the GNU General Public License | ||
59 | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
60 | 15 | */ | ||
61 | 16 | |||
62 | 17 | #include "photo-edit-thread.h" | ||
63 | 18 | #include "photo.h" | ||
64 | 19 | |||
65 | 20 | // medialoader | ||
66 | 21 | #include "photo-metadata.h" | ||
67 | 22 | |||
68 | 23 | // util | ||
69 | 24 | #include "imaging.h" | ||
70 | 25 | |||
71 | 26 | #include <QDebug> | ||
72 | 27 | |||
73 | 28 | /*! | ||
74 | 29 | * \brief PhotoEditThread::PhotoEditThread | ||
75 | 30 | */ | ||
76 | 31 | PhotoEditThread::PhotoEditThread(Photo *photo, const PhotoEditState &editState) | ||
77 | 32 | : QThread(), | ||
78 | 33 | m_photo(photo), | ||
79 | 34 | m_editState(editState), | ||
80 | 35 | m_caches(photo->file()), | ||
81 | 36 | m_oldOrientation(photo->orientation()) | ||
82 | 37 | { | ||
83 | 38 | } | ||
84 | 39 | |||
85 | 40 | /*! | ||
86 | 41 | * \brief PhotoEditThread::editState resturns the editing stse used for this processing | ||
87 | 42 | * \return | ||
88 | 43 | */ | ||
89 | 44 | const PhotoEditState &PhotoEditThread::editState() const | ||
90 | 45 | { | ||
91 | 46 | return m_editState; | ||
92 | 47 | } | ||
93 | 48 | |||
94 | 49 | /*! | ||
95 | 50 | * \brief PhotoEditThread::oldOrientation returns the orientation of the photo before the editing | ||
96 | 51 | * \return | ||
97 | 52 | */ | ||
98 | 53 | Orientation PhotoEditThread::oldOrientation() const | ||
99 | 54 | { | ||
100 | 55 | return m_oldOrientation; | ||
101 | 56 | } | ||
102 | 57 | |||
103 | 58 | /*! | ||
104 | 59 | * \brief PhotoEditThread::run \reimp | ||
105 | 60 | */ | ||
106 | 61 | void PhotoEditThread::run() | ||
107 | 62 | { | ||
108 | 63 | // As a special case, if editing to the original version, we simply restore | ||
109 | 64 | // from the original and call it a day. | ||
110 | 65 | if (m_editState.isOriginal()) { | ||
111 | 66 | if (!m_caches.restoreOriginal()) | ||
112 | 67 | qWarning() << "Error restoring original for" << m_photo->file().filePath(); | ||
113 | 68 | else | ||
114 | 69 | Q_EMIT resetToOriginalSize(); | ||
115 | 70 | |||
116 | 71 | // As a courtesy, when the original goes away, we get rid of the other | ||
117 | 72 | // cached files too. | ||
118 | 73 | m_caches.discardCachedEnhanced(); | ||
119 | 74 | return; | ||
120 | 75 | } | ||
121 | 76 | |||
122 | 77 | if (!m_caches.cacheOriginal()) | ||
123 | 78 | qWarning() << "Error caching original for" << m_photo->file().filePath(); | ||
124 | 79 | |||
125 | 80 | if (m_editState.is_enhanced_ && !m_caches.hasCachedEnhanced()) | ||
126 | 81 | createCachedEnhanced(); | ||
127 | 82 | |||
128 | 83 | if (!m_caches.overwriteFromCache(m_editState.is_enhanced_)) | ||
129 | 84 | qWarning() << "Error overwriting" << m_photo->file().filePath() << "from cache"; | ||
130 | 85 | |||
131 | 86 | // Have we been rotated and _not_ cropped? | ||
132 | 87 | if (m_photo->fileFormatHasOrientation() && (!m_editState.crop_rectangle_.isValid()) && | ||
133 | 88 | m_editState.exposureCompensation_ == 0 && | ||
134 | 89 | (m_editState.orientation_ != PhotoEditState::ORIGINAL_ORIENTATION)) { | ||
135 | 90 | // Yes; skip out on decoding and re-encoding the image. | ||
136 | 91 | handleSimpleMetadataRotation(m_editState); | ||
137 | 92 | return; | ||
138 | 93 | } | ||
139 | 94 | |||
140 | 95 | // TODO: we might be able to avoid reading/writing pixel data (and other | ||
141 | 96 | // more general optimizations) under certain conditions here. Might be worth | ||
142 | 97 | // doing if it doesn't make the code too much worse. | ||
143 | 98 | // | ||
144 | 99 | // At the moment, we are skipping at least one decode and one encode in cases | ||
145 | 100 | // where a .jpeg file has been rotated, but not cropped, since rotation can be | ||
146 | 101 | // controlled by manipulating its metadata without having to modify pixel data; | ||
147 | 102 | // please see the method handle_simple_metadata_rotation() for details. | ||
148 | 103 | |||
149 | 104 | QImage image(m_photo->file().filePath(), m_photo->fileFormat().toStdString().c_str()); | ||
150 | 105 | if (image.isNull()) { | ||
151 | 106 | qWarning() << "Error loading" << m_photo->file().filePath() << "for editing"; | ||
152 | 107 | return; | ||
153 | 108 | } | ||
154 | 109 | PhotoMetadata* metadata = PhotoMetadata::fromFile(m_photo->file()); | ||
155 | 110 | |||
156 | 111 | if (m_photo->fileFormatHasOrientation() && | ||
157 | 112 | m_editState.orientation_ != PhotoEditState::ORIGINAL_ORIENTATION) | ||
158 | 113 | metadata->setOrientation(m_editState.orientation_); | ||
159 | 114 | |||
160 | 115 | if (m_photo->fileFormatHasOrientation() && | ||
161 | 116 | metadata->orientation() != TOP_LEFT_ORIGIN) | ||
162 | 117 | image = image.transformed(metadata->orientationTransform()); | ||
163 | 118 | else if (m_editState.orientation_ != PhotoEditState::ORIGINAL_ORIENTATION && | ||
164 | 119 | m_editState.orientation_ != TOP_LEFT_ORIGIN) | ||
165 | 120 | image = image.transformed( | ||
166 | 121 | OrientationCorrection::fromOrientation(m_editState.orientation_).toTransform()); | ||
167 | 122 | |||
168 | 123 | if (m_editState.crop_rectangle_.isValid()) | ||
169 | 124 | image = image.copy(m_editState.crop_rectangle_); | ||
170 | 125 | |||
171 | 126 | // exposure compensation | ||
172 | 127 | if (m_editState.exposureCompensation_ != 0.0) { | ||
173 | 128 | image = compensateExposure(image, m_editState.exposureCompensation_); | ||
174 | 129 | } | ||
175 | 130 | |||
176 | 131 | // exposure compensation | ||
177 | 132 | if (!m_editState.colorBalance_.isNull()) { | ||
178 | 133 | const QVector4D &v = m_editState.colorBalance_; | ||
179 | 134 | image = doColorBalance(image, v.x(), v.y(), v.z(), v.w()); | ||
180 | 135 | } | ||
181 | 136 | |||
182 | 137 | QSize new_size = image.size(); | ||
183 | 138 | |||
184 | 139 | // We need to apply the reverse transformation so that when we reload the | ||
185 | 140 | // file and reapply the transformation it comes out correctly. | ||
186 | 141 | if (m_photo->fileFormatHasOrientation() && | ||
187 | 142 | metadata->orientation() != TOP_LEFT_ORIGIN) | ||
188 | 143 | image = image.transformed(metadata->orientationTransform().inverted()); | ||
189 | 144 | |||
190 | 145 | bool saved = image.save(m_photo->file().filePath(), m_photo->fileFormat().toStdString().c_str(), 90); | ||
191 | 146 | if (saved && m_photo->fileFormatHasMetadata()) | ||
192 | 147 | saved = metadata->save(); | ||
193 | 148 | if (!saved) | ||
194 | 149 | qWarning() << "Error saving edited" << m_photo->file().filePath(); | ||
195 | 150 | |||
196 | 151 | delete metadata; | ||
197 | 152 | |||
198 | 153 | Q_EMIT newSize(new_size); | ||
199 | 154 | } | ||
200 | 155 | |||
201 | 156 | /*! | ||
202 | 157 | * \brief PhotoEditThread::handleSimpleMetadataRotation | ||
203 | 158 | * Handler for the case of an image whose only change is to its | ||
204 | 159 | * orientation; used to skip re-encoding of JPEGs. | ||
205 | 160 | * \param state | ||
206 | 161 | */ | ||
207 | 162 | void PhotoEditThread::handleSimpleMetadataRotation(const PhotoEditState& state) | ||
208 | 163 | { | ||
209 | 164 | PhotoMetadata* metadata = PhotoMetadata::fromFile(m_photo->file()); | ||
210 | 165 | metadata->setOrientation(state.orientation_); | ||
211 | 166 | |||
212 | 167 | metadata->save(); | ||
213 | 168 | delete(metadata); | ||
214 | 169 | |||
215 | 170 | OrientationCorrection orig_correction = | ||
216 | 171 | OrientationCorrection::fromOrientation(m_photo->originalOrientation()); | ||
217 | 172 | OrientationCorrection dest_correction = | ||
218 | 173 | OrientationCorrection::fromOrientation(state.orientation_); | ||
219 | 174 | |||
220 | 175 | QSize new_size = m_photo->originalSize(); | ||
221 | 176 | int angle = dest_correction.getNormalizedRotationDifference(orig_correction); | ||
222 | 177 | |||
223 | 178 | if ((angle == 90) || (angle == 270)) { | ||
224 | 179 | new_size = m_photo->originalSize().transposed(); | ||
225 | 180 | } | ||
226 | 181 | |||
227 | 182 | Q_EMIT newSize(new_size); | ||
228 | 183 | } | ||
229 | 184 | |||
230 | 185 | /*! | ||
231 | 186 | * \brief PhotoEditThread::createCachedEnhanced | ||
232 | 187 | */ | ||
233 | 188 | void PhotoEditThread::createCachedEnhanced() | ||
234 | 189 | { | ||
235 | 190 | if (!m_caches.cacheEnhancedFromOriginal()) { | ||
236 | 191 | qWarning() << "Error creating enhanced file for" << m_photo->file().filePath(); | ||
237 | 192 | return; | ||
238 | 193 | } | ||
239 | 194 | |||
240 | 195 | QFileInfo to_enhance = m_photo->enhancedFile(); | ||
241 | 196 | PhotoMetadata* metadata = PhotoMetadata::fromFile(to_enhance); | ||
242 | 197 | |||
243 | 198 | QImage unenhanced_img(to_enhance.filePath(), m_photo->fileFormat().toStdString().c_str()); | ||
244 | 199 | int width = unenhanced_img.width(); | ||
245 | 200 | int height = unenhanced_img.height(); | ||
246 | 201 | |||
247 | 202 | QImage sample_img = (unenhanced_img.width() > 400) ? | ||
248 | 203 | unenhanced_img.scaledToWidth(400) : unenhanced_img; | ||
249 | 204 | |||
250 | 205 | AutoEnhanceTransformation enhance_txn = AutoEnhanceTransformation(sample_img); | ||
251 | 206 | |||
252 | 207 | QImage::Format dest_format = unenhanced_img.format(); | ||
253 | 208 | |||
254 | 209 | // Can't write into indexed images, due to a limitation in Qt. | ||
255 | 210 | if (dest_format == QImage::Format_Indexed8) | ||
256 | 211 | dest_format = QImage::Format_RGB32; | ||
257 | 212 | |||
258 | 213 | QImage enhanced_image(width, height, dest_format); | ||
259 | 214 | |||
260 | 215 | for (int j = 0; j < height; j++) { | ||
261 | 216 | for (int i = 0; i < width; i++) { | ||
262 | 217 | QColor px = enhance_txn.transformPixel( | ||
263 | 218 | QColor(unenhanced_img.pixel(i, j))); | ||
264 | 219 | enhanced_image.setPixel(i, j, px.rgb()); | ||
265 | 220 | } | ||
266 | 221 | } | ||
267 | 222 | |||
268 | 223 | bool saved = enhanced_image.save(to_enhance.filePath(), | ||
269 | 224 | m_photo->fileFormat().toStdString().c_str(), 90); | ||
270 | 225 | if (saved && m_photo->fileFormatHasMetadata()) | ||
271 | 226 | saved = metadata->save(); | ||
272 | 227 | if (!saved) { | ||
273 | 228 | qWarning() << "Error saving enhanced file for" << m_photo->file().filePath(); | ||
274 | 229 | m_caches.discardCachedEnhanced(); | ||
275 | 230 | } | ||
276 | 231 | |||
277 | 232 | delete metadata; | ||
278 | 233 | } | ||
279 | 234 | |||
280 | 235 | /*! | ||
281 | 236 | * \brief PhotoEditThread::compensateExposure Compensates the exposure | ||
282 | 237 | * Compensating the exposure is a change in brightnes | ||
283 | 238 | * \param image Image to change the brightnes | ||
284 | 239 | * \param compansation -1.0 is total dark, +1.0 is total bright | ||
285 | 240 | * \return The image with adjusted brightnes | ||
286 | 241 | */ | ||
287 | 242 | QImage PhotoEditThread::compensateExposure(const QImage &image, qreal compansation) | ||
288 | 243 | { | ||
289 | 244 | int shift = qBound(-255, (int)(255*compansation), 255); | ||
290 | 245 | QImage result(image.width(), image.height(), image.format()); | ||
291 | 246 | |||
292 | 247 | for (int j = 0; j < image.height(); j++) { | ||
293 | 248 | for (int i = 0; i <image.width(); i++) { | ||
294 | 249 | QColor px = image.pixel(i, j); | ||
295 | 250 | int red = qBound(0, px.red() + shift, 255); | ||
296 | 251 | int green = qBound(0, px.green() + shift, 255); | ||
297 | 252 | int blue = qBound(0, px.blue() + shift, 255); | ||
298 | 253 | result.setPixel(i, j, qRgb(red, green, blue)); | ||
299 | 254 | } | ||
300 | 255 | } | ||
301 | 256 | |||
302 | 257 | return result; | ||
303 | 258 | } | ||
304 | 259 | |||
305 | 260 | /*! | ||
306 | 261 | * \brief PhotoEditThread::colorBalance | ||
307 | 262 | * \param image | ||
308 | 263 | * \param brightness 0 is total dark, 1 is as the original, grater than 1 is brigther | ||
309 | 264 | * \param contrast from 0 maybe 5. 1 is as the original | ||
310 | 265 | * \param saturation from 0 maybe 5. 1 is as the original | ||
311 | 266 | * \param hue from 0 to 360. 0 and 360 is as the original | ||
312 | 267 | * \return | ||
313 | 268 | */ | ||
314 | 269 | QImage PhotoEditThread::doColorBalance(const QImage &image, qreal brightness, qreal contrast, qreal saturation, qreal hue) | ||
315 | 270 | { | ||
316 | 271 | QImage result(image.width(), image.height(), image.format()); | ||
317 | 272 | |||
318 | 273 | ColorBalance cb(brightness, contrast, saturation, hue); | ||
319 | 274 | |||
320 | 275 | for (int j = 0; j < image.height(); j++) { | ||
321 | 276 | for (int i = 0; i <image.width(); i++) { | ||
322 | 277 | QColor px = image.pixel(i, j); | ||
323 | 278 | QColor tpx = cb.transformPixel(px); | ||
324 | 279 | result.setPixel(i, j, tpx.rgb()); | ||
325 | 280 | } | ||
326 | 281 | } | ||
327 | 282 | |||
328 | 283 | return result; | ||
329 | 284 | } | ||
330 | 285 | |||
331 | 0 | 286 | ||
332 | === added file 'src/photo/photo-edit-thread.h' | |||
333 | --- src/photo/photo-edit-thread.h 1970-01-01 00:00:00 +0000 | |||
334 | +++ src/photo/photo-edit-thread.h 2013-07-26 16:08:26 +0000 | |||
335 | @@ -0,0 +1,63 @@ | |||
336 | 1 | /* | ||
337 | 2 | * Copyright (C) 2013 Canonical Ltd | ||
338 | 3 | * | ||
339 | 4 | * This program is free software: you can redistribute it and/or modify | ||
340 | 5 | * it under the terms of the GNU General Public License version 3 as | ||
341 | 6 | * published by the Free Software Foundation. | ||
342 | 7 | * | ||
343 | 8 | * This program is distributed in the hope that it will be useful, | ||
344 | 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
345 | 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
346 | 11 | * GNU General Public License for more details. | ||
347 | 12 | * | ||
348 | 13 | * You should have received a copy of the GNU General Public License | ||
349 | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
350 | 15 | */ | ||
351 | 16 | |||
352 | 17 | #ifndef GALLERY_PHOTO_EDIT_THREAD_H_ | ||
353 | 18 | #define GALLERY_PHOTO_EDIT_THREAD_H_ | ||
354 | 19 | |||
355 | 20 | #include "photo-caches.h" | ||
356 | 21 | #include "photo-edit-state.h" | ||
357 | 22 | |||
358 | 23 | // util | ||
359 | 24 | #include "orientation.h" | ||
360 | 25 | |||
361 | 26 | #include <QImage> | ||
362 | 27 | #include <QThread> | ||
363 | 28 | #include <QUrl> | ||
364 | 29 | |||
365 | 30 | class Photo; | ||
366 | 31 | |||
367 | 32 | /*! | ||
368 | 33 | * \brief The PhotoEditThread class | ||
369 | 34 | */ | ||
370 | 35 | class PhotoEditThread: public QThread | ||
371 | 36 | { | ||
372 | 37 | Q_OBJECT | ||
373 | 38 | public: | ||
374 | 39 | PhotoEditThread(Photo *photo, const PhotoEditState& editState); | ||
375 | 40 | |||
376 | 41 | const PhotoEditState& editState() const; | ||
377 | 42 | Orientation oldOrientation() const; | ||
378 | 43 | |||
379 | 44 | Q_SIGNALS: | ||
380 | 45 | void newSize(QSize size); | ||
381 | 46 | void resetToOriginalSize(); | ||
382 | 47 | |||
383 | 48 | protected: | ||
384 | 49 | void run() Q_DECL_OVERRIDE; | ||
385 | 50 | |||
386 | 51 | private: | ||
387 | 52 | void createCachedEnhanced(); | ||
388 | 53 | QImage compensateExposure(const QImage& image, qreal compansation); | ||
389 | 54 | QImage doColorBalance(const QImage& image, qreal brightness, qreal contrast, qreal saturation, qreal hue); | ||
390 | 55 | void handleSimpleMetadataRotation(const PhotoEditState& state); | ||
391 | 56 | |||
392 | 57 | Photo *m_photo; | ||
393 | 58 | PhotoEditState m_editState; | ||
394 | 59 | PhotoCaches m_caches; | ||
395 | 60 | Orientation m_oldOrientation; | ||
396 | 61 | }; | ||
397 | 62 | |||
398 | 63 | #endif | ||
399 | 0 | 64 | ||
400 | === modified file 'src/photo/photo.cpp' | |||
401 | --- src/photo/photo.cpp 2013-07-05 08:12:31 +0000 | |||
402 | +++ src/photo/photo.cpp 2013-07-26 16:08:26 +0000 | |||
403 | @@ -23,6 +23,7 @@ | |||
404 | 23 | 23 | ||
405 | 24 | #include "photo.h" | 24 | #include "photo.h" |
406 | 25 | #include "photo-edit-state.h" | 25 | #include "photo-edit-state.h" |
407 | 26 | #include "photo-edit-thread.h" | ||
408 | 26 | 27 | ||
409 | 27 | // database | 28 | // database |
410 | 28 | #include "database.h" | 29 | #include "database.h" |
411 | @@ -185,6 +186,7 @@ | |||
412 | 185 | Photo::Photo(const QFileInfo& file) | 186 | Photo::Photo(const QFileInfo& file) |
413 | 186 | : MediaSource(file), | 187 | : MediaSource(file), |
414 | 187 | m_editRevision(0), | 188 | m_editRevision(0), |
415 | 189 | m_editThread(0), | ||
416 | 188 | m_caches(file), | 190 | m_caches(file), |
417 | 189 | m_originalSize(), | 191 | m_originalSize(), |
418 | 190 | m_originalOrientation(TOP_LEFT_ORIGIN), | 192 | m_originalOrientation(TOP_LEFT_ORIGIN), |
419 | @@ -201,6 +203,10 @@ | |||
420 | 201 | */ | 203 | */ |
421 | 202 | Photo::~Photo() | 204 | Photo::~Photo() |
422 | 203 | { | 205 | { |
423 | 206 | if (m_editThread) { | ||
424 | 207 | m_editThread->wait(); | ||
425 | 208 | finishEditing(); | ||
426 | 209 | } | ||
427 | 204 | delete(d_ptr); | 210 | delete(d_ptr); |
428 | 205 | } | 211 | } |
429 | 206 | 212 | ||
430 | @@ -347,13 +353,15 @@ | |||
431 | 347 | void Photo::undo() | 353 | void Photo::undo() |
432 | 348 | { | 354 | { |
433 | 349 | Q_D(Photo); | 355 | Q_D(Photo); |
435 | 350 | Orientation old_orientation = orientation(); | 356 | if (busy()) { |
436 | 357 | qWarning() << "Don't start edit operation, while another one is running"; | ||
437 | 358 | return; | ||
438 | 359 | } | ||
439 | 351 | 360 | ||
440 | 352 | PhotoEditState prev = d->editStack()->current(); | 361 | PhotoEditState prev = d->editStack()->current(); |
441 | 353 | PhotoEditState next = d->editStack()->undo(); | 362 | PhotoEditState next = d->editStack()->undo(); |
442 | 354 | if (next != prev) { | 363 | if (next != prev) { |
445 | 355 | save(next, old_orientation); | 364 | asyncEdit(next); |
444 | 356 | emit editStackChanged(); | ||
446 | 357 | } | 365 | } |
447 | 358 | } | 366 | } |
448 | 359 | 367 | ||
449 | @@ -363,13 +371,15 @@ | |||
450 | 363 | void Photo::redo() | 371 | void Photo::redo() |
451 | 364 | { | 372 | { |
452 | 365 | Q_D(Photo); | 373 | Q_D(Photo); |
454 | 366 | Orientation old_orientation = orientation(); | 374 | if (busy()) { |
455 | 375 | qWarning() << "Don't start edit operation, while another one is running"; | ||
456 | 376 | return; | ||
457 | 377 | } | ||
458 | 367 | 378 | ||
459 | 368 | PhotoEditState prev = d->editStack()->current(); | 379 | PhotoEditState prev = d->editStack()->current(); |
460 | 369 | PhotoEditState next = d->editStack()->redo(); | 380 | PhotoEditState next = d->editStack()->redo(); |
461 | 370 | if (next != prev) { | 381 | if (next != prev) { |
464 | 371 | save(next, old_orientation); | 382 | asyncEdit(next); |
463 | 372 | emit editStackChanged(); | ||
465 | 373 | } | 383 | } |
466 | 374 | } | 384 | } |
467 | 375 | 385 | ||
468 | @@ -556,6 +566,14 @@ | |||
469 | 556 | } | 566 | } |
470 | 557 | 567 | ||
471 | 558 | /*! | 568 | /*! |
472 | 569 | * \brief Photo::resetToOriginalSize set the size to the one of the orifinal photo | ||
473 | 570 | */ | ||
474 | 571 | void Photo::resetToOriginalSize() | ||
475 | 572 | { | ||
476 | 573 | setSize(originalSize(PhotoEditState::ORIGINAL_ORIENTATION)); | ||
477 | 574 | } | ||
478 | 575 | |||
479 | 576 | /*! | ||
480 | 559 | * \brief Photo::currentState | 577 | * \brief Photo::currentState |
481 | 560 | * \return | 578 | * \return |
482 | 561 | */ | 579 | */ |
483 | @@ -607,26 +625,43 @@ | |||
484 | 607 | */ | 625 | */ |
485 | 608 | void Photo::makeUndoableEdit(const PhotoEditState& state) | 626 | void Photo::makeUndoableEdit(const PhotoEditState& state) |
486 | 609 | { | 627 | { |
487 | 628 | if (busy()) { | ||
488 | 629 | qWarning() << "Don't start edit operation, while another one is running"; | ||
489 | 630 | return; | ||
490 | 631 | } | ||
491 | 632 | |||
492 | 610 | Q_D(Photo); | 633 | Q_D(Photo); |
493 | 611 | Orientation old_orientation = orientation(); | ||
494 | 612 | |||
495 | 613 | d->editStack()->pushEdit(state); | 634 | d->editStack()->pushEdit(state); |
498 | 614 | save(state, old_orientation); | 635 | asyncEdit(state); |
497 | 615 | emit editStackChanged(); | ||
499 | 616 | } | 636 | } |
500 | 617 | 637 | ||
501 | 618 | /*! | 638 | /*! |
505 | 619 | * \brief Photo::save | 639 | * \brief Photo::asyncEdit does edit the photo according to the given state |
506 | 620 | * \param state | 640 | * in a background task |
507 | 621 | * \param oldOrientation | 641 | * \param state the new editing state |
508 | 622 | */ | 642 | */ |
510 | 623 | void Photo::save(const PhotoEditState& state, Orientation oldOrientation) | 643 | void Photo::asyncEdit(const PhotoEditState& state) |
511 | 624 | { | 644 | { |
512 | 625 | setBusy(true); | 645 | setBusy(true); |
514 | 626 | editFile(state); | 646 | m_editThread = new PhotoEditThread(this, state); |
515 | 647 | connect(m_editThread, SIGNAL(finished()), this, SLOT(finishEditing())); | ||
516 | 648 | connect(m_editThread, SIGNAL(newSize(QSize)), this, SLOT(setSize(QSize))); | ||
517 | 649 | connect(m_editThread, SIGNAL(resetToOriginalSize()), this, SLOT(resetToOriginalSize())); | ||
518 | 650 | m_editThread->start(); | ||
519 | 651 | } | ||
520 | 652 | |||
521 | 653 | /*! | ||
522 | 654 | * \brief Photo::finishEditing do all the updates once the editing is done | ||
523 | 655 | */ | ||
524 | 656 | void Photo::finishEditing() | ||
525 | 657 | { | ||
526 | 658 | if (!m_editThread || m_editThread->isRunning()) | ||
527 | 659 | return; | ||
528 | 660 | |||
529 | 661 | const PhotoEditState &state = m_editThread->editState(); | ||
530 | 627 | GalleryManager::instance()->database()->getPhotoEditTable()->setEditState(id(), state); | 662 | GalleryManager::instance()->database()->getPhotoEditTable()->setEditState(id(), state); |
531 | 628 | 663 | ||
533 | 629 | if (orientation() != oldOrientation) | 664 | if (orientation() != m_editThread->oldOrientation()) |
534 | 630 | emit orientationChanged(); | 665 | emit orientationChanged(); |
535 | 631 | notifyDataChanged(); | 666 | notifyDataChanged(); |
536 | 632 | 667 | ||
537 | @@ -635,244 +670,10 @@ | |||
538 | 635 | emit galleryPathChanged(); | 670 | emit galleryPathChanged(); |
539 | 636 | emit galleryPreviewPathChanged(); | 671 | emit galleryPreviewPathChanged(); |
540 | 637 | emit galleryThumbnailPathChanged(); | 672 | emit galleryThumbnailPathChanged(); |
541 | 673 | m_editThread->deleteLater(); | ||
542 | 674 | m_editThread = 0; | ||
543 | 638 | setBusy(false); | 675 | setBusy(false); |
781 | 639 | } | 676 | emit editStackChanged(); |
545 | 640 | |||
546 | 641 | /*! | ||
547 | 642 | * \brief Photo::handleSimpleMetadataRotation | ||
548 | 643 | * Handler for the case of an image whose only change is to its | ||
549 | 644 | * orientation; used to skip re-encoding of JPEGs. | ||
550 | 645 | * \param state | ||
551 | 646 | */ | ||
552 | 647 | void Photo::handleSimpleMetadataRotation(const PhotoEditState& state) | ||
553 | 648 | { | ||
554 | 649 | PhotoMetadata* metadata = PhotoMetadata::fromFile(file()); | ||
555 | 650 | metadata->setOrientation(state.orientation_); | ||
556 | 651 | |||
557 | 652 | metadata->save(); | ||
558 | 653 | delete(metadata); | ||
559 | 654 | |||
560 | 655 | OrientationCorrection orig_correction = | ||
561 | 656 | OrientationCorrection::fromOrientation(m_originalOrientation); | ||
562 | 657 | OrientationCorrection dest_correction = | ||
563 | 658 | OrientationCorrection::fromOrientation(state.orientation_); | ||
564 | 659 | |||
565 | 660 | QSize new_size = m_originalSize; | ||
566 | 661 | int angle = dest_correction.getNormalizedRotationDifference(orig_correction); | ||
567 | 662 | |||
568 | 663 | if ((angle == 90) || (angle == 270)) { | ||
569 | 664 | new_size = m_originalSize.transposed(); | ||
570 | 665 | } | ||
571 | 666 | |||
572 | 667 | setSize(new_size); | ||
573 | 668 | } | ||
574 | 669 | |||
575 | 670 | /*! | ||
576 | 671 | * \brief Photo::editFile | ||
577 | 672 | * \param state | ||
578 | 673 | */ | ||
579 | 674 | void Photo::editFile(const PhotoEditState& state) | ||
580 | 675 | { | ||
581 | 676 | // As a special case, if editing to the original version, we simply restore | ||
582 | 677 | // from the original and call it a day. | ||
583 | 678 | if (state.isOriginal()) { | ||
584 | 679 | if (!m_caches.restoreOriginal()) | ||
585 | 680 | qDebug("Error restoring original for %s", qPrintable(file().filePath())); | ||
586 | 681 | else | ||
587 | 682 | setSize(originalSize(PhotoEditState::ORIGINAL_ORIENTATION)); | ||
588 | 683 | |||
589 | 684 | // As a courtesy, when the original goes away, we get rid of the other | ||
590 | 685 | // cached files too. | ||
591 | 686 | m_caches.discardCachedEnhanced(); | ||
592 | 687 | return; | ||
593 | 688 | } | ||
594 | 689 | |||
595 | 690 | if (!m_caches.cacheOriginal()) | ||
596 | 691 | qDebug("Error caching original for %s", qPrintable(file().filePath())); | ||
597 | 692 | |||
598 | 693 | if (state.is_enhanced_ && !m_caches.hasCachedEnhanced()) | ||
599 | 694 | createCachedEnhanced(); | ||
600 | 695 | |||
601 | 696 | if (!m_caches.overwriteFromCache(state.is_enhanced_)) | ||
602 | 697 | qDebug("Error overwriting %s from cache", qPrintable(file().filePath())); | ||
603 | 698 | |||
604 | 699 | // Have we been rotated and _not_ cropped? | ||
605 | 700 | if (fileFormatHasOrientation() && (!state.crop_rectangle_.isValid()) && | ||
606 | 701 | state.exposureCompensation_ == 0 && | ||
607 | 702 | (state.orientation_ != PhotoEditState::ORIGINAL_ORIENTATION)) { | ||
608 | 703 | // Yes; skip out on decoding and re-encoding the image. | ||
609 | 704 | handleSimpleMetadataRotation(state); | ||
610 | 705 | return; | ||
611 | 706 | } | ||
612 | 707 | |||
613 | 708 | // TODO: we might be able to avoid reading/writing pixel data (and other | ||
614 | 709 | // more general optimizations) under certain conditions here. Might be worth | ||
615 | 710 | // doing if it doesn't make the code too much worse. | ||
616 | 711 | // | ||
617 | 712 | // At the moment, we are skipping at least one decode and one encode in cases | ||
618 | 713 | // where a .jpeg file has been rotated, but not cropped, since rotation can be | ||
619 | 714 | // controlled by manipulating its metadata without having to modify pixel data; | ||
620 | 715 | // please see the method handle_simple_metadata_rotation() for details. | ||
621 | 716 | |||
622 | 717 | QImage image(file().filePath(), m_fileFormat.toStdString().c_str()); | ||
623 | 718 | if (image.isNull()) { | ||
624 | 719 | qDebug("Error loading %s for editing", qPrintable(file().filePath())); | ||
625 | 720 | return; | ||
626 | 721 | } | ||
627 | 722 | PhotoMetadata* metadata = PhotoMetadata::fromFile(file()); | ||
628 | 723 | |||
629 | 724 | if (fileFormatHasOrientation() && | ||
630 | 725 | state.orientation_ != PhotoEditState::ORIGINAL_ORIENTATION) | ||
631 | 726 | metadata->setOrientation(state.orientation_); | ||
632 | 727 | |||
633 | 728 | if (fileFormatHasOrientation() && | ||
634 | 729 | metadata->orientation() != TOP_LEFT_ORIGIN) | ||
635 | 730 | image = image.transformed(metadata->orientationTransform()); | ||
636 | 731 | else if (state.orientation_ != PhotoEditState::ORIGINAL_ORIENTATION && | ||
637 | 732 | state.orientation_ != TOP_LEFT_ORIGIN) | ||
638 | 733 | image = image.transformed( | ||
639 | 734 | OrientationCorrection::fromOrientation(state.orientation_).toTransform()); | ||
640 | 735 | |||
641 | 736 | // Cache this here so we may be able to avoid another JPEG decode later just | ||
642 | 737 | // to find the dimensions. | ||
643 | 738 | if (!m_originalSize.isValid()) | ||
644 | 739 | m_originalSize = image.size(); | ||
645 | 740 | |||
646 | 741 | if (state.crop_rectangle_.isValid()) | ||
647 | 742 | image = image.copy(state.crop_rectangle_); | ||
648 | 743 | |||
649 | 744 | // exposure compensation | ||
650 | 745 | if (state.exposureCompensation_ != 0.0) { | ||
651 | 746 | image = compensateExposure(image, state.exposureCompensation_); | ||
652 | 747 | } | ||
653 | 748 | |||
654 | 749 | // exposure compensation | ||
655 | 750 | if (!state.colorBalance_.isNull()) { | ||
656 | 751 | const QVector4D &v = state.colorBalance_; | ||
657 | 752 | image = doColorBalance(image, v.x(), v.y(), v.z(), v.w()); | ||
658 | 753 | } | ||
659 | 754 | |||
660 | 755 | QSize new_size = image.size(); | ||
661 | 756 | |||
662 | 757 | // We need to apply the reverse transformation so that when we reload the | ||
663 | 758 | // file and reapply the transformation it comes out correctly. | ||
664 | 759 | if (fileFormatHasOrientation() && | ||
665 | 760 | metadata->orientation() != TOP_LEFT_ORIGIN) | ||
666 | 761 | image = image.transformed(metadata->orientationTransform().inverted()); | ||
667 | 762 | |||
668 | 763 | bool saved = image.save(file().filePath(), m_fileFormat.toStdString().c_str(), 90); | ||
669 | 764 | if (saved && fileFormatHasMetadata()) | ||
670 | 765 | saved = metadata->save(); | ||
671 | 766 | if (!saved) | ||
672 | 767 | qDebug("Error saving edited %s", qPrintable(file().filePath())); | ||
673 | 768 | |||
674 | 769 | delete metadata; | ||
675 | 770 | |||
676 | 771 | setSize(new_size); | ||
677 | 772 | } | ||
678 | 773 | |||
679 | 774 | /*! | ||
680 | 775 | * \brief Photo::createCachedEnhanced | ||
681 | 776 | */ | ||
682 | 777 | void Photo::createCachedEnhanced() | ||
683 | 778 | { | ||
684 | 779 | if (!m_caches.cacheEnhancedFromOriginal()) { | ||
685 | 780 | qDebug("Error creating enhanced file for %s", qPrintable(file().filePath())); | ||
686 | 781 | return; | ||
687 | 782 | } | ||
688 | 783 | |||
689 | 784 | QFileInfo to_enhance = m_caches.enhancedFile(); | ||
690 | 785 | PhotoMetadata* metadata = PhotoMetadata::fromFile(to_enhance); | ||
691 | 786 | |||
692 | 787 | QImage unenhanced_img(to_enhance.filePath(), m_fileFormat.toStdString().c_str()); | ||
693 | 788 | int width = unenhanced_img.width(); | ||
694 | 789 | int height = unenhanced_img.height(); | ||
695 | 790 | |||
696 | 791 | QImage sample_img = (unenhanced_img.width() > 400) ? | ||
697 | 792 | unenhanced_img.scaledToWidth(400) : unenhanced_img; | ||
698 | 793 | |||
699 | 794 | AutoEnhanceTransformation enhance_txn = AutoEnhanceTransformation(sample_img); | ||
700 | 795 | |||
701 | 796 | QImage::Format dest_format = unenhanced_img.format(); | ||
702 | 797 | |||
703 | 798 | // Can't write into indexed images, due to a limitation in Qt. | ||
704 | 799 | if (dest_format == QImage::Format_Indexed8) | ||
705 | 800 | dest_format = QImage::Format_RGB32; | ||
706 | 801 | |||
707 | 802 | QImage enhanced_image(width, height, dest_format); | ||
708 | 803 | |||
709 | 804 | for (int j = 0; j < height; j++) { | ||
710 | 805 | //QApplication::processEvents(); | ||
711 | 806 | for (int i = 0; i < width; i++) { | ||
712 | 807 | QColor px = enhance_txn.transformPixel( | ||
713 | 808 | QColor(unenhanced_img.pixel(i, j))); | ||
714 | 809 | enhanced_image.setPixel(i, j, px.rgb()); | ||
715 | 810 | } | ||
716 | 811 | } | ||
717 | 812 | |||
718 | 813 | bool saved = enhanced_image.save(to_enhance.filePath(), | ||
719 | 814 | m_fileFormat.toStdString().c_str(), 99); | ||
720 | 815 | if (saved && fileFormatHasMetadata()) | ||
721 | 816 | saved = metadata->save(); | ||
722 | 817 | if (!saved) { | ||
723 | 818 | qDebug("Error saving enhanced file for %s", qPrintable(file().filePath())); | ||
724 | 819 | m_caches.discardCachedEnhanced(); | ||
725 | 820 | } | ||
726 | 821 | |||
727 | 822 | delete metadata; | ||
728 | 823 | } | ||
729 | 824 | |||
730 | 825 | /*! | ||
731 | 826 | * \brief Photo::compensateExposure Compensates the exposure | ||
732 | 827 | * Compensating the exposure is a change in brightnes | ||
733 | 828 | * \param image Image to change the brightnes | ||
734 | 829 | * \param compansation -1.0 is total dark, +1.0 is total bright | ||
735 | 830 | * \return The image with adjusted brightnes | ||
736 | 831 | */ | ||
737 | 832 | QImage Photo::compensateExposure(const QImage &image, qreal compansation) | ||
738 | 833 | { | ||
739 | 834 | int shift = qBound(-255, (int)(255*compansation), 255); | ||
740 | 835 | QImage result(image.width(), image.height(), image.format()); | ||
741 | 836 | |||
742 | 837 | for (int j = 0; j < image.height(); j++) { | ||
743 | 838 | QApplication::processEvents(); | ||
744 | 839 | for (int i = 0; i <image.width(); i++) { | ||
745 | 840 | QColor px = image.pixel(i, j); | ||
746 | 841 | int red = qBound(0, px.red() + shift, 255); | ||
747 | 842 | int green = qBound(0, px.green() + shift, 255); | ||
748 | 843 | int blue = qBound(0, px.blue() + shift, 255); | ||
749 | 844 | result.setPixel(i, j, qRgb(red, green, blue)); | ||
750 | 845 | } | ||
751 | 846 | } | ||
752 | 847 | |||
753 | 848 | return result; | ||
754 | 849 | } | ||
755 | 850 | |||
756 | 851 | /*! | ||
757 | 852 | * \brief Photo::colorBalance | ||
758 | 853 | * \param image | ||
759 | 854 | * \param brightness 0 is total dark, 1 is as the original, grater than 1 is brigther | ||
760 | 855 | * \param contrast from 0 maybe 5. 1 is as the original | ||
761 | 856 | * \param saturation from 0 maybe 5. 1 is as the original | ||
762 | 857 | * \param hue from 0 to 360. 0 and 360 is as the original | ||
763 | 858 | * \return | ||
764 | 859 | */ | ||
765 | 860 | QImage Photo::doColorBalance(const QImage &image, qreal brightness, qreal contrast, qreal saturation, qreal hue) | ||
766 | 861 | { | ||
767 | 862 | QImage result(image.width(), image.height(), image.format()); | ||
768 | 863 | |||
769 | 864 | ColorBalance cb(brightness, contrast, saturation, hue); | ||
770 | 865 | |||
771 | 866 | for (int j = 0; j < image.height(); j++) { | ||
772 | 867 | QApplication::processEvents(); | ||
773 | 868 | for (int i = 0; i <image.width(); i++) { | ||
774 | 869 | QColor px = image.pixel(i, j); | ||
775 | 870 | QColor tpx = cb.transformPixel(px); | ||
776 | 871 | result.setPixel(i, j, tpx.rgb()); | ||
777 | 872 | } | ||
778 | 873 | } | ||
779 | 874 | |||
780 | 875 | return result; | ||
782 | 876 | } | 677 | } |
783 | 877 | 678 | ||
784 | 878 | /*! | 679 | /*! |
785 | @@ -901,6 +702,15 @@ | |||
786 | 901 | } | 702 | } |
787 | 902 | 703 | ||
788 | 903 | /*! | 704 | /*! |
789 | 705 | * \brief Photo::fileFormat returns the file format as QString | ||
790 | 706 | * \return | ||
791 | 707 | */ | ||
792 | 708 | const QString &Photo::fileFormat() const | ||
793 | 709 | { | ||
794 | 710 | return m_fileFormat; | ||
795 | 711 | } | ||
796 | 712 | |||
797 | 713 | /*! | ||
798 | 904 | * \brief Photo::fileFormatHasMetadata | 714 | * \brief Photo::fileFormatHasMetadata |
799 | 905 | * \return | 715 | * \return |
800 | 906 | */ | 716 | */ |
801 | @@ -927,3 +737,22 @@ | |||
802 | 927 | { | 737 | { |
803 | 928 | m_originalOrientation = orientation; | 738 | m_originalOrientation = orientation; |
804 | 929 | } | 739 | } |
805 | 740 | |||
806 | 741 | /*! | ||
807 | 742 | * \brief Photo::originalOrientation returns the original orientation | ||
808 | 743 | * \return | ||
809 | 744 | */ | ||
810 | 745 | Orientation Photo::originalOrientation() const | ||
811 | 746 | { | ||
812 | 747 | return m_originalOrientation; | ||
813 | 748 | } | ||
814 | 749 | |||
815 | 750 | /*! | ||
816 | 751 | * \brief Photo::originalSize | ||
817 | 752 | * \return | ||
818 | 753 | */ | ||
819 | 754 | const QSize &Photo::originalSize() | ||
820 | 755 | { | ||
821 | 756 | originalSize(PhotoEditState::ORIGINAL_ORIENTATION); | ||
822 | 757 | return m_originalSize; | ||
823 | 758 | } | ||
824 | 930 | 759 | ||
825 | === modified file 'src/photo/photo.h' | |||
826 | --- src/photo/photo.h 2013-06-17 12:57:21 +0000 | |||
827 | +++ src/photo/photo.h 2013-07-26 16:08:26 +0000 | |||
828 | @@ -31,6 +31,7 @@ | |||
829 | 31 | #include "orientation.h" | 31 | #include "orientation.h" |
830 | 32 | 32 | ||
831 | 33 | class PhotoEditState; | 33 | class PhotoEditState; |
832 | 34 | class PhotoEditThread; | ||
833 | 34 | class PhotoPrivate; | 35 | class PhotoPrivate; |
834 | 35 | 36 | ||
835 | 36 | /*! | 37 | /*! |
836 | @@ -81,6 +82,12 @@ | |||
837 | 81 | Q_INVOKABLE void crop(QVariant vrect); | 82 | Q_INVOKABLE void crop(QVariant vrect); |
838 | 82 | 83 | ||
839 | 83 | void setOriginalOrientation(Orientation orientation); | 84 | void setOriginalOrientation(Orientation orientation); |
840 | 85 | Orientation originalOrientation() const; | ||
841 | 86 | const QSize &originalSize(); | ||
842 | 87 | |||
843 | 88 | const QString &fileFormat() const; | ||
844 | 89 | bool fileFormatHasMetadata() const; | ||
845 | 90 | bool fileFormatHasOrientation() const; | ||
846 | 84 | 91 | ||
847 | 85 | signals: | 92 | signals: |
848 | 86 | void editStackChanged(); | 93 | void editStackChanged(); |
849 | @@ -88,22 +95,21 @@ | |||
850 | 88 | protected: | 95 | protected: |
851 | 89 | virtual void destroySource(bool destroyBacking, bool asOrphan); | 96 | virtual void destroySource(bool destroyBacking, bool asOrphan); |
852 | 90 | 97 | ||
853 | 98 | private Q_SLOTS: | ||
854 | 99 | void resetToOriginalSize(); | ||
855 | 100 | void finishEditing(); | ||
856 | 101 | |||
857 | 91 | private: | 102 | private: |
858 | 92 | const PhotoEditState& currentState() const; | 103 | const PhotoEditState& currentState() const; |
859 | 93 | QSize originalSize(Orientation orientation); | 104 | QSize originalSize(Orientation orientation); |
860 | 94 | void makeUndoableEdit(const PhotoEditState& state); | 105 | void makeUndoableEdit(const PhotoEditState& state); |
862 | 95 | void save(const PhotoEditState& state, Orientation oldOrientation); | 106 | void asyncEdit(const PhotoEditState& state); |
863 | 96 | void editFile(const PhotoEditState& state); | 107 | void editFile(const PhotoEditState& state); |
864 | 97 | void createCachedEnhanced(); | ||
865 | 98 | QImage compensateExposure(const QImage& image, qreal compansation); | ||
866 | 99 | QImage doColorBalance(const QImage& image, qreal brightness, qreal contrast, qreal saturation, qreal hue); | ||
867 | 100 | void appendPathParams(QUrl* url, Orientation orientation, const int sizeLevel) const; | 108 | void appendPathParams(QUrl* url, Orientation orientation, const int sizeLevel) const; |
868 | 101 | void handleSimpleMetadataRotation(const PhotoEditState& state); | ||
869 | 102 | bool fileFormatHasMetadata() const; | ||
870 | 103 | bool fileFormatHasOrientation() const; | ||
871 | 104 | 109 | ||
872 | 105 | QString m_fileFormat; | 110 | QString m_fileFormat; |
873 | 106 | int m_editRevision; // How many times the pixel data has been modified by us. | 111 | int m_editRevision; // How many times the pixel data has been modified by us. |
874 | 112 | PhotoEditThread *m_editThread; | ||
875 | 107 | PhotoCaches m_caches; | 113 | PhotoCaches m_caches; |
876 | 108 | 114 | ||
877 | 109 | // We cache this data to avoid an image read at various times. | 115 | // We cache this data to avoid an image read at various times. |
878 | 110 | 116 | ||
879 | === modified file 'tests/unittests/stubs/photo_stub.cpp' | |||
880 | --- tests/unittests/stubs/photo_stub.cpp 2013-06-17 12:57:21 +0000 | |||
881 | +++ tests/unittests/stubs/photo_stub.cpp 2013-07-26 16:08:26 +0000 | |||
882 | @@ -159,3 +159,12 @@ | |||
883 | 159 | { | 159 | { |
884 | 160 | m_originalOrientation = orientation; | 160 | m_originalOrientation = orientation; |
885 | 161 | } | 161 | } |
886 | 162 | |||
887 | 163 | void Photo::resetToOriginalSize() | ||
888 | 164 | { | ||
889 | 165 | } | ||
890 | 166 | |||
891 | 167 | void Photo::finishEditing() | ||
892 | 168 | { | ||
893 | 169 | } | ||
894 | 170 |
PASSED: Continuous integration, rev:787 jenkins. qa.ubuntu. com/job/ gallery- app-ci/ 372/ jenkins. qa.ubuntu. com/job/ gallery- app-saucy- amd64-ci/ 182 jenkins. qa.ubuntu. com/job/ gallery- app-saucy- armhf-ci/ 182 jenkins. qa.ubuntu. com/job/ gallery- app-saucy- armhf-ci/ 182/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ gallery- app-saucy- i386-ci/ 182 jenkins. qa.ubuntu. com/job/ generic- mediumtests- saucy/1602 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- saucy/1606 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- saucy/1606/ artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ generic- mediumtests- runner- saucy/1356
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins: 8080/job/ gallery- app-ci/ 372/rebuild
http://