Merge lp:~schwann/gallery-app/gallery-edit-thread into lp:gallery-app

Proposed by Günter Schwann
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
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.

To post a comment you must log in.
788. By Günter Schwann

Properly block undo/redo

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Gustavo Pichorim Boiko (boiko) wrote :

Looks good.

review: Approve

Preview Diff

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

Subscribers

People subscribed via source and target branches