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
=== modified file 'src/media/media-source.h'
--- src/media/media-source.h 2013-07-05 08:12:31 +0000
+++ src/media/media-source.h 2013-07-26 16:08:26 +0000
@@ -103,7 +103,6 @@
103 void setFileTimestamp(const QDateTime& timestamp);103 void setFileTimestamp(const QDateTime& timestamp);
104104
105 const QSize& size();105 const QSize& size();
106 void setSize(const QSize& size);
107106
108 qint64 id() const;107 qint64 id() const;
109 void setId(qint64 id);108 void setId(qint64 id);
@@ -112,6 +111,9 @@
112111
113 void setMediaTable(MediaTable *mediaTable);112 void setMediaTable(MediaTable *mediaTable);
114113
114public Q_SLOTS:
115 void setSize(const QSize& size);
116
115protected:117protected:
116 bool isSizeSet() const;118 bool isSizeSet() const;
117119
118120
=== modified file 'src/photo/CMakeLists.txt'
--- src/photo/CMakeLists.txt 2013-07-03 11:53:48 +0000
+++ src/photo/CMakeLists.txt 2013-07-26 16:08:26 +0000
@@ -18,12 +18,14 @@
18 photo.h18 photo.h
19 photo-caches.h19 photo-caches.h
20 photo-edit-state.h20 photo-edit-state.h
21 photo-edit-thread.h
21 )22 )
2223
23set(gallery_photo_SRCS24set(gallery_photo_SRCS
24 photo.cpp25 photo.cpp
25 photo-caches.cpp26 photo-caches.cpp
26 photo-edit-state.cpp27 photo-edit-state.cpp
28 photo-edit-thread.cpp
27 )29 )
2830
29add_library(${GALLERY_PHOTO_LIB}31add_library(${GALLERY_PHOTO_LIB}
3032
=== added file 'src/photo/photo-edit-thread.cpp'
--- src/photo/photo-edit-thread.cpp 1970-01-01 00:00:00 +0000
+++ src/photo/photo-edit-thread.cpp 2013-07-26 16:08:26 +0000
@@ -0,0 +1,285 @@
1/*
2 * Copyright (C) 2013 Canonical Ltd
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#include "photo-edit-thread.h"
18#include "photo.h"
19
20// medialoader
21#include "photo-metadata.h"
22
23// util
24#include "imaging.h"
25
26#include <QDebug>
27
28/*!
29 * \brief PhotoEditThread::PhotoEditThread
30 */
31PhotoEditThread::PhotoEditThread(Photo *photo, const PhotoEditState &editState)
32 : QThread(),
33 m_photo(photo),
34 m_editState(editState),
35 m_caches(photo->file()),
36 m_oldOrientation(photo->orientation())
37{
38}
39
40/*!
41 * \brief PhotoEditThread::editState resturns the editing stse used for this processing
42 * \return
43 */
44const PhotoEditState &PhotoEditThread::editState() const
45{
46 return m_editState;
47}
48
49/*!
50 * \brief PhotoEditThread::oldOrientation returns the orientation of the photo before the editing
51 * \return
52 */
53Orientation PhotoEditThread::oldOrientation() const
54{
55 return m_oldOrientation;
56}
57
58/*!
59 * \brief PhotoEditThread::run \reimp
60 */
61void PhotoEditThread::run()
62{
63 // As a special case, if editing to the original version, we simply restore
64 // from the original and call it a day.
65 if (m_editState.isOriginal()) {
66 if (!m_caches.restoreOriginal())
67 qWarning() << "Error restoring original for" << m_photo->file().filePath();
68 else
69 Q_EMIT resetToOriginalSize();
70
71 // As a courtesy, when the original goes away, we get rid of the other
72 // cached files too.
73 m_caches.discardCachedEnhanced();
74 return;
75 }
76
77 if (!m_caches.cacheOriginal())
78 qWarning() << "Error caching original for" << m_photo->file().filePath();
79
80 if (m_editState.is_enhanced_ && !m_caches.hasCachedEnhanced())
81 createCachedEnhanced();
82
83 if (!m_caches.overwriteFromCache(m_editState.is_enhanced_))
84 qWarning() << "Error overwriting" << m_photo->file().filePath() << "from cache";
85
86 // Have we been rotated and _not_ cropped?
87 if (m_photo->fileFormatHasOrientation() && (!m_editState.crop_rectangle_.isValid()) &&
88 m_editState.exposureCompensation_ == 0 &&
89 (m_editState.orientation_ != PhotoEditState::ORIGINAL_ORIENTATION)) {
90 // Yes; skip out on decoding and re-encoding the image.
91 handleSimpleMetadataRotation(m_editState);
92 return;
93 }
94
95 // TODO: we might be able to avoid reading/writing pixel data (and other
96 // more general optimizations) under certain conditions here. Might be worth
97 // doing if it doesn't make the code too much worse.
98 //
99 // At the moment, we are skipping at least one decode and one encode in cases
100 // where a .jpeg file has been rotated, but not cropped, since rotation can be
101 // controlled by manipulating its metadata without having to modify pixel data;
102 // please see the method handle_simple_metadata_rotation() for details.
103
104 QImage image(m_photo->file().filePath(), m_photo->fileFormat().toStdString().c_str());
105 if (image.isNull()) {
106 qWarning() << "Error loading" << m_photo->file().filePath() << "for editing";
107 return;
108 }
109 PhotoMetadata* metadata = PhotoMetadata::fromFile(m_photo->file());
110
111 if (m_photo->fileFormatHasOrientation() &&
112 m_editState.orientation_ != PhotoEditState::ORIGINAL_ORIENTATION)
113 metadata->setOrientation(m_editState.orientation_);
114
115 if (m_photo->fileFormatHasOrientation() &&
116 metadata->orientation() != TOP_LEFT_ORIGIN)
117 image = image.transformed(metadata->orientationTransform());
118 else if (m_editState.orientation_ != PhotoEditState::ORIGINAL_ORIENTATION &&
119 m_editState.orientation_ != TOP_LEFT_ORIGIN)
120 image = image.transformed(
121 OrientationCorrection::fromOrientation(m_editState.orientation_).toTransform());
122
123 if (m_editState.crop_rectangle_.isValid())
124 image = image.copy(m_editState.crop_rectangle_);
125
126 // exposure compensation
127 if (m_editState.exposureCompensation_ != 0.0) {
128 image = compensateExposure(image, m_editState.exposureCompensation_);
129 }
130
131 // exposure compensation
132 if (!m_editState.colorBalance_.isNull()) {
133 const QVector4D &v = m_editState.colorBalance_;
134 image = doColorBalance(image, v.x(), v.y(), v.z(), v.w());
135 }
136
137 QSize new_size = image.size();
138
139 // We need to apply the reverse transformation so that when we reload the
140 // file and reapply the transformation it comes out correctly.
141 if (m_photo->fileFormatHasOrientation() &&
142 metadata->orientation() != TOP_LEFT_ORIGIN)
143 image = image.transformed(metadata->orientationTransform().inverted());
144
145 bool saved = image.save(m_photo->file().filePath(), m_photo->fileFormat().toStdString().c_str(), 90);
146 if (saved && m_photo->fileFormatHasMetadata())
147 saved = metadata->save();
148 if (!saved)
149 qWarning() << "Error saving edited" << m_photo->file().filePath();
150
151 delete metadata;
152
153 Q_EMIT newSize(new_size);
154}
155
156/*!
157 * \brief PhotoEditThread::handleSimpleMetadataRotation
158 * Handler for the case of an image whose only change is to its
159 * orientation; used to skip re-encoding of JPEGs.
160 * \param state
161 */
162void PhotoEditThread::handleSimpleMetadataRotation(const PhotoEditState& state)
163{
164 PhotoMetadata* metadata = PhotoMetadata::fromFile(m_photo->file());
165 metadata->setOrientation(state.orientation_);
166
167 metadata->save();
168 delete(metadata);
169
170 OrientationCorrection orig_correction =
171 OrientationCorrection::fromOrientation(m_photo->originalOrientation());
172 OrientationCorrection dest_correction =
173 OrientationCorrection::fromOrientation(state.orientation_);
174
175 QSize new_size = m_photo->originalSize();
176 int angle = dest_correction.getNormalizedRotationDifference(orig_correction);
177
178 if ((angle == 90) || (angle == 270)) {
179 new_size = m_photo->originalSize().transposed();
180 }
181
182 Q_EMIT newSize(new_size);
183}
184
185/*!
186 * \brief PhotoEditThread::createCachedEnhanced
187 */
188void PhotoEditThread::createCachedEnhanced()
189{
190 if (!m_caches.cacheEnhancedFromOriginal()) {
191 qWarning() << "Error creating enhanced file for" << m_photo->file().filePath();
192 return;
193 }
194
195 QFileInfo to_enhance = m_photo->enhancedFile();
196 PhotoMetadata* metadata = PhotoMetadata::fromFile(to_enhance);
197
198 QImage unenhanced_img(to_enhance.filePath(), m_photo->fileFormat().toStdString().c_str());
199 int width = unenhanced_img.width();
200 int height = unenhanced_img.height();
201
202 QImage sample_img = (unenhanced_img.width() > 400) ?
203 unenhanced_img.scaledToWidth(400) : unenhanced_img;
204
205 AutoEnhanceTransformation enhance_txn = AutoEnhanceTransformation(sample_img);
206
207 QImage::Format dest_format = unenhanced_img.format();
208
209 // Can't write into indexed images, due to a limitation in Qt.
210 if (dest_format == QImage::Format_Indexed8)
211 dest_format = QImage::Format_RGB32;
212
213 QImage enhanced_image(width, height, dest_format);
214
215 for (int j = 0; j < height; j++) {
216 for (int i = 0; i < width; i++) {
217 QColor px = enhance_txn.transformPixel(
218 QColor(unenhanced_img.pixel(i, j)));
219 enhanced_image.setPixel(i, j, px.rgb());
220 }
221 }
222
223 bool saved = enhanced_image.save(to_enhance.filePath(),
224 m_photo->fileFormat().toStdString().c_str(), 90);
225 if (saved && m_photo->fileFormatHasMetadata())
226 saved = metadata->save();
227 if (!saved) {
228 qWarning() << "Error saving enhanced file for" << m_photo->file().filePath();
229 m_caches.discardCachedEnhanced();
230 }
231
232 delete metadata;
233}
234
235/*!
236 * \brief PhotoEditThread::compensateExposure Compensates the exposure
237 * Compensating the exposure is a change in brightnes
238 * \param image Image to change the brightnes
239 * \param compansation -1.0 is total dark, +1.0 is total bright
240 * \return The image with adjusted brightnes
241 */
242QImage PhotoEditThread::compensateExposure(const QImage &image, qreal compansation)
243{
244 int shift = qBound(-255, (int)(255*compansation), 255);
245 QImage result(image.width(), image.height(), image.format());
246
247 for (int j = 0; j < image.height(); j++) {
248 for (int i = 0; i <image.width(); i++) {
249 QColor px = image.pixel(i, j);
250 int red = qBound(0, px.red() + shift, 255);
251 int green = qBound(0, px.green() + shift, 255);
252 int blue = qBound(0, px.blue() + shift, 255);
253 result.setPixel(i, j, qRgb(red, green, blue));
254 }
255 }
256
257 return result;
258}
259
260/*!
261 * \brief PhotoEditThread::colorBalance
262 * \param image
263 * \param brightness 0 is total dark, 1 is as the original, grater than 1 is brigther
264 * \param contrast from 0 maybe 5. 1 is as the original
265 * \param saturation from 0 maybe 5. 1 is as the original
266 * \param hue from 0 to 360. 0 and 360 is as the original
267 * \return
268 */
269QImage PhotoEditThread::doColorBalance(const QImage &image, qreal brightness, qreal contrast, qreal saturation, qreal hue)
270{
271 QImage result(image.width(), image.height(), image.format());
272
273 ColorBalance cb(brightness, contrast, saturation, hue);
274
275 for (int j = 0; j < image.height(); j++) {
276 for (int i = 0; i <image.width(); i++) {
277 QColor px = image.pixel(i, j);
278 QColor tpx = cb.transformPixel(px);
279 result.setPixel(i, j, tpx.rgb());
280 }
281 }
282
283 return result;
284}
285
0286
=== added file 'src/photo/photo-edit-thread.h'
--- src/photo/photo-edit-thread.h 1970-01-01 00:00:00 +0000
+++ src/photo/photo-edit-thread.h 2013-07-26 16:08:26 +0000
@@ -0,0 +1,63 @@
1/*
2 * Copyright (C) 2013 Canonical Ltd
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 3 as
6 * published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17#ifndef GALLERY_PHOTO_EDIT_THREAD_H_
18#define GALLERY_PHOTO_EDIT_THREAD_H_
19
20#include "photo-caches.h"
21#include "photo-edit-state.h"
22
23// util
24#include "orientation.h"
25
26#include <QImage>
27#include <QThread>
28#include <QUrl>
29
30class Photo;
31
32/*!
33 * \brief The PhotoEditThread class
34 */
35class PhotoEditThread: public QThread
36{
37 Q_OBJECT
38public:
39 PhotoEditThread(Photo *photo, const PhotoEditState& editState);
40
41 const PhotoEditState& editState() const;
42 Orientation oldOrientation() const;
43
44Q_SIGNALS:
45 void newSize(QSize size);
46 void resetToOriginalSize();
47
48protected:
49 void run() Q_DECL_OVERRIDE;
50
51private:
52 void createCachedEnhanced();
53 QImage compensateExposure(const QImage& image, qreal compansation);
54 QImage doColorBalance(const QImage& image, qreal brightness, qreal contrast, qreal saturation, qreal hue);
55 void handleSimpleMetadataRotation(const PhotoEditState& state);
56
57 Photo *m_photo;
58 PhotoEditState m_editState;
59 PhotoCaches m_caches;
60 Orientation m_oldOrientation;
61};
62
63#endif
064
=== modified file 'src/photo/photo.cpp'
--- src/photo/photo.cpp 2013-07-05 08:12:31 +0000
+++ src/photo/photo.cpp 2013-07-26 16:08:26 +0000
@@ -23,6 +23,7 @@
2323
24#include "photo.h"24#include "photo.h"
25#include "photo-edit-state.h"25#include "photo-edit-state.h"
26#include "photo-edit-thread.h"
2627
27// database28// database
28#include "database.h"29#include "database.h"
@@ -185,6 +186,7 @@
185Photo::Photo(const QFileInfo& file)186Photo::Photo(const QFileInfo& file)
186 : MediaSource(file),187 : MediaSource(file),
187 m_editRevision(0),188 m_editRevision(0),
189 m_editThread(0),
188 m_caches(file),190 m_caches(file),
189 m_originalSize(),191 m_originalSize(),
190 m_originalOrientation(TOP_LEFT_ORIGIN),192 m_originalOrientation(TOP_LEFT_ORIGIN),
@@ -201,6 +203,10 @@
201 */203 */
202Photo::~Photo()204Photo::~Photo()
203{205{
206 if (m_editThread) {
207 m_editThread->wait();
208 finishEditing();
209 }
204 delete(d_ptr);210 delete(d_ptr);
205}211}
206212
@@ -347,13 +353,15 @@
347void Photo::undo()353void Photo::undo()
348{354{
349 Q_D(Photo);355 Q_D(Photo);
350 Orientation old_orientation = orientation();356 if (busy()) {
357 qWarning() << "Don't start edit operation, while another one is running";
358 return;
359 }
351360
352 PhotoEditState prev = d->editStack()->current();361 PhotoEditState prev = d->editStack()->current();
353 PhotoEditState next = d->editStack()->undo();362 PhotoEditState next = d->editStack()->undo();
354 if (next != prev) {363 if (next != prev) {
355 save(next, old_orientation);364 asyncEdit(next);
356 emit editStackChanged();
357 }365 }
358}366}
359367
@@ -363,13 +371,15 @@
363void Photo::redo()371void Photo::redo()
364{372{
365 Q_D(Photo);373 Q_D(Photo);
366 Orientation old_orientation = orientation();374 if (busy()) {
375 qWarning() << "Don't start edit operation, while another one is running";
376 return;
377 }
367378
368 PhotoEditState prev = d->editStack()->current();379 PhotoEditState prev = d->editStack()->current();
369 PhotoEditState next = d->editStack()->redo();380 PhotoEditState next = d->editStack()->redo();
370 if (next != prev) {381 if (next != prev) {
371 save(next, old_orientation);382 asyncEdit(next);
372 emit editStackChanged();
373 }383 }
374}384}
375385
@@ -556,6 +566,14 @@
556}566}
557567
558/*!568/*!
569 * \brief Photo::resetToOriginalSize set the size to the one of the orifinal photo
570 */
571void Photo::resetToOriginalSize()
572{
573 setSize(originalSize(PhotoEditState::ORIGINAL_ORIENTATION));
574}
575
576/*!
559 * \brief Photo::currentState577 * \brief Photo::currentState
560 * \return578 * \return
561 */579 */
@@ -607,26 +625,43 @@
607 */625 */
608void Photo::makeUndoableEdit(const PhotoEditState& state)626void Photo::makeUndoableEdit(const PhotoEditState& state)
609{627{
628 if (busy()) {
629 qWarning() << "Don't start edit operation, while another one is running";
630 return;
631 }
632
610 Q_D(Photo);633 Q_D(Photo);
611 Orientation old_orientation = orientation();
612
613 d->editStack()->pushEdit(state);634 d->editStack()->pushEdit(state);
614 save(state, old_orientation);635 asyncEdit(state);
615 emit editStackChanged();
616}636}
617637
618/*!638/*!
619 * \brief Photo::save639 * \brief Photo::asyncEdit does edit the photo according to the given state
620 * \param state640 * in a background task
621 * \param oldOrientation641 * \param state the new editing state
622 */642 */
623void Photo::save(const PhotoEditState& state, Orientation oldOrientation)643void Photo::asyncEdit(const PhotoEditState& state)
624{644{
625 setBusy(true);645 setBusy(true);
626 editFile(state);646 m_editThread = new PhotoEditThread(this, state);
647 connect(m_editThread, SIGNAL(finished()), this, SLOT(finishEditing()));
648 connect(m_editThread, SIGNAL(newSize(QSize)), this, SLOT(setSize(QSize)));
649 connect(m_editThread, SIGNAL(resetToOriginalSize()), this, SLOT(resetToOriginalSize()));
650 m_editThread->start();
651}
652
653/*!
654 * \brief Photo::finishEditing do all the updates once the editing is done
655 */
656void Photo::finishEditing()
657{
658 if (!m_editThread || m_editThread->isRunning())
659 return;
660
661 const PhotoEditState &state = m_editThread->editState();
627 GalleryManager::instance()->database()->getPhotoEditTable()->setEditState(id(), state);662 GalleryManager::instance()->database()->getPhotoEditTable()->setEditState(id(), state);
628663
629 if (orientation() != oldOrientation)664 if (orientation() != m_editThread->oldOrientation())
630 emit orientationChanged();665 emit orientationChanged();
631 notifyDataChanged();666 notifyDataChanged();
632667
@@ -635,244 +670,10 @@
635 emit galleryPathChanged();670 emit galleryPathChanged();
636 emit galleryPreviewPathChanged();671 emit galleryPreviewPathChanged();
637 emit galleryThumbnailPathChanged();672 emit galleryThumbnailPathChanged();
673 m_editThread->deleteLater();
674 m_editThread = 0;
638 setBusy(false);675 setBusy(false);
639}676 emit editStackChanged();
640
641/*!
642 * \brief Photo::handleSimpleMetadataRotation
643 * Handler for the case of an image whose only change is to its
644 * orientation; used to skip re-encoding of JPEGs.
645 * \param state
646 */
647void Photo::handleSimpleMetadataRotation(const PhotoEditState& state)
648{
649 PhotoMetadata* metadata = PhotoMetadata::fromFile(file());
650 metadata->setOrientation(state.orientation_);
651
652 metadata->save();
653 delete(metadata);
654
655 OrientationCorrection orig_correction =
656 OrientationCorrection::fromOrientation(m_originalOrientation);
657 OrientationCorrection dest_correction =
658 OrientationCorrection::fromOrientation(state.orientation_);
659
660 QSize new_size = m_originalSize;
661 int angle = dest_correction.getNormalizedRotationDifference(orig_correction);
662
663 if ((angle == 90) || (angle == 270)) {
664 new_size = m_originalSize.transposed();
665 }
666
667 setSize(new_size);
668}
669
670/*!
671 * \brief Photo::editFile
672 * \param state
673 */
674void Photo::editFile(const PhotoEditState& state)
675{
676 // As a special case, if editing to the original version, we simply restore
677 // from the original and call it a day.
678 if (state.isOriginal()) {
679 if (!m_caches.restoreOriginal())
680 qDebug("Error restoring original for %s", qPrintable(file().filePath()));
681 else
682 setSize(originalSize(PhotoEditState::ORIGINAL_ORIENTATION));
683
684 // As a courtesy, when the original goes away, we get rid of the other
685 // cached files too.
686 m_caches.discardCachedEnhanced();
687 return;
688 }
689
690 if (!m_caches.cacheOriginal())
691 qDebug("Error caching original for %s", qPrintable(file().filePath()));
692
693 if (state.is_enhanced_ && !m_caches.hasCachedEnhanced())
694 createCachedEnhanced();
695
696 if (!m_caches.overwriteFromCache(state.is_enhanced_))
697 qDebug("Error overwriting %s from cache", qPrintable(file().filePath()));
698
699 // Have we been rotated and _not_ cropped?
700 if (fileFormatHasOrientation() && (!state.crop_rectangle_.isValid()) &&
701 state.exposureCompensation_ == 0 &&
702 (state.orientation_ != PhotoEditState::ORIGINAL_ORIENTATION)) {
703 // Yes; skip out on decoding and re-encoding the image.
704 handleSimpleMetadataRotation(state);
705 return;
706 }
707
708 // TODO: we might be able to avoid reading/writing pixel data (and other
709 // more general optimizations) under certain conditions here. Might be worth
710 // doing if it doesn't make the code too much worse.
711 //
712 // At the moment, we are skipping at least one decode and one encode in cases
713 // where a .jpeg file has been rotated, but not cropped, since rotation can be
714 // controlled by manipulating its metadata without having to modify pixel data;
715 // please see the method handle_simple_metadata_rotation() for details.
716
717 QImage image(file().filePath(), m_fileFormat.toStdString().c_str());
718 if (image.isNull()) {
719 qDebug("Error loading %s for editing", qPrintable(file().filePath()));
720 return;
721 }
722 PhotoMetadata* metadata = PhotoMetadata::fromFile(file());
723
724 if (fileFormatHasOrientation() &&
725 state.orientation_ != PhotoEditState::ORIGINAL_ORIENTATION)
726 metadata->setOrientation(state.orientation_);
727
728 if (fileFormatHasOrientation() &&
729 metadata->orientation() != TOP_LEFT_ORIGIN)
730 image = image.transformed(metadata->orientationTransform());
731 else if (state.orientation_ != PhotoEditState::ORIGINAL_ORIENTATION &&
732 state.orientation_ != TOP_LEFT_ORIGIN)
733 image = image.transformed(
734 OrientationCorrection::fromOrientation(state.orientation_).toTransform());
735
736 // Cache this here so we may be able to avoid another JPEG decode later just
737 // to find the dimensions.
738 if (!m_originalSize.isValid())
739 m_originalSize = image.size();
740
741 if (state.crop_rectangle_.isValid())
742 image = image.copy(state.crop_rectangle_);
743
744 // exposure compensation
745 if (state.exposureCompensation_ != 0.0) {
746 image = compensateExposure(image, state.exposureCompensation_);
747 }
748
749 // exposure compensation
750 if (!state.colorBalance_.isNull()) {
751 const QVector4D &v = state.colorBalance_;
752 image = doColorBalance(image, v.x(), v.y(), v.z(), v.w());
753 }
754
755 QSize new_size = image.size();
756
757 // We need to apply the reverse transformation so that when we reload the
758 // file and reapply the transformation it comes out correctly.
759 if (fileFormatHasOrientation() &&
760 metadata->orientation() != TOP_LEFT_ORIGIN)
761 image = image.transformed(metadata->orientationTransform().inverted());
762
763 bool saved = image.save(file().filePath(), m_fileFormat.toStdString().c_str(), 90);
764 if (saved && fileFormatHasMetadata())
765 saved = metadata->save();
766 if (!saved)
767 qDebug("Error saving edited %s", qPrintable(file().filePath()));
768
769 delete metadata;
770
771 setSize(new_size);
772}
773
774/*!
775 * \brief Photo::createCachedEnhanced
776 */
777void Photo::createCachedEnhanced()
778{
779 if (!m_caches.cacheEnhancedFromOriginal()) {
780 qDebug("Error creating enhanced file for %s", qPrintable(file().filePath()));
781 return;
782 }
783
784 QFileInfo to_enhance = m_caches.enhancedFile();
785 PhotoMetadata* metadata = PhotoMetadata::fromFile(to_enhance);
786
787 QImage unenhanced_img(to_enhance.filePath(), m_fileFormat.toStdString().c_str());
788 int width = unenhanced_img.width();
789 int height = unenhanced_img.height();
790
791 QImage sample_img = (unenhanced_img.width() > 400) ?
792 unenhanced_img.scaledToWidth(400) : unenhanced_img;
793
794 AutoEnhanceTransformation enhance_txn = AutoEnhanceTransformation(sample_img);
795
796 QImage::Format dest_format = unenhanced_img.format();
797
798 // Can't write into indexed images, due to a limitation in Qt.
799 if (dest_format == QImage::Format_Indexed8)
800 dest_format = QImage::Format_RGB32;
801
802 QImage enhanced_image(width, height, dest_format);
803
804 for (int j = 0; j < height; j++) {
805 //QApplication::processEvents();
806 for (int i = 0; i < width; i++) {
807 QColor px = enhance_txn.transformPixel(
808 QColor(unenhanced_img.pixel(i, j)));
809 enhanced_image.setPixel(i, j, px.rgb());
810 }
811 }
812
813 bool saved = enhanced_image.save(to_enhance.filePath(),
814 m_fileFormat.toStdString().c_str(), 99);
815 if (saved && fileFormatHasMetadata())
816 saved = metadata->save();
817 if (!saved) {
818 qDebug("Error saving enhanced file for %s", qPrintable(file().filePath()));
819 m_caches.discardCachedEnhanced();
820 }
821
822 delete metadata;
823}
824
825/*!
826 * \brief Photo::compensateExposure Compensates the exposure
827 * Compensating the exposure is a change in brightnes
828 * \param image Image to change the brightnes
829 * \param compansation -1.0 is total dark, +1.0 is total bright
830 * \return The image with adjusted brightnes
831 */
832QImage Photo::compensateExposure(const QImage &image, qreal compansation)
833{
834 int shift = qBound(-255, (int)(255*compansation), 255);
835 QImage result(image.width(), image.height(), image.format());
836
837 for (int j = 0; j < image.height(); j++) {
838 QApplication::processEvents();
839 for (int i = 0; i <image.width(); i++) {
840 QColor px = image.pixel(i, j);
841 int red = qBound(0, px.red() + shift, 255);
842 int green = qBound(0, px.green() + shift, 255);
843 int blue = qBound(0, px.blue() + shift, 255);
844 result.setPixel(i, j, qRgb(red, green, blue));
845 }
846 }
847
848 return result;
849}
850
851/*!
852 * \brief Photo::colorBalance
853 * \param image
854 * \param brightness 0 is total dark, 1 is as the original, grater than 1 is brigther
855 * \param contrast from 0 maybe 5. 1 is as the original
856 * \param saturation from 0 maybe 5. 1 is as the original
857 * \param hue from 0 to 360. 0 and 360 is as the original
858 * \return
859 */
860QImage Photo::doColorBalance(const QImage &image, qreal brightness, qreal contrast, qreal saturation, qreal hue)
861{
862 QImage result(image.width(), image.height(), image.format());
863
864 ColorBalance cb(brightness, contrast, saturation, hue);
865
866 for (int j = 0; j < image.height(); j++) {
867 QApplication::processEvents();
868 for (int i = 0; i <image.width(); i++) {
869 QColor px = image.pixel(i, j);
870 QColor tpx = cb.transformPixel(px);
871 result.setPixel(i, j, tpx.rgb());
872 }
873 }
874
875 return result;
876}677}
877678
878/*!679/*!
@@ -901,6 +702,15 @@
901}702}
902703
903/*!704/*!
705 * \brief Photo::fileFormat returns the file format as QString
706 * \return
707 */
708const QString &Photo::fileFormat() const
709{
710 return m_fileFormat;
711}
712
713/*!
904 * \brief Photo::fileFormatHasMetadata714 * \brief Photo::fileFormatHasMetadata
905 * \return715 * \return
906 */716 */
@@ -927,3 +737,22 @@
927{737{
928 m_originalOrientation = orientation;738 m_originalOrientation = orientation;
929}739}
740
741/*!
742 * \brief Photo::originalOrientation returns the original orientation
743 * \return
744 */
745Orientation Photo::originalOrientation() const
746{
747 return m_originalOrientation;
748}
749
750/*!
751 * \brief Photo::originalSize
752 * \return
753 */
754const QSize &Photo::originalSize()
755{
756 originalSize(PhotoEditState::ORIGINAL_ORIENTATION);
757 return m_originalSize;
758}
930759
=== modified file 'src/photo/photo.h'
--- src/photo/photo.h 2013-06-17 12:57:21 +0000
+++ src/photo/photo.h 2013-07-26 16:08:26 +0000
@@ -31,6 +31,7 @@
31#include "orientation.h"31#include "orientation.h"
3232
33class PhotoEditState;33class PhotoEditState;
34class PhotoEditThread;
34class PhotoPrivate;35class PhotoPrivate;
3536
36/*!37/*!
@@ -81,6 +82,12 @@
81 Q_INVOKABLE void crop(QVariant vrect);82 Q_INVOKABLE void crop(QVariant vrect);
8283
83 void setOriginalOrientation(Orientation orientation);84 void setOriginalOrientation(Orientation orientation);
85 Orientation originalOrientation() const;
86 const QSize &originalSize();
87
88 const QString &fileFormat() const;
89 bool fileFormatHasMetadata() const;
90 bool fileFormatHasOrientation() const;
8491
85signals:92signals:
86 void editStackChanged();93 void editStackChanged();
@@ -88,22 +95,21 @@
88protected:95protected:
89 virtual void destroySource(bool destroyBacking, bool asOrphan);96 virtual void destroySource(bool destroyBacking, bool asOrphan);
9097
98private Q_SLOTS:
99 void resetToOriginalSize();
100 void finishEditing();
101
91private:102private:
92 const PhotoEditState& currentState() const;103 const PhotoEditState& currentState() const;
93 QSize originalSize(Orientation orientation);104 QSize originalSize(Orientation orientation);
94 void makeUndoableEdit(const PhotoEditState& state);105 void makeUndoableEdit(const PhotoEditState& state);
95 void save(const PhotoEditState& state, Orientation oldOrientation);106 void asyncEdit(const PhotoEditState& state);
96 void editFile(const PhotoEditState& state);107 void editFile(const PhotoEditState& state);
97 void createCachedEnhanced();
98 QImage compensateExposure(const QImage& image, qreal compansation);
99 QImage doColorBalance(const QImage& image, qreal brightness, qreal contrast, qreal saturation, qreal hue);
100 void appendPathParams(QUrl* url, Orientation orientation, const int sizeLevel) const;108 void appendPathParams(QUrl* url, Orientation orientation, const int sizeLevel) const;
101 void handleSimpleMetadataRotation(const PhotoEditState& state);
102 bool fileFormatHasMetadata() const;
103 bool fileFormatHasOrientation() const;
104109
105 QString m_fileFormat;110 QString m_fileFormat;
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.
112 PhotoEditThread *m_editThread;
107 PhotoCaches m_caches;113 PhotoCaches m_caches;
108114
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.
110116
=== modified file 'tests/unittests/stubs/photo_stub.cpp'
--- tests/unittests/stubs/photo_stub.cpp 2013-06-17 12:57:21 +0000
+++ tests/unittests/stubs/photo_stub.cpp 2013-07-26 16:08:26 +0000
@@ -159,3 +159,12 @@
159{159{
160 m_originalOrientation = orientation;160 m_originalOrientation = orientation;
161}161}
162
163void Photo::resetToOriginalSize()
164{
165}
166
167void Photo::finishEditing()
168{
169}
170

Subscribers

People subscribed via source and target branches