Merge lp:~fboucault/ubuntu-ui-extras/editor_with_halide into lp:ubuntu-ui-extras

Proposed by Florian Boucault
Status: Needs review
Proposed branch: lp:~fboucault/ubuntu-ui-extras/editor_with_halide
Merge into: lp:ubuntu-ui-extras
Prerequisite: lp:~fboucault/ubuntu-ui-extras/fix_exif_orientation
Diff against target: 3538 lines (+962/-2077)
29 files modified
debian/control (+1/-0)
modules/Ubuntu/Components/Extras/PhotoEditor.qml (+301/-122)
modules/Ubuntu/Components/Extras/PhotoEditor/ActionsBar.qml (+6/-0)
modules/Ubuntu/Components/Extras/PhotoEditor/BusyIndicator.qml (+10/-7)
modules/Ubuntu/Components/Extras/PhotoEditor/CropInteractor.qml (+21/-32)
modules/Ubuntu/Components/Extras/PhotoEditor/EditStack.qml (+0/-134)
modules/Ubuntu/Components/Extras/PhotoEditor/EditorAction.qml (+48/-0)
modules/Ubuntu/Components/Extras/PhotoEditor/ExifOrientation.qml (+32/-0)
modules/Ubuntu/Components/Extras/PhotoEditor/FunctionStateSaver.qml (+56/-0)
modules/Ubuntu/Components/Extras/PhotoEditor/FunctionTransform.qml (+77/-0)
modules/Ubuntu/Components/Extras/plugin/CMakeLists.txt (+8/-4)
modules/Ubuntu/Components/Extras/plugin/components.cpp (+4/-4)
modules/Ubuntu/Components/Extras/plugin/photoeditor/file-utils.cpp (+4/-0)
modules/Ubuntu/Components/Extras/plugin/photoeditor/file-utils.h (+4/-0)
modules/Ubuntu/Components/Extras/plugin/photoeditor/generator_transform.cpp (+65/-0)
modules/Ubuntu/Components/Extras/plugin/photoeditor/halide_common.h (+91/-0)
modules/Ubuntu/Components/Extras/plugin/photoeditor/halide_generators_common.h (+63/-0)
modules/Ubuntu/Components/Extras/plugin/photoeditor/imaging.cpp (+0/-363)
modules/Ubuntu/Components/Extras/plugin/photoeditor/imaging.h (+0/-169)
modules/Ubuntu/Components/Extras/plugin/photoeditor/orientation.cpp (+0/-152)
modules/Ubuntu/Components/Extras/plugin/photoeditor/orientation.h (+0/-64)
modules/Ubuntu/Components/Extras/plugin/photoeditor/photo-data.cpp (+0/-275)
modules/Ubuntu/Components/Extras/plugin/photoeditor/photo-data.h (+0/-92)
modules/Ubuntu/Components/Extras/plugin/photoeditor/photo-edit-thread.cpp (+0/-185)
modules/Ubuntu/Components/Extras/plugin/photoeditor/photo-edit-thread.h (+0/-56)
modules/Ubuntu/Components/Extras/plugin/photoeditor/photo-metadata.cpp (+72/-235)
modules/Ubuntu/Components/Extras/plugin/photoeditor/photo-metadata.h (+51/-38)
tests/unittests/sampledata.qrc (+1/-1)
tests/unittests/tst_PhotoEditorPhoto.cpp (+47/-144)
To merge this branch: bzr merge lp:~fboucault/ubuntu-ui-extras/editor_with_halide
Reviewer Review Type Date Requested Status
system-apps-ci-bot continuous-integration Needs Fixing
PS Jenkins bot continuous-integration Needs Fixing
Ugo Riboni Pending
Review via email: mp+261622@code.launchpad.net

Commit message

PhotoEditor: use Halide instead of custom editing functions.

To post a comment you must log in.
76. By Florian Boucault

Fixed deb dependencies.

Revision history for this message
Florian Boucault (fboucault) wrote :
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
77. By Florian Boucault

Fix for long loading when editing starts.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
78. By Florian Boucault

Enclosed classes into PhotoEditor namespace as to not conflict with Gallery's version of the same classes.

79. By Florian Boucault

Enable function as late as possible.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :
review: Needs Fixing (continuous-integration)

Unmerged revisions

79. By Florian Boucault

Enable function as late as possible.

78. By Florian Boucault

Enclosed classes into PhotoEditor namespace as to not conflict with Gallery's version of the same classes.

77. By Florian Boucault

Fix for long loading when editing starts.

76. By Florian Boucault

Fixed deb dependencies.

75. By Florian Boucault

PhotoEditor: use Halide instead of custom editing functions.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/control'
2--- debian/control 2015-02-12 13:20:41 +0000
3+++ debian/control 2015-06-12 13:08:47 +0000
4@@ -31,6 +31,7 @@
5 qtdeclarative5-qtquick2-plugin,
6 qtdeclarative5-ubuntu-ui-toolkit-plugin,
7 qtdeclarative5-window-plugin,
8+ qtdeclarative5-halide-plugin0.1,
9 libexiv2-13,
10 Provides: qtdeclarative5-ubuntu-ui-extras0.1
11 Conflicts: qtdeclarative5-ubuntu-ui-extras0.1
12
13=== modified file 'modules/Ubuntu/Components/Extras/PhotoEditor.qml'
14--- modules/Ubuntu/Components/Extras/PhotoEditor.qml 2015-03-02 20:25:09 +0000
15+++ modules/Ubuntu/Components/Extras/PhotoEditor.qml 2015-06-12 13:08:47 +0000
16@@ -1,5 +1,5 @@
17 /*
18- * Copyright (C) 2014 Canonical Ltd
19+ * Copyright (C) 2015 Canonical Ltd
20 *
21 * This program is free software: you can redistribute it and/or modify
22 * it under the terms of the GNU General Public License version 3 as
23@@ -18,94 +18,301 @@
24 import Ubuntu.Components 1.1
25 import Ubuntu.Components.Popups 1.0
26 import Ubuntu.Components.Extras 0.2
27+import QtHalide 0.1
28 import "PhotoEditor"
29
30 Item {
31 id: editor
32- property string photo
33- property bool modified: stack.modified
34+
35+ // public API
36
37 signal closed(bool photoWasModified)
38
39- property list<Action> actions
40- actions: [stack.undoAction, stack.redoAction]
41-
42- EditStack {
43- id: stack
44- data: photoData
45- actionsEnabled: !exposureSelector.visible && !cropper.visible && !photoData.busy
46- onRevertRequested: PopupUtils.open(revertPromptComponent)
47- }
48-
49- property list<Action> toolActions: [
50- Action {
51- objectName: "cropButton"
52- text: i18n.tr("Crop")
53- iconSource: Qt.resolvedUrl("PhotoEditor/assets/edit_crop.png")
54- onTriggered: {
55- photoData.isLongOperation = false;
56- cropper.start("image://photo/" + photoData.path);
57- }
58- },
59- Action {
60- objectName: "rotateButton"
61- text: i18n.tr("Rotate")
62- iconSource: Qt.resolvedUrl("PhotoEditor/assets/edit_rotate_left.png")
63- onTriggered: {
64- photoData.isLongOperation = false;
65- photoData.rotateRight()
66- }
67+ property list<Action> actions: [
68+ Action {
69+ text: i18n.tr("Revert to Original")
70+ iconName: "reset"
71+ enabled: internal.actionsEnabled && (internal.hasOriginal || internal.undoStates.length != 0)
72+ onTriggered: PopupUtils.open(revertPromptComponent)
73+ },
74+ Action {
75+ text: i18n.tr("Undo")
76+ iconName: "undo"
77+ enabled: internal.actionsEnabled && internal.undoStates.length != 0
78+ onTriggered: internal.undo()
79+ },
80+ Action {
81+ text: i18n.tr("Redo")
82+ iconName: "redo"
83+ enabled: internal.actionsEnabled && internal.redoStates.length != 0
84+ onTriggered: internal.redo()
85 }
86 ]
87
88- function close(saveIfModified) {
89- stack.endEditingSession(saveIfModified);
90- editor.closed(editor.modified);
91- }
92-
93- function open(photo) {
94- editor.photo = photo;
95- stack.startEditingSession(photo);
96- photoData.path = stack.currentFile;
97- image.source = "image://photo/" + photoData.path;
98- }
99-
100+ function open(photoFile) {
101+ internal.currentFile = photoFile;
102+ internal.currentFileModified = false;
103+ internal.clearAllStates();
104+ }
105+
106+ function close() {
107+ if (internal.undoStates.length != 0) {
108+ internal.saveToDisk(function() {editor.closed(internal.currentFileModified)});
109+ } else {
110+ editor.closed(internal.currentFileModified);
111+ }
112+ internal.clearAllStates();
113+ }
114+
115+ // private
116+
117+ QtObject {
118+ id: internal
119+
120+ property string currentFile
121+ property string originalFile: FileUtils.parentDirectory(internal.currentFile)
122+ + "/.original/" + FileUtils.nameFromPath(internal.currentFile)
123+ property bool hasOriginal: FileUtils.exists(internal.originalFile)
124+ property bool currentFileModified: false
125+ property bool actionsEnabled: !busyIndicator.running && !cropper.active
126+ property var undoStates: []
127+ property var redoStates: []
128+
129+ // pop/push helper functions necessary to make sure that undoStates.length
130+ // and redoStates.length emit a modification signal
131+ function popUndoState() {
132+ var state = internal.undoStates.pop();
133+ internal.undoStates = internal.undoStates;
134+ return state;
135+ }
136+
137+ function popRedoState() {
138+ var state = internal.redoStates.pop();
139+ internal.redoStates = internal.redoStates;
140+ return state;
141+ }
142+
143+ function pushUndoState(state) {
144+ internal.undoStates.push(state);
145+ internal.undoStates = internal.undoStates;
146+ }
147+
148+ function pushRedoState(state) {
149+ internal.redoStates.push(state);
150+ internal.redoStates = internal.redoStates;
151+ }
152+
153+ function clearUndoStates() {
154+ for (var i=0; i<internal.undoStates.length; i++) {
155+ internal.undoStates[i].destroy();
156+ }
157+ internal.undoStates = [];
158+ }
159+
160+ function clearRedoStates() {
161+ for (var i=0; i<internal.redoStates.length; i++) {
162+ internal.redoStates[i].destroy();
163+ }
164+ internal.redoStates = [];
165+ }
166+
167+ function clearAllStates() {
168+ internal.clearUndoStates();
169+ internal.clearRedoStates();
170+ }
171+
172+ property list<Action> toolActions: [
173+ EditorAction {
174+ objectName: "cropButton"
175+ text: i18n.tr("Crop")
176+ iconSource: Qt.resolvedUrl("PhotoEditor/assets/edit_crop.png")
177+ halideFunction: functionTransform
178+ halideTransform: previewTransform
179+ manualCommit: true
180+ function execute() {
181+ cropper.start(previewTransform.output, commit);
182+ }
183+ },
184+ EditorAction {
185+ objectName: "rotateButton"
186+ text: i18n.tr("Rotate")
187+ iconSource: Qt.resolvedUrl("PhotoEditor/assets/edit_rotate_right.png")
188+ halideFunction: functionTransform
189+ halideTransform: previewTransform
190+ function execute() {
191+ functionTransform.rotateRight90();
192+ exifOrientation.rotateRight90();
193+ }
194+ }
195+ ]
196+
197+ function undo() {
198+ var undoState = internal.popUndoState();
199+ if (undoState) {
200+ previewTransform.live = false;
201+ var redoState = internal.snapshotFunctionState(undoState.target);
202+ internal.pushRedoState(redoState);
203+ undoState.restore();
204+ undoState.destroy();
205+ previewTransform.live = true;
206+ }
207+ }
208+
209+ function redo() {
210+ var redoState = internal.popRedoState();
211+ if (redoState) {
212+ previewTransform.live = false;
213+ var undoState = internal.snapshotFunctionState(redoState.target);
214+ internal.pushUndoState(undoState);
215+ redoState.restore();
216+ redoState.destroy();
217+ previewTransform.live = true;
218+ }
219+ }
220+
221+ function revertToOriginal() {
222+ previewTransform.live = false;
223+ for (var i=internal.undoStates.length-1; i>=0; i--) {
224+ internal.undoStates[i].restore();
225+ }
226+ internal.clearAllStates();
227+
228+ if (internal.hasOriginal) {
229+ FileUtils.copy(internal.originalFile, internal.currentFile);
230+ FileUtils.remove(internal.originalFile);
231+ internal.hasOriginal = false;
232+ previewImage.reload();
233+ internal.currentFileModified = true;
234+ }
235+ previewTransform.live = true;
236+ }
237+
238+ function snapshotFunctionState(targetFunction) {
239+ var functionStateComponent = Qt.createComponent("PhotoEditor/FunctionStateSaver.qml");
240+ var state = functionStateComponent.createObject(null, {"target": targetFunction});
241+ state.save();
242+ return state;
243+ }
244+
245+ property var saveToDiskCallback
246+ function saveToDisk(callback) {
247+ internal.saveToDiskCallback = callback;
248+ saveImage.source = internal.currentFile;
249+ }
250+ }
251+
252+ // preview pipeline
253 Rectangle {
254 color: "black"
255 anchors.fill: parent
256 }
257
258- Image {
259- id: image
260+ MouseArea {
261 anchors.fill: parent
262- asynchronous: true
263- cache: false
264- source: photoData.path ? "image://photo/" + photoData.path : ""
265- fillMode: Image.PreserveAspectFit
266- sourceSize {
267- width: image.width
268- height: image.height
269- }
270+ }
271+
272+ HalideImage {
273+ id: previewImage
274+ sourceSize: Qt.size(Math.max(editor.width, editor.height), 0)
275+ source: internal.currentFile
276
277 function reload() {
278- image.asynchronous = false;
279- image.source = "";
280- image.asynchronous = true;
281- image.source = "image://photo/" + photoData.path;
282- }
283- }
284-
285- PhotoData {
286- id: photoData
287- onDataChanged: image.reload()
288- property bool isLongOperation: false
289-
290- onEditFinished: {
291- console.log("Edit finished")
292- // If we are editing exposure we don't need to checkpoint at every
293- // edit, and the exposure UI will checkpoint when the user confirms.
294- if (exposureSelector.opacity > 0) exposureSelector.reload()
295- else stack.checkpoint()
296+ previewImage.source = "";
297+ previewImage.source = internal.currentFile;
298+ }
299+ }
300+
301+ HalideTransform {
302+ id: previewTransform
303+ input: previewImage
304+ live: true
305+
306+ FunctionTransform {
307+ id: functionTransform
308+ enabled: false
309+ }
310+ }
311+
312+ HalideImageRenderer {
313+ anchors.fill: parent
314+ image: previewTransform.output
315+ fillMode: HalideImageRenderer.PreserveAspectFit
316+ }
317+
318+ ExifOrientation {
319+ id: exifOrientation
320+ }
321+
322+ // saving pipeline
323+ HalideImage {
324+ id: saveImage
325+ onStatusChanged: {
326+ if (status == HalideImage.Ready) {
327+ saveTransform.save();
328+ source = "";
329+ }
330+ }
331+ }
332+
333+ HalideTransform {
334+ id: saveTransform
335+ input: saveImage
336+ live: false
337+
338+ FunctionTransform {
339+ id: functionTransformSave
340+ }
341+
342+ function copyProperties(objectSource, objectDestination) {
343+ for (var prop in objectSource) {
344+ if (typeof(objectSource[prop]) !== "function") {
345+ objectDestination[prop] = objectSource[prop];
346+ }
347+ }
348+ }
349+
350+ function save() {
351+ for (var i=0; i<saveTransform.functions.count; i++) {
352+ var previewFunction = previewTransform.functions.get(i);
353+ var saveFunction = saveTransform.functions.get(i);
354+ copyProperties(previewFunction, saveFunction);
355+ }
356+
357+ if (!internal.hasOriginal) {
358+ FileUtils.createDirectory(FileUtils.parentDirectory(internal.originalFile));
359+ FileUtils.copy(internal.currentFile, internal.originalFile);
360+ internal.hasOriginal = true;
361+ }
362+ saveTransform.scheduleUpdate(true);
363+ }
364+
365+ onUpdatingChanged: {
366+ if (!saveTransform.updating) {
367+ // update is finished
368+ photoMetadata.fileName = internal.currentFile;
369+
370+ if (functionTransform.rotationOnly) {
371+ // special case when only rotating: we implement the rotation by
372+ // modifying the EXIF rotation tag
373+ photoMetadata.orientation = exifOrientation.add(photoMetadata.orientation,
374+ exifOrientation.orientation);
375+ photoMetadata.save();
376+ } else {
377+ // the actual pixels are being rotated: reset EXIF orientation tag
378+ photoMetadata.orientation = 1;
379+ photoMetadata.saveImage(saveTransform.output.image);
380+ }
381+ }
382+ }
383+ }
384+
385+ PhotoMetadata {
386+ id: photoMetadata
387+ onSavingChanged: {
388+ if (!saving) {
389+ internal.currentFileModified = true;
390+ internal.saveToDiskCallback();
391+ }
392 }
393 }
394
395@@ -114,6 +321,8 @@
396
397 anchors.fill: parent
398
399+ source: "PhotoEditor/CropInteractor.qml"
400+ active: false
401 opacity: 0.0
402 visible: opacity > 0
403 Behavior on opacity { UbuntuNumberAnimation { } }
404@@ -122,72 +331,50 @@
405 target: cropper.item
406 ignoreUnknownSignals: true
407 onCropped: {
408- var qtRect = Qt.rect(rect.x, rect.y, rect.width, rect.height);
409- photoData.crop(qtRect);
410+ functionTransform.crop(rect.x, rect.y, rect.width, rect.height);
411 cropper.opacity = 0.0;
412- cropper.source = ""
413+ cropper.active = false;
414+ cropper.callback();
415 }
416 onCanceled: {
417 cropper.opacity = 0.0;
418- cropper.source = ""
419+ cropper.active = false;
420 }
421 }
422
423- function start(target) {
424- source = "PhotoEditor/CropInteractor.qml";
425+ property var callback
426+
427+ function start(target, callback) {
428+ cropper.callback = callback;
429+ cropper.active = true;
430 item.targetPhoto = target;
431 }
432
433 onLoaded: opacity = 1.0
434 }
435
436- ExposureAdjuster {
437- id: exposureSelector
438- anchors.fill: parent
439- opacity: 0.0
440- enabled: !photoData.busy
441- onExposureChanged: {
442- // Restore the starting version of the image, otherwise we will
443- // accumulate compensations over the previous ones.
444- stack.restoreSnapshot(stack.level)
445- photoData.exposureCompensation(exposure)
446- }
447- onConfirm: {
448- stack.checkpoint();
449- exposureSelector.opacity = 0.0
450- }
451- onCancel: {
452- stack.restoreSnapshot(stack.level)
453- exposureSelector.opacity = 0.0
454- }
455- visible: opacity > 0
456- }
457-
458 ActionsBar {
459 id: actionsBar
460 objectName: "editorActionsBar"
461 anchors.bottom: parent.bottom
462 anchors.left: parent.left
463 anchors.right: parent.right
464+ toolActions: internal.toolActions
465+ enabled: visible
466
467+ opacity: cropper.opacity == 0 ? 1.0 : 0.0
468 visible: opacity > 0.0
469- opacity: (exposureSelector.opacity == 0 && cropper.opacity == 0) ? 1.0 : 0.0
470-
471- enabled: !photoData.busy
472- toolActions: {
473- // This is necessary because QML does not let us declare a list with
474- // mixed component declarations and identifiers, like this:
475- // property list<Action> foo: { Action{}, someOtherAction }
476- var list = [];
477- for (var i = 0; i < editor.toolActions.length; i++)
478- list.push(editor.toolActions[i]);
479- list.push(stack.revertAction);
480- return list;
481- }
482-
483 Behavior on opacity { UbuntuNumberAnimation {} }
484 }
485
486+ BusyIndicator {
487+ id: busyIndicator
488+ anchors.centerIn: parent
489+ text: i18n.tr("Saving modifications...")
490+ running: previewTransform.updating || saveTransform.updating || photoMetadata.saving
491+ longOperation: saveTransform.updating || photoMetadata.saving
492+ }
493+
494 Component {
495 id: revertPromptComponent
496 Dialog {
497@@ -213,18 +400,10 @@
498 color: UbuntuColors.green
499 onClicked: {
500 PopupUtils.close(revertPrompt)
501- stack.revertToPristine()
502+ internal.revertToOriginal();
503 }
504 }
505 }
506 }
507 }
508-
509- BusyIndicator {
510- id: busyIndicator
511- anchors.centerIn: parent
512- text: i18n.tr("Enhancing photo...")
513- running: photoData.busy
514- longOperation: photoData.isLongOperation
515- }
516 }
517
518=== modified file 'modules/Ubuntu/Components/Extras/PhotoEditor/ActionsBar.qml'
519--- modules/Ubuntu/Components/Extras/PhotoEditor/ActionsBar.qml 2015-03-10 21:31:16 +0000
520+++ modules/Ubuntu/Components/Extras/PhotoEditor/ActionsBar.qml 2015-06-12 13:08:47 +0000
521@@ -50,6 +50,11 @@
522 action: modelData
523 enabled: bar.enabled
524
525+ Rectangle {
526+ anchors.fill: parent
527+ color: pressed ? Qt.rgba(1.0, 1.0, 1.0, 0.3) : Qt.rgba(0.0, 0.0, 0.0, 0.0)
528+ }
529+
530 Icon {
531 anchors.centerIn: parent
532 name: modelData.iconName
533@@ -57,6 +62,7 @@
534 width: units.gu(3)
535 height: units.gu(3)
536 opacity: modelData.enabled && parent.enabled ? 1.0 : 0.5
537+ color: "white"
538 }
539 }
540 }
541
542=== modified file 'modules/Ubuntu/Components/Extras/PhotoEditor/BusyIndicator.qml'
543--- modules/Ubuntu/Components/Extras/PhotoEditor/BusyIndicator.qml 2015-03-11 00:24:51 +0000
544+++ modules/Ubuntu/Components/Extras/PhotoEditor/BusyIndicator.qml 2015-06-12 13:08:47 +0000
545@@ -14,23 +14,25 @@
546 * along with this program. If not, see <http://www.gnu.org/licenses/>.
547 */
548
549-import QtQuick 2.0
550-import Ubuntu.Components 0.1
551+import QtQuick 2.3
552+import Ubuntu.Components 1.0
553
554 Item {
555 id: busy
556- width: childrenRect.width
557- height: childrenRect.height
558+ width: busyColumn.width
559+ height: busyColumn.height
560 property alias text: label.text
561 property alias running: spinner.running
562 property bool longOperation: false
563
564- visible: running
565+ visible: opacity != 0.0
566+ opacity: running ? 1.0 : 0.0
567+ Behavior on opacity { UbuntuNumberAnimation {} }
568
569 UbuntuShape {
570 id: busyUbuntuShape
571 objectName: "busyUbuntuShape"
572- color: "white"
573+ color: "black"
574 anchors.centerIn: parent
575 width: parent.width + units.gu(4)
576 height: parent.height + units.gu(4)
577@@ -41,7 +43,7 @@
578 id: busyColumn
579 objectName: "busyColumn"
580 anchors.centerIn: parent
581- width: childrenRect.width
582+ width: label.width
583 spacing: units.gu(2)
584
585 ActivityIndicator {
586@@ -55,6 +57,7 @@
587 anchors.horizontalCenter: parent.horizontalCenter
588 horizontalAlignment: Text.AlignHCenter
589 visible: longOperation
590+ color: "white"
591 }
592 }
593 }
594
595=== modified file 'modules/Ubuntu/Components/Extras/PhotoEditor/CropInteractor.qml'
596--- modules/Ubuntu/Components/Extras/PhotoEditor/CropInteractor.qml 2015-01-15 10:48:51 +0000
597+++ modules/Ubuntu/Components/Extras/PhotoEditor/CropInteractor.qml 2015-06-12 13:08:47 +0000
598@@ -20,6 +20,7 @@
599
600 import QtQuick 2.3
601 import Ubuntu.Components 1.1
602+import QtHalide 0.1
603 import "GraphicsRoutines.js" as GraphicsRoutines
604
605 /*!
606@@ -30,7 +31,7 @@
607
608 color: "black"
609
610- property alias targetPhoto: original.source
611+ property alias targetPhoto: original.image
612
613 property string matteColor: "black"
614 property real matteOpacity: 0.6
615@@ -41,12 +42,7 @@
616 signal canceled()
617
618 function computeRectSet() {
619- var actualImage = Qt.rect(
620- (original.width - original.paintedWidth) / 2.0,
621- (original.height - original.paintedHeight) / 2.0,
622- original.paintedWidth,
623- original.paintedHeight
624- );
625+ var actualImage = Qt.rect(0, 0, original.width, original.height);
626 var photoPreview = GraphicsRoutines.fitRect(viewport, actualImage);
627
628 var unfitCrop = Qt.rect(0, 0, photoPreview.width, photoPreview.height);
629@@ -102,7 +98,7 @@
630 }
631 }
632
633- Image {
634+ HalideImageRenderer {
635 id: original
636
637 x: viewport.x
638@@ -111,30 +107,23 @@
639 height: viewport.height
640 transformOrigin: Item.TopLeft
641 fillMode: Image.PreserveAspectFit
642- cache: false
643- sourceSize {
644- width: original.width
645- height: original.height
646- }
647-
648- onStatusChanged: {
649- if (status == Image.Ready) {
650- var rects = computeRectSet();
651-
652- overlay.initialFrameX = rects.cropFrame.x;
653- overlay.initialFrameY = rects.cropFrame.y;
654- overlay.initialFrameWidth = rects.cropFrame.width;
655- overlay.initialFrameHeight = rects.cropFrame.height;
656-
657- overlay.resetFor(rects);
658- overlay.visible = true;
659-
660- x = rects.photoExtent.x;
661- y = rects.photoExtent.y;
662- width = rects.photoPreview.width;
663- height = rects.photoPreview.height;
664- scale = rects.photoExtentScale;
665- }
666+
667+ onImageChanged: {
668+ var rects = computeRectSet();
669+
670+ overlay.initialFrameX = rects.cropFrame.x;
671+ overlay.initialFrameY = rects.cropFrame.y;
672+ overlay.initialFrameWidth = rects.cropFrame.width;
673+ overlay.initialFrameHeight = rects.cropFrame.height;
674+
675+ overlay.resetFor(rects);
676+ overlay.visible = true;
677+
678+ x = rects.photoExtent.x;
679+ y = rects.photoExtent.y;
680+ width = rects.photoPreview.width;
681+ height = rects.photoPreview.height;
682+ scale = rects.photoExtentScale;
683 }
684 }
685 }
686
687=== removed file 'modules/Ubuntu/Components/Extras/PhotoEditor/EditStack.qml'
688--- modules/Ubuntu/Components/Extras/PhotoEditor/EditStack.qml 2015-02-11 14:46:34 +0000
689+++ modules/Ubuntu/Components/Extras/PhotoEditor/EditStack.qml 1970-01-01 00:00:00 +0000
690@@ -1,134 +0,0 @@
691-/*
692- * Copyright (C) 2014 Canonical Ltd
693- *
694- * This program is free software: you can redistribute it and/or modify
695- * it under the terms of the GNU General Public License version 3 as
696- * published by the Free Software Foundation.
697- *
698- * This program is distributed in the hope that it will be useful,
699- * but WITHOUT ANY WARRANTY; without even the implied warranty of
700- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
701- * GNU General Public License for more details.
702- *
703- * You should have received a copy of the GNU General Public License
704- * along with this program. If not, see <http://www.gnu.org/licenses/>.
705- */
706-
707-import QtQuick 2.3
708-import Ubuntu.Components 1.1
709-import Ubuntu.Components.Extras 0.2
710-
711-Item {
712- property PhotoData data
713- property bool actionsEnabled: true
714- property var items: []
715- property int level: 0
716- property string editingSessionPath
717- property string currentFile
718- property string originalFile
719- property string pristineFile
720- property bool modified: level > 0 || _revertedInThisSession
721-
722- property bool _revertedInThisSession
723- property bool _pristineFileExists
724-
725- signal revertRequested
726-
727- function startEditingSession(original) {
728- var originalFileName = FileUtils.nameFromPath(original);
729- var baseName = FileUtils.parentDirectory(original) +
730- "/.photo_editing." + originalFileName + ".";
731- editingSessionPath = FileUtils.createTemporaryDirectory(baseName);
732- if (editingSessionPath == "") return false;
733-
734- originalFile = original;
735- currentFile = editingSessionPath + "/current";
736-
737- pristineFile = FileUtils.parentDirectory(original) +
738- "/.original/" + originalFileName
739- _revertedInThisSession = false;
740- _pristineFileExists = FileUtils.exists(pristineFile)
741-
742- FileUtils.copy(originalFile, currentFile)
743-
744- items = [createSnapshot(0)];
745- level = 0;
746- return true;
747- }
748-
749- function endEditingSession(saveIfModified) {
750- if (saveIfModified && modified) { // file modified
751- // if we don't have a copy of the very first original, create one
752- if (!_pristineFileExists) {
753- FileUtils.createDirectory(FileUtils.parentDirectory(pristineFile));
754- FileUtils.copy(originalFile, pristineFile);
755- } else {
756- // if we reverted to original (and made no other changes)
757- // we don't need to keep the pristine copy around
758- if (_revertedInThisSession && level <= 0) {
759- FileUtils.remove(pristineFile);
760- }
761- }
762-
763- FileUtils.copy(currentFile, originalFile); // actually save
764- }
765-
766- FileUtils.removeDirectory(editingSessionPath, true); // clear editing cache
767- editingSessionPath = originalFile = pristineFile = currentFile = "";
768- }
769-
770- function createSnapshot(name) {
771- var snapshotFile = editingSessionPath + "/edit." + name;
772- FileUtils.copy(currentFile, snapshotFile);
773- return snapshotFile;
774- }
775-
776- function restoreSnapshot(name) {
777- var snapshotFile = editingSessionPath + "/edit." + name;
778- FileUtils.copy(snapshotFile, currentFile);
779- data.refreshFromDisk();
780- }
781-
782- function checkpoint() {
783- level++;
784- items = items.slice(0, level);
785- items.push(createSnapshot(items.length));
786- }
787-
788- function revertToPristine() {
789- if (!FileUtils.exists(pristineFile)) {
790- restoreSnapshot(0);
791- items = items.slice(0, 1);
792- level = 0;
793- } else {
794- FileUtils.copy(pristineFile, currentFile);
795- data.refreshFromDisk();
796- items = [];
797- checkpoint();
798- level = 0;
799- _revertedInThisSession = true;
800- }
801- }
802-
803- property Action undoAction: Action {
804- text: i18n.tr("Undo")
805- iconName: "undo"
806- enabled: items.length > 0 && level > 0 && actionsEnabled
807- onTriggered: restoreSnapshot(--level);
808- }
809-
810- property Action redoAction: Action {
811- text: i18n.tr("Redo")
812- iconName: "redo"
813- enabled: level < items.length - 1 && actionsEnabled
814- onTriggered: restoreSnapshot(++level);
815- }
816-
817- property Action revertAction: Action {
818- text: i18n.tr("Revert to Original")
819- iconSource: Qt.resolvedUrl("assets/edit_revert.png")
820- enabled: actionsEnabled &&
821- (level > 0 || (!_revertedInThisSession && _pristineFileExists))
822- onTriggered: revertRequested()
823- }
824-}
825
826=== added file 'modules/Ubuntu/Components/Extras/PhotoEditor/EditorAction.qml'
827--- modules/Ubuntu/Components/Extras/PhotoEditor/EditorAction.qml 1970-01-01 00:00:00 +0000
828+++ modules/Ubuntu/Components/Extras/PhotoEditor/EditorAction.qml 2015-06-12 13:08:47 +0000
829@@ -0,0 +1,48 @@
830+/*
831+ * Copyright (C) 2015 Canonical, Ltd.
832+ *
833+ * This program is free software; you can redistribute it and/or modify
834+ * it under the terms of the GNU Lesser General Public License as published by
835+ * the Free Software Foundation; version 3.
836+ *
837+ * This program is distributed in the hope that it will be useful,
838+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
839+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
840+ * GNU Lesser General Public License for more details.
841+ *
842+ * You should have received a copy of the GNU Lesser General Public License
843+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
844+ */
845+
846+import QtQuick 2.3
847+import Ubuntu.Components 1.1
848+import QtHalide 0.1
849+
850+Action {
851+ id: editorAction
852+
853+ property HalideFunction halideFunction
854+ property HalideTransform halideTransform
855+ property bool manualCommit: false
856+ property var savedState
857+
858+ onTriggered: {
859+ halideTransform.live = false;
860+ savedState = internal.snapshotFunctionState(halideFunction);
861+ execute();
862+ if (!manualCommit) {
863+ commit();
864+ }
865+ }
866+
867+ function commit() {
868+ internal.pushUndoState(savedState);
869+ internal.clearRedoStates();
870+ halideFunction.enabled = true;
871+ halideTransform.live = true;
872+ }
873+
874+ function execute() {
875+ // to be overridden by subclasses
876+ }
877+}
878
879=== added file 'modules/Ubuntu/Components/Extras/PhotoEditor/ExifOrientation.qml'
880--- modules/Ubuntu/Components/Extras/PhotoEditor/ExifOrientation.qml 1970-01-01 00:00:00 +0000
881+++ modules/Ubuntu/Components/Extras/PhotoEditor/ExifOrientation.qml 2015-06-12 13:08:47 +0000
882@@ -0,0 +1,32 @@
883+/*
884+ * Copyright (C) 2015 Canonical, Ltd.
885+ *
886+ * This program is free software; you can redistribute it and/or modify
887+ * it under the terms of the GNU Lesser General Public License as published by
888+ * the Free Software Foundation; version 3.
889+ *
890+ * This program is distributed in the hope that it will be useful,
891+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
892+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
893+ * GNU Lesser General Public License for more details.
894+ *
895+ * You should have received a copy of the GNU Lesser General Public License
896+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
897+ */
898+
899+import QtQml 2.0
900+
901+QtObject {
902+ property int orientation: 1
903+ property var sequence: [1, 6, 3, 8]
904+
905+ function rotateRight90() {
906+ var index = (sequence.indexOf(orientation) + 1) % sequence.length;
907+ orientation = sequence[index];
908+ }
909+
910+ function add(a, b) {
911+ var index = (sequence.indexOf(a) + sequence.indexOf(b)) % sequence.length;
912+ return sequence[index];
913+ }
914+}
915
916=== added file 'modules/Ubuntu/Components/Extras/PhotoEditor/FunctionStateSaver.qml'
917--- modules/Ubuntu/Components/Extras/PhotoEditor/FunctionStateSaver.qml 1970-01-01 00:00:00 +0000
918+++ modules/Ubuntu/Components/Extras/PhotoEditor/FunctionStateSaver.qml 2015-06-12 13:08:47 +0000
919@@ -0,0 +1,56 @@
920+/*
921+ * Copyright (C) 2015 Canonical Ltd
922+ *
923+ * This program is free software: you can redistribute it and/or modify
924+ * it under the terms of the GNU General Public License version 3 as
925+ * published by the Free Software Foundation.
926+ *
927+ * This program is distributed in the hope that it will be useful,
928+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
929+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
930+ * GNU General Public License for more details.
931+ *
932+ * You should have received a copy of the GNU General Public License
933+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
934+ */
935+
936+import QtQuick 2.3
937+
938+QtObject {
939+ property QtObject target
940+ property var values
941+
942+ function qmltypeof(obj, className) {
943+ // className plus "(" is the class instance without modification
944+ // className plus "_QML" is the class instance with user-defined properties
945+ if (obj != null) {
946+ var str = obj.toString();
947+ return str.indexOf(className + "(") == 0 || str.indexOf(className + "_QML") == 0;
948+ } else {
949+ return false;
950+ }
951+ }
952+
953+ function copy(destination, prop, value) {
954+ if (qmltypeof(value, "QMatrix4x4")) {
955+ destination[prop] = value.transposed().transposed();
956+ } else {
957+ destination[prop] = value;
958+ }
959+ }
960+
961+ function save() {
962+ values = {};
963+ for (var prop in target) {
964+ if (typeof(target[prop]) !== "function") {
965+ copy(values, prop, target[prop]);
966+ }
967+ }
968+ }
969+
970+ function restore() {
971+ for (var prop in values) {
972+ copy(target, prop, values[prop]);
973+ }
974+ }
975+}
976
977=== added file 'modules/Ubuntu/Components/Extras/PhotoEditor/FunctionTransform.qml'
978--- modules/Ubuntu/Components/Extras/PhotoEditor/FunctionTransform.qml 1970-01-01 00:00:00 +0000
979+++ modules/Ubuntu/Components/Extras/PhotoEditor/FunctionTransform.qml 2015-06-12 13:08:47 +0000
980@@ -0,0 +1,77 @@
981+/*
982+ * Copyright (C) 2015 Canonical, Ltd.
983+ *
984+ * This program is free software; you can redistribute it and/or modify
985+ * it under the terms of the GNU Lesser General Public License as published by
986+ * the Free Software Foundation; version 3.
987+ *
988+ * This program is distributed in the hope that it will be useful,
989+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
990+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
991+ * GNU Lesser General Public License for more details.
992+ *
993+ * You should have received a copy of the GNU Lesser General Public License
994+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
995+ */
996+
997+import QtQuick 2.3
998+import QtHalide 0.1
999+
1000+HalideFunction {
1001+ name: "transform"
1002+
1003+ property bool rotationOnly: true
1004+ property HalideImage input
1005+ property matrix4x4 transform: Qt.matrix4x4(1, 0, 0, 0,
1006+ 0, 1, 0, 0,
1007+ 0, 0, 1, 0,
1008+ 0, 0, 0, 1)
1009+ property matrix4x4 size: Qt.matrix4x4(1, 0, 0, 0,
1010+ 0, 1, 0, 0,
1011+ 0, 0, 1, 0,
1012+ 0, 0, 0, 1)
1013+
1014+ function rotateRight90() {
1015+ var angle = Math.PI/2;
1016+ var resizing = Qt.matrix4x4(Math.cos(angle), Math.sin(angle), 0, 0,
1017+ Math.sin(angle), Math.cos(angle), 0, 0,
1018+ 0, 0, 1, 0,
1019+ 0, 0, 0, 1);
1020+
1021+ var originalSize = Qt.vector3d(1, 1, 1);
1022+ var inputSize = size.times(originalSize);
1023+ var outputSize = resizing.times(inputSize);
1024+
1025+ var centering = Qt.matrix4x4(1, 0, (outputSize.x - inputSize.x)/2, 0,
1026+ 0, 1, (outputSize.y - inputSize.y)/2, 0,
1027+ 0, 0, 1, 0,
1028+ 0, 0, 0, 1);
1029+ var translation = Qt.matrix4x4(1, 0, inputSize.x/2.0, 0,
1030+ 0, 1, inputSize.y/2.0, 0,
1031+ 0, 0, 1, 0,
1032+ 0, 0, 0, 1);
1033+ var rotation = Qt.matrix4x4(Math.cos(angle), -Math.sin(angle), 0, 0,
1034+ Math.sin(angle), Math.cos(angle), 0, 0,
1035+ 0, 0, 1, 0,
1036+ 0, 0, 0, 1);
1037+ transform = transform.times(centering.times(translation.times(rotation.times(translation.inverted()))).inverted());
1038+ size = resizing.times(size);
1039+ }
1040+
1041+ function crop(startX, startY, cropWidth, cropHeight) {
1042+ rotationOnly = false;
1043+ var resizing = Qt.matrix4x4(cropWidth, 0, 0, 0,
1044+ 0, cropHeight, 0, 0,
1045+ 0, 0, 1, 0,
1046+ 0, 0, 0, 1);
1047+
1048+ var originalSize = Qt.vector3d(1, 1, 1);
1049+ var inputSize = size.times(originalSize);
1050+ var translation = Qt.matrix4x4(1, 0, startX*inputSize.x, 0,
1051+ 0, 1, startY*inputSize.y, 0,
1052+ 0, 0, 1, 0,
1053+ 0, 0, 0, 1);
1054+ transform = transform.times(translation);
1055+ size = resizing.times(size);
1056+ }
1057+}
1058
1059=== removed file 'modules/Ubuntu/Components/Extras/PhotoEditor/assets/edit_revert@27.png'
1060Binary files modules/Ubuntu/Components/Extras/PhotoEditor/assets/edit_revert@27.png 2015-02-03 16:38:18 +0000 and modules/Ubuntu/Components/Extras/PhotoEditor/assets/edit_revert@27.png 1970-01-01 00:00:00 +0000 differ
1061=== renamed file 'modules/Ubuntu/Components/Extras/PhotoEditor/assets/edit_rotate_left@27.png' => 'modules/Ubuntu/Components/Extras/PhotoEditor/assets/edit_rotate_right@27.png'
1062Binary files modules/Ubuntu/Components/Extras/PhotoEditor/assets/edit_rotate_left@27.png 2015-02-03 16:38:18 +0000 and modules/Ubuntu/Components/Extras/PhotoEditor/assets/edit_rotate_right@27.png 2015-06-12 13:08:47 +0000 differ
1063=== modified file 'modules/Ubuntu/Components/Extras/plugin/CMakeLists.txt'
1064--- modules/Ubuntu/Components/Extras/plugin/CMakeLists.txt 2014-12-09 10:57:19 +0000
1065+++ modules/Ubuntu/Components/Extras/plugin/CMakeLists.txt 2015-06-12 13:08:47 +0000
1066@@ -1,5 +1,10 @@
1067 include(FindPkgConfig)
1068 pkg_check_modules(EXIV2 REQUIRED exiv2) # photoeditor
1069+pkg_check_modules(HALIDE Halide)
1070+
1071+# Necessary to use Halide's Generators
1072+add_definitions("-std=c++11")
1073+add_definitions("-fno-rtti")
1074
1075 set(PLUGIN_SRC
1076 components.cpp
1077@@ -11,16 +16,14 @@
1078
1079 set(PHOTO_EDITOR_PLUGIN_SRC
1080 photoeditor/file-utils.cpp
1081- photoeditor/orientation.cpp
1082- photoeditor/photo-data.cpp
1083 photoeditor/photo-image-provider.cpp
1084 photoeditor/photo-metadata.cpp
1085- photoeditor/imaging.cpp
1086- photoeditor/photo-edit-thread.cpp
1087+ photoeditor/generator_transform.cpp
1088 )
1089
1090 include_directories(
1091 ${CMAKE_BINARY_DIR}
1092+ ${HALIDE_INCLUDE_DIRS}
1093 )
1094
1095 add_library(ubuntu-ui-extras-plugin SHARED ${PLUGIN_SRC} ${PLUGIN_HDRS}
1096@@ -28,6 +31,7 @@
1097 qt5_use_modules(ubuntu-ui-extras-plugin Core Qml Quick Xml Widgets)
1098 target_link_libraries(ubuntu-ui-extras-plugin
1099 ${EXIV2_LIBRARIES}
1100+ ${HALIDE_LIBRARIES}
1101 )
1102
1103 # Qt5's cmake does not export QT_IMPORTS_DIR, lets query qmake on our own for now
1104
1105=== modified file 'modules/Ubuntu/Components/Extras/plugin/components.cpp'
1106--- modules/Ubuntu/Components/Extras/plugin/components.cpp 2015-06-12 13:08:47 +0000
1107+++ modules/Ubuntu/Components/Extras/plugin/components.cpp 2015-06-12 13:08:47 +0000
1108@@ -19,7 +19,7 @@
1109 #include "components.h"
1110 #include "example/example-model.h"
1111
1112-#include "photoeditor/photo-data.h"
1113+#include "photoeditor/photo-metadata.h"
1114 #include "photoeditor/photo-image-provider.h"
1115 #include "photoeditor/file-utils.h"
1116
1117@@ -29,8 +29,8 @@
1118 qmlRegisterType<ExampleModel>(uri, 0, 2, "ExampleModel");
1119
1120 // PhotoEditor component
1121- qmlRegisterType<PhotoData>(uri, 0, 2, "PhotoData");
1122- qmlRegisterSingletonType<FileUtils>(uri, 0, 2, "FileUtils",
1123+ qmlRegisterType<PhotoEditor::PhotoMetadata>(uri, 0, 2, "PhotoMetadata");
1124+ qmlRegisterSingletonType<PhotoEditor::FileUtils>(uri, 0, 2, "FileUtils",
1125 exportFileUtilsSingleton);
1126 }
1127
1128@@ -49,5 +49,5 @@
1129 Q_UNUSED(engine);
1130 Q_UNUSED(scriptEngine);
1131
1132- return new FileUtils();
1133+ return new PhotoEditor::FileUtils();
1134 }
1135
1136=== modified file 'modules/Ubuntu/Components/Extras/plugin/photoeditor/file-utils.cpp'
1137--- modules/Ubuntu/Components/Extras/plugin/photoeditor/file-utils.cpp 2014-11-25 15:44:31 +0000
1138+++ modules/Ubuntu/Components/Extras/plugin/photoeditor/file-utils.cpp 2015-06-12 13:08:47 +0000
1139@@ -22,6 +22,8 @@
1140 #include <QFileInfo>
1141 #include <QTemporaryDir>
1142
1143+namespace PhotoEditor {
1144+
1145 FileUtils::FileUtils(QObject *parent) :
1146 QObject(parent)
1147 {
1148@@ -95,3 +97,5 @@
1149 {
1150 return QFileInfo::exists(path);
1151 }
1152+
1153+}
1154
1155=== modified file 'modules/Ubuntu/Components/Extras/plugin/photoeditor/file-utils.h'
1156--- modules/Ubuntu/Components/Extras/plugin/photoeditor/file-utils.h 2014-12-09 15:15:43 +0000
1157+++ modules/Ubuntu/Components/Extras/plugin/photoeditor/file-utils.h 2015-06-12 13:08:47 +0000
1158@@ -19,6 +19,8 @@
1159
1160 #include <QObject>
1161
1162+namespace PhotoEditor {
1163+
1164 class FileUtils : public QObject
1165 {
1166 Q_OBJECT
1167@@ -39,4 +41,6 @@
1168 Q_INVOKABLE bool exists(QString path) const;
1169 };
1170
1171+}
1172+
1173 #endif // PHOTOUTILS_H
1174
1175=== added file 'modules/Ubuntu/Components/Extras/plugin/photoeditor/generator_transform.cpp'
1176--- modules/Ubuntu/Components/Extras/plugin/photoeditor/generator_transform.cpp 1970-01-01 00:00:00 +0000
1177+++ modules/Ubuntu/Components/Extras/plugin/photoeditor/generator_transform.cpp 2015-06-12 13:08:47 +0000
1178@@ -0,0 +1,65 @@
1179+/*
1180+ * Copyright (C) 2015 Canonical, Ltd.
1181+ *
1182+ * This program is free software; you can redistribute it and/or modify
1183+ * it under the terms of the GNU Lesser General Public License as published by
1184+ * the Free Software Foundation; version 3.
1185+ *
1186+ * This program is distributed in the hope that it will be useful,
1187+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1188+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1189+ * GNU Lesser General Public License for more details.
1190+ *
1191+ * You should have received a copy of the GNU Lesser General Public License
1192+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1193+ */
1194+
1195+#include <Halide.h>
1196+#include "halide_generators_common.h"
1197+
1198+namespace {
1199+
1200+class Transform : public HalideGeneratorExtended<Transform> {
1201+public:
1202+ Halide::ImageParam input{ Halide::type_of<uint8_t>(), 3, "input" };
1203+ Halide::ImageParam transform{ Halide::type_of<float>(), 2, "transform" };
1204+ Halide::ImageParam size{ Halide::type_of<float>(), 2, "size" };
1205+
1206+ Halide::Func build() {
1207+ Halide::Var x, y, c;
1208+ Halide::Func function;
1209+ Halide::Func clamped;
1210+ clamped(x, y, c) = input(Halide::clamp(x, 0, input.width()-1),
1211+ Halide::clamp(y, 0, input.height()-1), c);
1212+ function(x, y, c) = clamped(Halide::cast<int>(transform(0, 0)*x + transform(0, 1)*y + transform(0, 2)*input.width()),
1213+ Halide::cast<int>(transform(1, 0)*x + transform(1, 1)*y + transform(1, 2)*input.height()),
1214+ c);
1215+ schedule_cpu_and_glsl(this, input, function, x, y, c);
1216+
1217+ set_packed(input);
1218+ set_packed(function.output_buffer());
1219+
1220+ return function;
1221+ }
1222+
1223+ float getMatrixValue(Halide::ImageParam matrix, unsigned int row, unsigned int column) {
1224+ return ((float*)(matrix.get().raw_buffer()->host))[row + column * matrix.get().extent(1)];
1225+ }
1226+
1227+ QRect getOutputGeometry() {
1228+ int input_width = input.get().extent(0);
1229+ int input_height = input.get().extent(1);
1230+ int width = getMatrixValue(size, 0, 0) * input_width + getMatrixValue(size, 0, 1) * input_height;
1231+ int height = getMatrixValue(size, 1, 0) * input_width + getMatrixValue(size, 1, 1) * input_height;
1232+
1233+ QRect out;
1234+ out.setWidth(width);
1235+ out.setHeight(height);
1236+
1237+ return out;
1238+ }
1239+};
1240+
1241+Halide::RegisterGenerator<Transform> register_transform{"transform"};
1242+
1243+}
1244
1245=== added file 'modules/Ubuntu/Components/Extras/plugin/photoeditor/halide_common.h'
1246--- modules/Ubuntu/Components/Extras/plugin/photoeditor/halide_common.h 1970-01-01 00:00:00 +0000
1247+++ modules/Ubuntu/Components/Extras/plugin/photoeditor/halide_common.h 2015-06-12 13:08:47 +0000
1248@@ -0,0 +1,91 @@
1249+/*
1250+ * Copyright (C) 2015 Canonical, Ltd.
1251+ *
1252+ * This program is free software; you can redistribute it and/or modify
1253+ * it under the terms of the GNU Lesser General Public License as published by
1254+ * the Free Software Foundation; version 3.
1255+ *
1256+ * This program is distributed in the hope that it will be useful,
1257+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1258+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1259+ * GNU Lesser General Public License for more details.
1260+ *
1261+ * You should have received a copy of the GNU Lesser General Public License
1262+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1263+ */
1264+
1265+#ifndef HALIDE_COMMON_H
1266+#define HALIDE_COMMON_H
1267+
1268+#include <QtCore/QRect>
1269+#include <QtCore/QHash>
1270+#include <QtGui/QImage>
1271+#include <Halide.h>
1272+
1273+typedef QRect (*getOutputGeometryFunction)(Halide::Internal::GeneratorBase*);
1274+
1275+class GeneratorHelperRegistry
1276+{
1277+public:
1278+ static QHash<Halide::Internal::GeneratorBase*, getOutputGeometryFunction>* getOutputGeometryRegistry() {
1279+ static QHash<Halide::Internal::GeneratorBase*, getOutputGeometryFunction>* registry = new QHash<Halide::Internal::GeneratorBase*, getOutputGeometryFunction>;
1280+ return registry;
1281+ }
1282+};
1283+
1284+inline void set_packed(Halide::OutputImageParam image, int channels = 4) {
1285+ image.set_stride(0, channels);
1286+ image.set_stride(2, 1);
1287+ image.set_extent(2, channels);
1288+}
1289+
1290+inline buffer_t* packedBuffer(uint8_t* pixels, int width, int height, int channels)
1291+{
1292+ buffer_t* buffer = new buffer_t;
1293+ memset(buffer, 0, sizeof(buffer_t));
1294+ buffer->host = pixels;
1295+ buffer->elem_size = 1;
1296+ buffer->extent[0] = width;
1297+ buffer->extent[1] = height;
1298+ buffer->extent[2] = channels;
1299+ buffer->stride[0] = buffer->extent[2];
1300+ buffer->stride[1] = buffer->extent[0] * buffer->stride[0];
1301+ buffer->stride[2] = 1;
1302+ return buffer;
1303+}
1304+
1305+inline buffer_t* halideBufferTFromQImage(QImage& image)
1306+{
1307+ image = image.convertToFormat(QImage::Format_RGBA8888);
1308+ return packedBuffer(image.bits(), image.width(), image.height(), 4);
1309+}
1310+
1311+inline Halide::Buffer halideBufferFromQImage(QImage& image)
1312+{
1313+ buffer_t* buft = halideBufferTFromQImage(image);
1314+ buft->host_dirty = true;
1315+ Halide::Buffer buffer(Halide::type_of<uint8_t>(), buft);
1316+ delete buft;
1317+ return buffer;
1318+}
1319+
1320+inline Halide::Buffer halideBufferFromTexture(uint textureId, QSize size)
1321+{
1322+ buffer_t* buft = packedBuffer(NULL, size.width(), size.height(), 4);
1323+ buft->dev = textureId;
1324+ Halide::Buffer buffer = Halide::Buffer(Halide::type_of<uint8_t>(), buft);
1325+ delete buft;
1326+ return buffer;
1327+}
1328+
1329+inline QRect getOutputGeometry(Halide::Internal::GeneratorBase* generator)
1330+{
1331+ getOutputGeometryFunction getOutputGeometry = GeneratorHelperRegistry::getOutputGeometryRegistry()->value(generator, 0);
1332+ return getOutputGeometry ? getOutputGeometry(generator) : QRect();
1333+}
1334+
1335+
1336+
1337+#endif // HALIDE_COMMON_H
1338+
1339+
1340
1341=== added file 'modules/Ubuntu/Components/Extras/plugin/photoeditor/halide_generators_common.h'
1342--- modules/Ubuntu/Components/Extras/plugin/photoeditor/halide_generators_common.h 1970-01-01 00:00:00 +0000
1343+++ modules/Ubuntu/Components/Extras/plugin/photoeditor/halide_generators_common.h 2015-06-12 13:08:47 +0000
1344@@ -0,0 +1,63 @@
1345+/*
1346+ * Copyright (C) 2015 Canonical, Ltd.
1347+ *
1348+ * This program is free software; you can redistribute it and/or modify
1349+ * it under the terms of the GNU Lesser General Public License as published by
1350+ * the Free Software Foundation; version 3.
1351+ *
1352+ * This program is distributed in the hope that it will be useful,
1353+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1354+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1355+ * GNU Lesser General Public License for more details.
1356+ *
1357+ * You should have received a copy of the GNU Lesser General Public License
1358+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1359+ */
1360+
1361+#ifndef HALIDE_GENERATORS_COMMON_H
1362+#define HALIDE_GENERATORS_COMMON_H
1363+
1364+#include <Halide.h>
1365+#include <QtCore/QRect>
1366+#include "halide_common.h"
1367+
1368+inline void schedule_cpu_and_glsl(Halide::Internal::GeneratorBase* generator, Halide::ImageParam& input, Halide::Func& function, Halide::Var x, Halide::Var y, Halide::Var c) {
1369+ Halide::Target target = generator->get_target();
1370+ if (target.has_feature(Halide::Target::OpenGL)) {
1371+ // Schedule for GLSL
1372+ input.set_bounds(2, 0, 3);
1373+ function.bound(c, 0, 4);
1374+ function.glsl(x, y, c);
1375+ } else {
1376+ // Schedule for CPU
1377+ function.vectorize(c, 4).parallel(y);
1378+ }
1379+}
1380+
1381+template <class T> class HalideGeneratorExtended : public Halide::Generator<T> {
1382+public:
1383+ HalideGeneratorExtended() {
1384+ GeneratorHelperRegistry::getOutputGeometryRegistry()->insert(this, &HalideGeneratorExtended::staticGetOutputGeometry);
1385+ }
1386+
1387+ static QRect staticGetOutputGeometry(Halide::Internal::GeneratorBase* generator) {
1388+ return (static_cast<HalideGeneratorExtended*>(generator))->getOutputGeometry();
1389+ }
1390+
1391+ virtual QRect getOutputGeometry() {
1392+ QRect out;
1393+ Q_FOREACH(Halide::Internal::Parameter parameter, this->get_filter_parameters()) {
1394+ if (parameter.is_buffer() && QString::fromStdString(parameter.name()) == "input") {
1395+ out.setWidth(parameter.get_buffer().extent(0));
1396+ out.setHeight(parameter.get_buffer().extent(1));
1397+ return out;
1398+ }
1399+ }
1400+ return out;
1401+ }
1402+};
1403+
1404+
1405+#endif // HALIDE_GENERATORS_COMMON_H
1406+
1407+
1408
1409=== removed file 'modules/Ubuntu/Components/Extras/plugin/photoeditor/imaging.cpp'
1410--- modules/Ubuntu/Components/Extras/plugin/photoeditor/imaging.cpp 2014-12-02 12:02:00 +0000
1411+++ modules/Ubuntu/Components/Extras/plugin/photoeditor/imaging.cpp 1970-01-01 00:00:00 +0000
1412@@ -1,363 +0,0 @@
1413-/*
1414- * Copyright (C) 2012 Canonical Ltd
1415- *
1416- * This program is free software: you can redistribute it and/or modify
1417- * it under the terms of the GNU General Public License version 3 as
1418- * published by the Free Software Foundation.
1419- *
1420- * This program is distributed in the hope that it will be useful,
1421- * but WITHOUT ANY WARRANTY; without even the implied warranty of
1422- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1423- * GNU General Public License for more details.
1424- *
1425- * You should have received a copy of the GNU General Public License
1426- * along with this program. If not, see <http://www.gnu.org/licenses/>.
1427- *
1428- * Authors:
1429- * Lucas Beeler <lucas@yorba.org>
1430- */
1431-
1432-
1433-#include <QApplication>
1434-#include <qmath.h>
1435-
1436-#include "imaging.h"
1437-
1438-/*!
1439- * \brief HSVTransformation::transformPixel
1440- * \param pixel_color
1441- * \return
1442- */
1443-QColor HSVTransformation::transformPixel(const QColor &pixel_color) const
1444-{
1445- QColor result;
1446-
1447- int h, s, v;
1448- pixel_color.getHsv(&h, &s, &v);
1449-
1450- v = remap_table_[v];
1451-
1452- result.setHsv(h, s, v);
1453-
1454- return result;
1455-}
1456-
1457-/*!
1458- * \brief IntensityHistogram::IntensityHistogram
1459- * \param basis_image
1460- */
1461-IntensityHistogram::IntensityHistogram(const QImage& basis_image)
1462-{
1463- for (int i = 0; i < 256; i++)
1464- m_counts[i] = 0;
1465-
1466- int width = basis_image.width();
1467- int height = basis_image.height();
1468-
1469- for (int j = 0; j < height; j++) {
1470- QApplication::processEvents();
1471-
1472- for (int i = 0; i < width; i++) {
1473- QColor c = QColor(basis_image.pixel(i, j));
1474- int intensity = c.value();
1475- m_counts[intensity]++;
1476- }
1477- }
1478-
1479- float pixel_count = (float)(width * height);
1480- float accumulator = 0.0f;
1481- for (int i = 0; i < 256; i++) {
1482- m_probabilities[i] = ((float) m_counts[i]) / pixel_count;
1483- accumulator += m_probabilities[i];
1484- m_cumulativeProbabilities[i] = accumulator;
1485- }
1486-}
1487-
1488-/*!
1489- * \brief IntensityHistogram::getCumulativeProbability
1490- * \param level
1491- * \return
1492- */
1493-float IntensityHistogram::getCumulativeProbability(int level)
1494-{
1495- return m_cumulativeProbabilities[level];
1496-}
1497-
1498-
1499-const float ToneExpansionTransformation::DEFAULT_LOW_DISCARD_MASS = 0.02f;
1500-const float ToneExpansionTransformation::DEFAULT_HIGH_DISCARD_MASS = 0.98f;
1501-/*!
1502- * \brief ToneExpansionTransformation::ToneExpansionTransformation
1503- * \param h
1504- * \param low_discard_mass
1505- * \param high_discard_mass
1506- */
1507-ToneExpansionTransformation::ToneExpansionTransformation(IntensityHistogram h,
1508- float low_discard_mass, float high_discard_mass)
1509-{
1510- if (low_discard_mass == -1.0f)
1511- low_discard_mass = DEFAULT_LOW_DISCARD_MASS;
1512- if (high_discard_mass == -1.0f)
1513- high_discard_mass = DEFAULT_HIGH_DISCARD_MASS;
1514-
1515- m_lowDiscardMass = low_discard_mass;
1516- m_highDiscardMass = high_discard_mass;
1517-
1518- m_lowKink = 0;
1519- m_highKink = 255;
1520-
1521- while (h.getCumulativeProbability(m_lowKink) < low_discard_mass)
1522- m_lowKink++;
1523-
1524- while (h.getCumulativeProbability(m_highKink) > high_discard_mass)
1525- m_highKink--;
1526-
1527- m_lowKink = clampi(m_lowKink, 0, 255);
1528- m_highKink = clampi(m_highKink, 0, 255);
1529-
1530- buildRemapTable();
1531-}
1532-
1533-/*!
1534- * \brief ToneExpansionTransformation::isIdentity
1535- * \return
1536- */
1537-bool ToneExpansionTransformation::isIdentity() const
1538-{
1539- return ((m_lowKink == 0) && (m_highKink == 255));
1540-}
1541-
1542-/*!
1543- * \brief ToneExpansionTransformation::buildRemapTable
1544- */
1545-void ToneExpansionTransformation::buildRemapTable()
1546-{
1547- float low_kink_f = ((float) m_lowKink) / 255.0f;
1548- float high_kink_f = ((float) m_highKink) / 255.0f;
1549-
1550- float slope = 1.0f / (high_kink_f - low_kink_f);
1551- float intercept = -(low_kink_f / (high_kink_f - low_kink_f));
1552-
1553- int i = 0;
1554- for ( ; i <= m_lowKink; i++)
1555- remap_table_[i] = 0;
1556-
1557- for ( ; i < m_highKink; i++)
1558- remap_table_[i] = (int) ((255.0f * (slope * (((float) i) / 255.0f) +
1559- intercept)) + 0.5);
1560-
1561- for ( ; i < 256; i++)
1562- remap_table_[i] = 255;
1563-}
1564-
1565-/*!
1566- * \brief ToneExpansionTransformation::lowDiscardMass
1567- * \return
1568- */
1569-float ToneExpansionTransformation::lowDiscardMass() const
1570-{
1571- return m_lowDiscardMass;
1572-}
1573-
1574-/*!
1575- * \brief ToneExpansionTransformation::highDiscardMass
1576- * \return
1577- */
1578-float ToneExpansionTransformation::highDiscardMass() const
1579-{
1580- return m_highDiscardMass;
1581-}
1582-
1583-
1584-/*!
1585- * \brief HermiteGammaApproximationFunction::HermiteGammaApproximationFunction
1586- * \param user_interval_upper
1587- */
1588-HermiteGammaApproximationFunction::HermiteGammaApproximationFunction(
1589- float user_interval_upper)
1590-{
1591- m_nonzeroIntervalUpper = clampf(user_interval_upper, 0.1f, 1.0f);
1592- m_xScale = 1.0f / m_nonzeroIntervalUpper;
1593-}
1594-
1595-/*!
1596- * \brief HermiteGammaApproximationFunction::evaluate
1597- * \param x
1598- * \return
1599- */
1600-float HermiteGammaApproximationFunction::evaluate(float x)
1601-{
1602- if (x < 0.0f)
1603- return 0.0f;
1604- else if (x > m_nonzeroIntervalUpper)
1605- return 0.0f;
1606- else {
1607- float indep_var = m_xScale * x;
1608-
1609- float dep_var = 6.0f * ((indep_var * indep_var * indep_var) -
1610- (2.0f * (indep_var * indep_var)) + (indep_var));
1611-
1612- return clampf(dep_var, 0.0f, 1.0f);
1613- }
1614-}
1615-
1616-
1617-const float ShadowDetailTransformation::MAX_EFFECT_SHIFT = 0.5f;
1618-const float ShadowDetailTransformation::MIN_TONAL_WIDTH = 0.1f;
1619-const float ShadowDetailTransformation::MAX_TONAL_WIDTH = 1.0f;
1620-const float ShadowDetailTransformation::TONAL_WIDTH = 1.0f;
1621-/*!
1622- * \brief ShadowDetailTransformation::ShadowDetailTransformation
1623- * \param intensity
1624- */
1625-ShadowDetailTransformation::ShadowDetailTransformation(float intensity)
1626-{
1627- m_intensity = intensity;
1628- float effect_shift = MAX_EFFECT_SHIFT * intensity;
1629-
1630- HermiteGammaApproximationFunction func =
1631- HermiteGammaApproximationFunction(TONAL_WIDTH);
1632-
1633- for (int i = 0; i < 256; i++) {
1634- float x = ((float) i) / 255.0f;
1635- float weight = func.evaluate(x);
1636-
1637- int remapped = (int) ((255.0f * (weight * (x + effect_shift)) + ((1.0f -
1638- weight) * x)) + 0.5f);
1639- remap_table_[i] = clampi(remapped, i, 255);
1640- }
1641-}
1642-
1643-/*!
1644- * \brief ShadowDetailTransformation::isIdentity
1645- * \return
1646- */
1647-bool ShadowDetailTransformation::isIdentity() const
1648-{
1649- return (m_intensity == 0.0f);
1650-}
1651-
1652-
1653-const int AutoEnhanceTransformation::SHADOW_DETECT_MIN_INTENSITY = 2;
1654-const int AutoEnhanceTransformation::SHADOW_DETECT_MAX_INTENSITY = 90;
1655-const int AutoEnhanceTransformation::SHADOW_DETECT_INTENSITY_RANGE =
1656- AutoEnhanceTransformation::SHADOW_DETECT_MAX_INTENSITY -
1657- AutoEnhanceTransformation::SHADOW_DETECT_MIN_INTENSITY;
1658-const int AutoEnhanceTransformation::EMPIRICAL_DARK = 40;
1659-const float AutoEnhanceTransformation::SHADOW_AGGRESSIVENESS_MUL = 0.45f;
1660-/*!
1661- * \brief AutoEnhanceTransformation::AutoEnhanceTransformation
1662- * \param basis
1663- */
1664-AutoEnhanceTransformation::AutoEnhanceTransformation(const QImage& basis)
1665- : m_shadowTransform(0), m_toneExpansionTransform(0)
1666-{
1667- IntensityHistogram histogram = IntensityHistogram(basis);
1668-
1669- /* compute the percentage of pixels in the image that fall into the
1670- shadow range -- this measures "of the pixels in the image, how many of
1671- them are in shadow?" */
1672- float pct_in_range = 100.0f *
1673- (histogram.getCumulativeProbability(SHADOW_DETECT_MAX_INTENSITY) -
1674- histogram.getCumulativeProbability(SHADOW_DETECT_MIN_INTENSITY));
1675-
1676- /* compute the mean intensity of the pixels that are in the shadow range --
1677- this measures "of those pixels that are in shadow, just how dark are
1678- they?" */
1679- float sh_prob_mu =
1680- (histogram.getCumulativeProbability(SHADOW_DETECT_MIN_INTENSITY) +
1681- histogram.getCumulativeProbability(SHADOW_DETECT_MAX_INTENSITY)) * 0.5f;
1682- int sh_intensity_mu = SHADOW_DETECT_MIN_INTENSITY;
1683- for ( ; sh_intensity_mu <= SHADOW_DETECT_MAX_INTENSITY; sh_intensity_mu++) {
1684- if (histogram.getCumulativeProbability(sh_intensity_mu) >= sh_prob_mu)
1685- break;
1686- }
1687-
1688- /* if more than 30 percent of the pixels in the image are in the shadow
1689- detection range, or if the mean intensity within the shadow range is less
1690- than an empirically determined threshold below which pixels appear very
1691- dark, regardless of the percent of pixels in it, then perform shadow
1692- detail enhancement. Otherwise, skip shadow detail enhancement and perform
1693- contrast expansion only */
1694- if ((pct_in_range > 30.0f) || ((pct_in_range > 10.0f) &&
1695- (sh_intensity_mu < EMPIRICAL_DARK))) {
1696- float shadow_trans_effect_size = ((((float) SHADOW_DETECT_MAX_INTENSITY) -
1697- ((float) sh_intensity_mu)) /
1698- ((float) SHADOW_DETECT_INTENSITY_RANGE));
1699- shadow_trans_effect_size *= SHADOW_AGGRESSIVENESS_MUL;
1700-
1701- m_shadowTransform
1702- = new ShadowDetailTransformation(shadow_trans_effect_size);
1703-
1704- QImage shadow_corrected_image = QImage(basis);
1705- // Can't write into indexed images, due to a limitation in Qt.
1706- if (shadow_corrected_image.format() == QImage::Format_Indexed8)
1707- shadow_corrected_image = shadow_corrected_image.convertToFormat(
1708- QImage::Format_RGB32);
1709-
1710- for (int j = 0; j < shadow_corrected_image.height(); j++) {
1711- QApplication::processEvents();
1712-
1713- for (int i = 0; i < shadow_corrected_image.width(); i++) {
1714- QColor px = m_shadowTransform->transformPixel(
1715- QColor(shadow_corrected_image.pixel(i, j)));
1716- shadow_corrected_image.setPixel(i, j, px.rgb());
1717- }
1718- }
1719-
1720- m_toneExpansionTransform = new ToneExpansionTransformation(
1721- IntensityHistogram(shadow_corrected_image), 0.005f, 0.995f);
1722-
1723- } else {
1724- m_toneExpansionTransform = new ToneExpansionTransformation(
1725- IntensityHistogram(basis));
1726- }
1727-}
1728-
1729-/*!
1730- * \brief AutoEnhanceTransformation::~AutoEnhanceTransformation
1731- */
1732-AutoEnhanceTransformation::~AutoEnhanceTransformation()
1733-{
1734- if (m_shadowTransform)
1735- delete m_shadowTransform;
1736- delete m_toneExpansionTransform;
1737-}
1738-
1739-/*!
1740- * \brief AutoEnhanceTransformation::transformPixel
1741- * \param pixel_color
1742- * \return
1743- */
1744-QColor AutoEnhanceTransformation::transformPixel(
1745- const QColor& pixel_color) const
1746-{
1747- QColor px = pixel_color;
1748-
1749- if (m_shadowTransform)
1750- px = m_shadowTransform->transformPixel(px);
1751-
1752- px = m_toneExpansionTransform->transformPixel(px);
1753-
1754- /* if tone expansion occurs, boost saturation to compensate for boosted
1755- dynamic range */
1756- if (!m_toneExpansionTransform->isIdentity()) {
1757- int h, s, v;
1758- px.getHsv(&h, &s, &v);
1759-
1760- float compensation_multiplier =
1761- (m_toneExpansionTransform->lowDiscardMass() < 0.01f) ? 1.02f : 1.10f;
1762-
1763- s = (int) (((float) s) * compensation_multiplier);
1764- s = clampi(s, 0, 255);
1765-
1766- px.setHsv(h, s, v);
1767- }
1768-
1769- return px;
1770-}
1771-
1772-bool AutoEnhanceTransformation::isIdentity() const
1773-{
1774- return false;
1775-}
1776
1777=== removed file 'modules/Ubuntu/Components/Extras/plugin/photoeditor/imaging.h'
1778--- modules/Ubuntu/Components/Extras/plugin/photoeditor/imaging.h 2014-12-02 12:02:00 +0000
1779+++ modules/Ubuntu/Components/Extras/plugin/photoeditor/imaging.h 1970-01-01 00:00:00 +0000
1780@@ -1,169 +0,0 @@
1781-/*
1782- * Copyright (C) 2012 Canonical Ltd
1783- *
1784- * This program is free software: you can redistribute it and/or modify
1785- * it under the terms of the GNU General Public License version 3 as
1786- * published by the Free Software Foundation.
1787- *
1788- * This program is distributed in the hope that it will be useful,
1789- * but WITHOUT ANY WARRANTY; without even the implied warranty of
1790- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1791- * GNU General Public License for more details.
1792- *
1793- * You should have received a copy of the GNU General Public License
1794- * along with this program. If not, see <http://www.gnu.org/licenses/>.
1795- *
1796- * Authors:
1797- * Lucas Beeler <lucas@yorba.org>
1798- */
1799-
1800-#ifndef GALLERY_UTIL_IMAGING_H_
1801-#define GALLERY_UTIL_IMAGING_H_
1802-
1803-#include <QColor>
1804-#include <QImage>
1805-#include <QVector4D>
1806-
1807-/*!
1808- * \brief clampi
1809- * \param i
1810- * \param min
1811- * \param max
1812- * \return
1813- */
1814-inline int clampi(int i, int min, int max) {
1815- return (i < min) ? min : ((i > max) ? max : i);
1816-}
1817-
1818-/*!
1819- * \brief clampf
1820- * \param x
1821- * \param min
1822- * \param max
1823- * \return
1824- */
1825-inline float clampf(float x, float min, float max) {
1826- return (x < min) ? min : ((x > max) ? max : x);
1827-}
1828-
1829-/*!
1830- * \brief The HSVTransformation class
1831- */
1832-class HSVTransformation
1833-{
1834-public:
1835- HSVTransformation() { }
1836- virtual ~HSVTransformation() { }
1837-
1838- virtual QColor transformPixel(const QColor& pixel_color) const;
1839- virtual bool isIdentity() const = 0;
1840-
1841-protected:
1842- int remap_table_[256];
1843-};
1844-
1845-/*!
1846- * \brief The IntensityHistogram class
1847- */
1848-class IntensityHistogram
1849-{
1850-public:
1851- IntensityHistogram(const QImage& basis_image);
1852- virtual ~IntensityHistogram() { }
1853-
1854- float getCumulativeProbability(int level);
1855-
1856-private:
1857- int m_counts[256];
1858- float m_probabilities[256];
1859- float m_cumulativeProbabilities[256];
1860-};
1861-
1862-/*!
1863- * \brief The ToneExpansionTransformation class
1864- */
1865-class ToneExpansionTransformation : public virtual HSVTransformation
1866-{
1867- static const float DEFAULT_LOW_DISCARD_MASS;
1868- static const float DEFAULT_HIGH_DISCARD_MASS;
1869-
1870-public:
1871- ToneExpansionTransformation(IntensityHistogram h, float lowDiscardMass =
1872- -1.0f, float highDiscardMass = -1.0f);
1873- virtual ~ToneExpansionTransformation() { }
1874-
1875- bool isIdentity() const;
1876-
1877- float lowDiscardMass() const;
1878- float highDiscardMass() const;
1879-
1880-private:
1881- void buildRemapTable();
1882-
1883- int m_lowKink;
1884- int m_highKink;
1885- float m_lowDiscardMass;
1886- float m_highDiscardMass;
1887-};
1888-
1889-/*!
1890- * \brief The HermiteGammaApproximationFunction class
1891- */
1892-class HermiteGammaApproximationFunction
1893-{
1894-public:
1895- HermiteGammaApproximationFunction(float user_interval_upper);
1896- virtual ~HermiteGammaApproximationFunction() { }
1897-
1898- float evaluate(float x);
1899-
1900-private:
1901- float m_xScale;
1902- float m_nonzeroIntervalUpper;
1903-};
1904-
1905-/*!
1906- * \brief The ShadowDetailTransformation class
1907- */
1908-class ShadowDetailTransformation : public virtual HSVTransformation
1909-{
1910- static const float MAX_EFFECT_SHIFT;
1911- static const float MIN_TONAL_WIDTH;
1912- static const float MAX_TONAL_WIDTH;
1913- static const float TONAL_WIDTH;
1914-
1915-public:
1916- ShadowDetailTransformation(float intensity);
1917-
1918- bool isIdentity() const;
1919-
1920-private:
1921- float m_intensity;
1922-};
1923-
1924-/*!
1925- * \brief The AutoEnhanceTransformation class
1926- */
1927-class AutoEnhanceTransformation : public virtual HSVTransformation
1928-{
1929- static const int SHADOW_DETECT_MIN_INTENSITY;
1930- static const int SHADOW_DETECT_MAX_INTENSITY;
1931- static const int SHADOW_DETECT_INTENSITY_RANGE;
1932- static const int EMPIRICAL_DARK;
1933- static const float SHADOW_AGGRESSIVENESS_MUL;
1934-
1935-public:
1936- AutoEnhanceTransformation(const QImage& basis_image);
1937- virtual ~AutoEnhanceTransformation();
1938-
1939- QColor transformPixel(const QColor& pixel_color) const;
1940- bool isIdentity() const;
1941-
1942-private:
1943- ShadowDetailTransformation* m_shadowTransform;
1944- ToneExpansionTransformation* m_toneExpansionTransform;
1945-};
1946-
1947-
1948-#endif // GALLERY_UTIL_IMAGING_H_
1949-
1950
1951=== removed file 'modules/Ubuntu/Components/Extras/plugin/photoeditor/orientation.cpp'
1952--- modules/Ubuntu/Components/Extras/plugin/photoeditor/orientation.cpp 2014-11-19 10:14:20 +0000
1953+++ modules/Ubuntu/Components/Extras/plugin/photoeditor/orientation.cpp 1970-01-01 00:00:00 +0000
1954@@ -1,152 +0,0 @@
1955-/*
1956- * Copyright (C) 2011 Canonical Ltd
1957- *
1958- * This program is free software: you can redistribute it and/or modify
1959- * it under the terms of the GNU General Public License version 3 as
1960- * published by the Free Software Foundation.
1961- *
1962- * This program is distributed in the hope that it will be useful,
1963- * but WITHOUT ANY WARRANTY; without even the implied warranty of
1964- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1965- * GNU General Public License for more details.
1966- *
1967- * You should have received a copy of the GNU General Public License
1968- * along with this program. If not, see <http://www.gnu.org/licenses/>.
1969- *
1970- * Authors:
1971- * Lucas Beeler <lucas@yorba.org>
1972- */
1973-
1974-#include <cstdio>
1975-
1976-#include "orientation.h"
1977-
1978-/*!
1979- * \brief OrientationCorrection::fromOrientation
1980- * \param o
1981- * \return
1982- */
1983-OrientationCorrection OrientationCorrection::fromOrientation(Orientation o)
1984-{
1985- double rotation_angle = 0.0;
1986- double horizontal_scale_factor = 1.0;
1987-
1988- switch (o) {
1989- case TOP_RIGHT_ORIGIN:
1990- horizontal_scale_factor = -1.0;
1991- break;
1992-
1993- case BOTTOM_RIGHT_ORIGIN:
1994- rotation_angle = 180.0;
1995- break;
1996-
1997- case BOTTOM_LEFT_ORIGIN:
1998- horizontal_scale_factor = -1.0;
1999- rotation_angle = 180.0;
2000- break;
2001-
2002- case LEFT_TOP_ORIGIN:
2003- horizontal_scale_factor = -1.0;
2004- rotation_angle = -90.0;
2005- break;
2006-
2007- case RIGHT_TOP_ORIGIN:
2008- rotation_angle = 90.0;
2009- break;
2010-
2011- case RIGHT_BOTTOM_ORIGIN:
2012- horizontal_scale_factor = -1.0;
2013- rotation_angle = 90.0;
2014- break;
2015-
2016- case LEFT_BOTTOM_ORIGIN:
2017- rotation_angle = -90.0;
2018- break;
2019-
2020- default:
2021- ; // do nothing
2022- break;
2023- }
2024-
2025- return OrientationCorrection(rotation_angle, horizontal_scale_factor);
2026-}
2027-
2028-/*!
2029- * \brief OrientationCorrection::identity
2030- * \return
2031- */
2032-OrientationCorrection OrientationCorrection::identity()
2033-{
2034- return OrientationCorrection(0.0, 1.0);
2035-}
2036-
2037-/*!
2038- * \brief OrientationCorrection::rotateOrientation
2039- * \param orientation
2040- * \param left
2041- * \return
2042- */
2043-Orientation OrientationCorrection::rotateOrientation(Orientation orientation, bool left)
2044-{
2045- QVector<Orientation> sequence_a;
2046- QVector<Orientation> sequence_b;
2047- sequence_a <<
2048- TOP_LEFT_ORIGIN << LEFT_BOTTOM_ORIGIN << BOTTOM_RIGHT_ORIGIN << RIGHT_TOP_ORIGIN;
2049- sequence_b <<
2050- TOP_RIGHT_ORIGIN << RIGHT_BOTTOM_ORIGIN << BOTTOM_LEFT_ORIGIN << LEFT_TOP_ORIGIN;
2051-
2052- const QVector<Orientation>& sequence = (
2053- sequence_a.contains(orientation) ? sequence_a : sequence_b);
2054-
2055- int current = sequence.indexOf(orientation);
2056- int jump = (left ? 1 : sequence.count() - 1);
2057- int next = (current + jump) % sequence.count();
2058-
2059- return sequence[next];
2060-}
2061-
2062-/*!
2063- * \brief OrientationCorrection::toTransform
2064- * Returns the correction as a QTransform.
2065- * \return Returns the correction as a QTransform.
2066- */
2067-QTransform OrientationCorrection::toTransform() const
2068-{
2069- QTransform result;
2070- result.scale(m_horizontalScaleFactor, 1.0);
2071- result.rotate(m_rotationAngle);
2072-
2073- return result;
2074-}
2075-
2076-/*!
2077- * \brief OrientationCorrection::isFlippedFrom
2078- * Returns whether the two orientations are flipped relative to each other.
2079- * Ignores rotation_angle; only checks horizontal_scale_factor_.
2080- * \param other
2081- * \return
2082- */
2083-bool OrientationCorrection::isFlippedFrom(
2084- const OrientationCorrection& other) const
2085-{
2086- return (m_horizontalScaleFactor != other.m_horizontalScaleFactor);
2087-}
2088-
2089-/*!
2090- * \brief OrientationCorrection::getNormalizedRotationDifference
2091- * Returns the rotation difference in degrees (this - other), normalized to
2092- * 0, 90, 180, or 270. Ignores the horizontal_scale_factor_.
2093- * \param other
2094- * \return
2095- */
2096-int OrientationCorrection::getNormalizedRotationDifference(
2097- const OrientationCorrection& other) const
2098-{
2099- int degrees_rotation = (int)m_rotationAngle - (int)other.m_rotationAngle;
2100- if (degrees_rotation < 0)
2101- degrees_rotation += 360;
2102-
2103- Q_ASSERT(degrees_rotation == 0 || degrees_rotation == 90 ||
2104- degrees_rotation == 180 || degrees_rotation == 270);
2105- return degrees_rotation;
2106-}
2107
2108=== removed file 'modules/Ubuntu/Components/Extras/plugin/photoeditor/orientation.h'
2109--- modules/Ubuntu/Components/Extras/plugin/photoeditor/orientation.h 2014-11-19 10:14:20 +0000
2110+++ modules/Ubuntu/Components/Extras/plugin/photoeditor/orientation.h 1970-01-01 00:00:00 +0000
2111@@ -1,64 +0,0 @@
2112-/*
2113- * Copyright (C) 2011 Canonical Ltd
2114- *
2115- * This program is free software: you can redistribute it and/or modify
2116- * it under the terms of the GNU General Public License version 3 as
2117- * published by the Free Software Foundation.
2118- *
2119- * This program is distributed in the hope that it will be useful,
2120- * but WITHOUT ANY WARRANTY; without even the implied warranty of
2121- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2122- * GNU General Public License for more details.
2123- *
2124- * You should have received a copy of the GNU General Public License
2125- * along with this program. If not, see <http://www.gnu.org/licenses/>.
2126- *
2127- * Authors:
2128- * Lucas Beeler <lucas@yorba.org>
2129- */
2130-
2131-#ifndef GALLERY_ORIENTATION_H_
2132-#define GALLERY_ORIENTATION_H_
2133-
2134-#include <QTransform>
2135-
2136-enum Orientation {
2137- ORIGINAL_ORIENTATION = 0,
2138- MIN_ORIENTATION = 1,
2139- TOP_LEFT_ORIGIN = 1,
2140- TOP_RIGHT_ORIGIN = 2,
2141- BOTTOM_RIGHT_ORIGIN = 3,
2142- BOTTOM_LEFT_ORIGIN = 4,
2143- LEFT_TOP_ORIGIN = 5,
2144- RIGHT_TOP_ORIGIN = 6,
2145- RIGHT_BOTTOM_ORIGIN = 7,
2146- LEFT_BOTTOM_ORIGIN = 8,
2147- MAX_ORIENTATION = 8
2148-};
2149-
2150-/*!
2151- * \brief The OrientationCorrection struct
2152- */
2153-class OrientationCorrection
2154-{
2155-public:
2156- static OrientationCorrection fromOrientation(Orientation o);
2157- static OrientationCorrection identity();
2158- static Orientation rotateOrientation(Orientation orientation, bool left);
2159-
2160- QTransform toTransform() const;
2161-
2162- bool isFlippedFrom(const OrientationCorrection& other) const;
2163- int getNormalizedRotationDifference(const OrientationCorrection& other) const;
2164-
2165-private:
2166- OrientationCorrection(double rotation_angle, double horizontal_scale_factor)
2167- : m_rotationAngle(rotation_angle),
2168- m_horizontalScaleFactor(horizontal_scale_factor) { }
2169-
2170- const double m_rotationAngle;
2171- const double m_horizontalScaleFactor;
2172-};
2173-
2174-
2175-#endif // GALLERY_ORIENTATION_H_
2176
2177=== removed file 'modules/Ubuntu/Components/Extras/plugin/photoeditor/photo-data.cpp'
2178--- modules/Ubuntu/Components/Extras/plugin/photoeditor/photo-data.cpp 2015-03-02 20:25:09 +0000
2179+++ modules/Ubuntu/Components/Extras/plugin/photoeditor/photo-data.cpp 1970-01-01 00:00:00 +0000
2180@@ -1,275 +0,0 @@
2181-/*
2182- * Copyright (C) 2011-2014 Canonical Ltd
2183- *
2184- * This program is free software: you can redistribute it and/or modify
2185- * it under the terms of the GNU General Public License version 3 as
2186- * published by the Free Software Foundation.
2187- *
2188- * This program is distributed in the hope that it will be useful,
2189- * but WITHOUT ANY WARRANTY; without even the implied warranty of
2190- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2191- * GNU General Public License for more details.
2192- *
2193- * You should have received a copy of the GNU General Public License
2194- * along with this program. If not, see <http://www.gnu.org/licenses/>.
2195- *
2196- * Authors:
2197- * Jim Nelson <jim@yorba.org>
2198- * Lucas Beeler <lucas@yorba.org>
2199- * Charles Lindsay <chaz@yorba.org>
2200- * Eric Gregory <eric@yorba.org>
2201- * Clint Rogers <clinton@yorba.org>
2202- * Ugo Riboni <ugo.riboni@canonical.com>
2203- */
2204-
2205-#include "photo-data.h"
2206-#include "photo-edit-command.h"
2207-#include "photo-edit-thread.h"
2208-
2209-// medialoader
2210-#include "photo-metadata.h"
2211-
2212-// util
2213-#include "imaging.h"
2214-
2215-#include <QApplication>
2216-#include <QDebug>
2217-#include <QDir>
2218-#include <QFileInfo>
2219-#include <QImage>
2220-#include <QImageReader>
2221-#include <QImageWriter>
2222-#include <QStack>
2223-#include <QStandardPaths>
2224-
2225-/*!
2226- * \brief Photo::isValid
2227- * \param file
2228- * \return
2229- */
2230-bool PhotoData::isValid(const QFileInfo& file)
2231-{
2232- QImageReader reader(file.filePath());
2233- QByteArray format = reader.format();
2234-
2235- if (QString(format).toLower() == "tiff") {
2236- // QImageReader.canRead() will detect some raw files as readable TIFFs,
2237- // though QImage will fail to load them.
2238- QString extension = file.suffix().toLower();
2239- if (extension != "tiff" && extension != "tif")
2240- return false;
2241- }
2242-
2243- PhotoMetadata* tmp = PhotoMetadata::fromFile(file);
2244- if (tmp == NULL)
2245- return false;
2246-
2247- delete tmp;
2248- return reader.canRead() &&
2249- QImageWriter::supportedImageFormats().contains(reader.format());
2250-}
2251-
2252-/*!
2253- * \brief Photo::Photo
2254- * \param file
2255- */
2256-PhotoData::PhotoData()
2257- : QObject(),
2258- m_editThread(0),
2259- m_busy(false),
2260- m_orientation(TOP_LEFT_ORIGIN)
2261-{
2262-}
2263-
2264-void PhotoData::setPath(QString path)
2265-{
2266- if (QFileInfo(path).absoluteFilePath() != m_file.absoluteFilePath()) {
2267- QFileInfo newFile(path);
2268- if (newFile.exists() && newFile.isFile()) {
2269- QByteArray format = QImageReader(newFile.absoluteFilePath()).format();
2270- m_fileFormat = QString(format).toLower();
2271- if (m_fileFormat == "jpg") // Why does Qt expose two different names here?
2272- m_fileFormat = "jpeg";
2273-
2274- m_file = newFile;
2275- Q_EMIT pathChanged();
2276-
2277- if (fileFormatHasMetadata()) {
2278- PhotoMetadata* metadata = PhotoMetadata::fromFile(newFile.absoluteFilePath());
2279- m_orientation = metadata->orientation();
2280- delete metadata;
2281- Q_EMIT orientationChanged();
2282- }
2283- }
2284- }
2285-}
2286-
2287-QString PhotoData::path() const
2288-{
2289- return m_file.absoluteFilePath();
2290-}
2291-
2292-QFileInfo PhotoData::file() const
2293-{
2294- return m_file;
2295-}
2296-
2297-/*!
2298- * \brief Photo::~Photo
2299- */
2300-PhotoData::~PhotoData()
2301-{
2302- if (m_editThread) {
2303- m_editThread->wait();
2304- finishEditing();
2305- }
2306-}
2307-
2308-/*!
2309- * \brief Photo::orientation
2310- * \return
2311- */
2312-Orientation PhotoData::orientation() const
2313-{
2314- return m_orientation;
2315-}
2316-
2317-void PhotoData::refreshFromDisk()
2318-{
2319- if (fileFormatHasMetadata()) {
2320- PhotoMetadata* metadata = PhotoMetadata::fromFile(m_file.absoluteFilePath());
2321- qDebug() << "Refreshing orient." << m_orientation << "to" << metadata->orientation();
2322- m_orientation = metadata->orientation();
2323- delete metadata;
2324- Q_EMIT orientationChanged();
2325- }
2326-
2327- Q_EMIT dataChanged();
2328-}
2329-
2330-/*!
2331- * \brief Photo::rotateRight
2332- */
2333-void PhotoData::rotateRight()
2334-{
2335- Orientation current = fileFormatHasOrientation() ? orientation() :
2336- TOP_LEFT_ORIGIN;
2337- Orientation rotated = OrientationCorrection::rotateOrientation(current,
2338- false);
2339- qDebug() << " Rotate from orientation " << current << "to" << rotated;
2340-
2341- PhotoEditCommand command;
2342- command.type = EDIT_ROTATE;
2343- command.orientation = rotated;
2344- asyncEdit(command);
2345-}
2346-
2347-/*!
2348- * \brief Photo::autoEnhance
2349- */
2350-void PhotoData::autoEnhance()
2351-{
2352- PhotoEditCommand command;
2353- command.type = EDIT_ENHANCE;
2354- asyncEdit(command);
2355-}
2356-
2357-/*!
2358- * \brief Photo::exposureCompensation Changes the brightnes of the image
2359- * \param value Value for the compensation. -1.0 moves the image into total black.
2360- * +1.0 to total white. 0.0 leaves it as it is.
2361- */
2362-void PhotoData::exposureCompensation(qreal value)
2363-{
2364- PhotoEditCommand command;
2365- command.type = EDIT_COMPENSATE_EXPOSURE;
2366- command.exposureCompensation = value;
2367- asyncEdit(command);
2368-}
2369-
2370-/*!
2371- * \brief Photo::crop
2372- * Specify all coords in [0.0, 1.0], where 1.0 is the full size of the image.
2373- * They will be clamped to this range if you don't.
2374- * \param vrect the rectangle specifying the region to be cropped
2375- */
2376-void PhotoData::crop(QVariant vrect)
2377-{
2378- PhotoEditCommand command;
2379- command.type = EDIT_CROP;
2380- command.crop_rectangle = vrect.toRectF();
2381- asyncEdit(command);
2382-}
2383-
2384-/*!
2385- * \brief Photo::asyncEdit does edit the photo according to the given command
2386- * in a background thread.
2387- * \param The command defining the edit operation to perform.
2388- */
2389-void PhotoData::asyncEdit(const PhotoEditCommand& command)
2390-{
2391- if (m_busy) {
2392- qWarning() << "Can't start edit operation while another one is running.";
2393- return;
2394- }
2395- m_busy = true;
2396- Q_EMIT busyChanged();
2397- m_editThread = new PhotoEditThread(this, command);
2398- connect(m_editThread, SIGNAL(finished()), this, SLOT(finishEditing()));
2399- m_editThread->start();
2400-}
2401-
2402-/*!
2403- * \brief Photo::finishEditing do all the updates once the editing is done
2404- */
2405-void PhotoData::finishEditing()
2406-{
2407- if (!m_editThread || m_editThread->isRunning())
2408- return;
2409-
2410- m_editThread->deleteLater();
2411- m_editThread = 0;
2412- m_busy = false;
2413-
2414- refreshFromDisk();
2415-
2416- Q_EMIT busyChanged();
2417- Q_EMIT editFinished();
2418-}
2419-
2420-/*!
2421- * \brief Photo::fileFormat returns the file format as QString
2422- * \return
2423- */
2424-const QString &PhotoData::fileFormat() const
2425-{
2426- return m_fileFormat;
2427-}
2428-
2429-/*!
2430- * \brief Photo::fileFormatHasMetadata
2431- * \return
2432- */
2433-bool PhotoData::fileFormatHasMetadata() const
2434-{
2435- return (m_fileFormat == "jpeg" || m_fileFormat == "tiff" ||
2436- m_fileFormat == "png");
2437-}
2438-
2439-/*!
2440- * \brief Photo::fileFormatHasOrientation
2441- * \return
2442- */
2443-bool PhotoData::fileFormatHasOrientation() const
2444-{
2445- return (m_fileFormat == "jpeg");
2446-}
2447-
2448-/*!
2449- * \brief Photo::busy return true if there is an editing operation in progress
2450- * \return
2451- */
2452-bool PhotoData::busy() const
2453-{
2454- return m_busy;
2455-}
2456
2457=== removed file 'modules/Ubuntu/Components/Extras/plugin/photoeditor/photo-data.h'
2458--- modules/Ubuntu/Components/Extras/plugin/photoeditor/photo-data.h 2015-03-02 20:25:09 +0000
2459+++ modules/Ubuntu/Components/Extras/plugin/photoeditor/photo-data.h 1970-01-01 00:00:00 +0000
2460@@ -1,92 +0,0 @@
2461-/*
2462- * Copyright (C) 2011-2014 Canonical Ltd
2463- *
2464- * This program is free software: you can redistribute it and/or modify
2465- * it under the terms of the GNU General Public License version 3 as
2466- * published by the Free Software Foundation.
2467- *
2468- * This program is distributed in the hope that it will be useful,
2469- * but WITHOUT ANY WARRANTY; without even the implied warranty of
2470- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2471- * GNU General Public License for more details.
2472- *
2473- * You should have received a copy of the GNU General Public License
2474- * along with this program. If not, see <http://www.gnu.org/licenses/>.
2475- *
2476- * Authors:
2477- * Jim Nelson <jim@yorba.org>
2478- * Lucas Beeler <lucas@yorba.org>
2479- * Charles Lindsay <chaz@yorba.org>
2480- * Ugo Riboni <ugo.riboni@canonical.com>
2481- */
2482-
2483-#ifndef PHOTO_DATA_H_
2484-#define PHOTO_DATA_H_
2485-
2486-// util
2487-#include "orientation.h"
2488-
2489-// QT
2490-#include <QFileInfo>
2491-#include <QVariant>
2492-
2493-class PhotoEditCommand;
2494-class PhotoEditThread;
2495-
2496-/*!
2497- * \brief The Photo class
2498- */
2499-class PhotoData : public QObject
2500-{
2501- Q_OBJECT
2502-
2503- Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
2504- Q_PROPERTY(int orientation READ orientation NOTIFY orientationChanged)
2505- Q_PROPERTY(bool busy READ busy NOTIFY busyChanged)
2506-
2507-public:
2508- explicit PhotoData();
2509- virtual ~PhotoData();
2510-
2511- static bool isValid(const QFileInfo& file);
2512-
2513- QString path() const;
2514- void setPath(QString path);
2515- QFileInfo file() const;
2516- bool busy() const;
2517-
2518- virtual Orientation orientation() const;
2519-
2520- Q_INVOKABLE void refreshFromDisk();
2521- Q_INVOKABLE void rotateRight();
2522- Q_INVOKABLE void autoEnhance();
2523- Q_INVOKABLE void exposureCompensation(qreal value);
2524- Q_INVOKABLE void crop(QVariant vrect);
2525-
2526- const QString &fileFormat() const;
2527- bool fileFormatHasMetadata() const;
2528- bool fileFormatHasOrientation() const;
2529-
2530-Q_SIGNALS:
2531- void pathChanged();
2532- void orientationChanged();
2533- void busyChanged();
2534-
2535- void editFinished();
2536- void dataChanged();
2537-
2538-private Q_SLOTS:
2539- void finishEditing();
2540-
2541-private:
2542- void asyncEdit(const PhotoEditCommand& state);
2543-
2544- QString m_fileFormat;
2545- PhotoEditThread *m_editThread;
2546- QFileInfo m_file;
2547- bool m_busy;
2548-
2549- Orientation m_orientation;
2550-};
2551-
2552-#endif // PHOTO_DATA_H_
2553
2554=== removed file 'modules/Ubuntu/Components/Extras/plugin/photoeditor/photo-edit-thread.cpp'
2555--- modules/Ubuntu/Components/Extras/plugin/photoeditor/photo-edit-thread.cpp 2015-01-28 12:11:56 +0000
2556+++ modules/Ubuntu/Components/Extras/plugin/photoeditor/photo-edit-thread.cpp 1970-01-01 00:00:00 +0000
2557@@ -1,185 +0,0 @@
2558-/*
2559- * Copyright (C) 2013-2014 Canonical Ltd
2560- *
2561- * This program is free software: you can redistribute it and/or modify
2562- * it under the terms of the GNU General Public License version 3 as
2563- * published by the Free Software Foundation.
2564- *
2565- * This program is distributed in the hope that it will be useful,
2566- * but WITHOUT ANY WARRANTY; without even the implied warranty of
2567- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2568- * GNU General Public License for more details.
2569- *
2570- * You should have received a copy of the GNU General Public License
2571- * along with this program. If not, see <http://www.gnu.org/licenses/>.
2572- */
2573-
2574-#include "photo-edit-thread.h"
2575-#include "photo-data.h"
2576-
2577-// medialoader
2578-#include "photo-metadata.h"
2579-
2580-// util
2581-#include "imaging.h"
2582-
2583-#include <QDebug>
2584-
2585-/*!
2586- * \brief PhotoEditThread::PhotoEditThread
2587- */
2588-PhotoEditThread::PhotoEditThread(PhotoData *photo, const PhotoEditCommand &command)
2589- : QThread(),
2590- m_photo(photo),
2591- m_command(command)
2592-{
2593-}
2594-
2595-/*!
2596- * \brief PhotoEditThread::command resturns the editing command used for this processing
2597- * \return
2598- */
2599-const PhotoEditCommand &PhotoEditThread::command() const
2600-{
2601- return m_command;
2602-}
2603-
2604-/*!
2605- * \brief PhotoEditThread::run \reimp
2606- */
2607-void PhotoEditThread::run()
2608-{
2609- // The only operation in which we don't have to work on the actual image
2610- // pixels is image rotation in the case where we can simply change the
2611- // metadata rotation field.
2612- if (m_command.type == EDIT_ROTATE && m_photo->fileFormatHasOrientation()) {
2613- handleSimpleMetadataRotation(m_command);
2614- return;
2615- }
2616-
2617- // In all other cases we load the image, do the work, and save it back.
2618- QImage image(m_photo->file().filePath(), m_photo->fileFormat().toStdString().c_str());
2619- if (image.isNull()) {
2620- qWarning() << "Error loading" << m_photo->file().filePath() << "for editing";
2621- return;
2622- }
2623-
2624- // Copy all metadata from the original image so that we can save it to the
2625- // new one after modifying the pixels.
2626- PhotoMetadata* original = PhotoMetadata::fromFile(m_photo->file());
2627-
2628- // If the photo was previously rotated through metadata and we are editing
2629- // the actual pixels, first rotate the image to match the orientation so
2630- // that the correct pixels are edited.
2631- // Obviously don't do this in the case we have been asked to do a rotation
2632- // operation on the pixels, as we would do it later as the operation itself.
2633- if (m_photo->fileFormatHasOrientation() && m_command.type != EDIT_ROTATE) {
2634- Orientation orientation = m_photo->orientation();
2635- QTransform transform = OrientationCorrection::fromOrientation(orientation).toTransform();
2636- image = image.transformed(transform);
2637- }
2638-
2639- if (m_command.type == EDIT_ROTATE) {
2640- QTransform transform = OrientationCorrection::fromOrientation(m_command.orientation).toTransform();
2641- image = image.transformed(transform);
2642- } else if (m_command.type == EDIT_CROP) {
2643- QRect rect;
2644- rect.setX(qBound(0.0, m_command.crop_rectangle.x(), 1.0) * image.width());
2645- rect.setY(qBound(0.0, m_command.crop_rectangle.y(), 1.0) * image.height());
2646- rect.setWidth(qBound(0.0, m_command.crop_rectangle.width(), 1.0) * image.width());
2647- rect.setHeight(qBound(0.0, m_command.crop_rectangle.height(), 1.0) * image.height());
2648-
2649- image = image.copy(rect);
2650- } else if (m_command.type == EDIT_ENHANCE) {
2651- image = enhanceImage(image);
2652- } else if (m_command.type == EDIT_COMPENSATE_EXPOSURE) {
2653- image = compensateExposure(image, m_command.exposureCompensation);
2654- } else {
2655- qWarning() << "Edit thread running with unknown or no operation.";
2656- return;
2657- }
2658-
2659- bool saved = image.save(m_photo->file().filePath(),
2660- m_photo->fileFormat().toStdString().c_str(), 90);
2661- if (!saved)
2662- qWarning() << "Error saving edited" << m_photo->file().filePath();
2663-
2664- PhotoMetadata* copy = PhotoMetadata::fromFile(m_photo->file());
2665- original->copyTo(copy);
2666- copy->setOrientation(TOP_LEFT_ORIGIN); // reset previous orientation
2667- copy->updateThumbnail(image);
2668- copy->save();
2669-
2670- delete original;
2671- delete copy;
2672-}
2673-
2674-/*!
2675- * \brief PhotoEditThread::handleSimpleMetadataRotation
2676- * Handler for the case of an image whose only change is to its
2677- * orientation; used to skip re-encoding of JPEGs.
2678- * \param state
2679- */
2680-void PhotoEditThread::handleSimpleMetadataRotation(const PhotoEditCommand& state)
2681-{
2682- PhotoMetadata* metadata = PhotoMetadata::fromFile(m_photo->file());
2683- metadata->setOrientation(state.orientation);
2684- metadata->save();
2685- delete(metadata);
2686-}
2687-
2688-/*!
2689- * \brief PhotoEditThread::enhanceImage
2690- */
2691-QImage PhotoEditThread::enhanceImage(const QImage& image)
2692-{
2693- int width = image.width();
2694- int height = image.height();
2695-
2696- QImage sample_img = (image.width() > 400) ? image.scaledToWidth(400) : image;
2697-
2698- AutoEnhanceTransformation enhance = AutoEnhanceTransformation(sample_img);
2699-
2700- QImage::Format dest_format = image.format();
2701-
2702- // Can't write into indexed images, due to a limitation in Qt.
2703- if (dest_format == QImage::Format_Indexed8)
2704- dest_format = QImage::Format_RGB32;
2705-
2706- QImage enhanced_image(width, height, dest_format);
2707-
2708- for (int j = 0; j < height; j++) {
2709- for (int i = 0; i < width; i++) {
2710- QColor px = enhance.transformPixel(
2711- QColor(image.pixel(i, j)));
2712- enhanced_image.setPixel(i, j, px.rgb());
2713- }
2714- }
2715-
2716- return enhanced_image;
2717-}
2718-
2719-/*!
2720- * \brief PhotoEditThread::compensateExposure Compensates the exposure
2721- * Compensating the exposure is a change in brightnes
2722- * \param image Image to change the brightnes
2723- * \param compansation -1.0 is total dark, +1.0 is total bright
2724- * \return The image with adjusted brightnes
2725- */
2726-QImage PhotoEditThread::compensateExposure(const QImage &image, qreal compensation)
2727-{
2728- int shift = qBound(-255, (int)(255*compensation), 255);
2729- QImage result(image.width(), image.height(), image.format());
2730-
2731- for (int j = 0; j < image.height(); j++) {
2732- for (int i = 0; i <image.width(); i++) {
2733- QColor px = image.pixel(i, j);
2734- int red = qBound(0, px.red() + shift, 255);
2735- int green = qBound(0, px.green() + shift, 255);
2736- int blue = qBound(0, px.blue() + shift, 255);
2737- result.setPixel(i, j, qRgb(red, green, blue));
2738- }
2739- }
2740-
2741- return result;
2742-}
2743
2744=== removed file 'modules/Ubuntu/Components/Extras/plugin/photoeditor/photo-edit-thread.h'
2745--- modules/Ubuntu/Components/Extras/plugin/photoeditor/photo-edit-thread.h 2014-12-02 11:43:13 +0000
2746+++ modules/Ubuntu/Components/Extras/plugin/photoeditor/photo-edit-thread.h 1970-01-01 00:00:00 +0000
2747@@ -1,56 +0,0 @@
2748-/*
2749- * Copyright (C) 2013-2014 Canonical Ltd
2750- *
2751- * This program is free software: you can redistribute it and/or modify
2752- * it under the terms of the GNU General Public License version 3 as
2753- * published by the Free Software Foundation.
2754- *
2755- * This program is distributed in the hope that it will be useful,
2756- * but WITHOUT ANY WARRANTY; without even the implied warranty of
2757- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2758- * GNU General Public License for more details.
2759- *
2760- * You should have received a copy of the GNU General Public License
2761- * along with this program. If not, see <http://www.gnu.org/licenses/>.
2762- */
2763-
2764-#ifndef GALLERY_PHOTO_EDIT_THREAD_H_
2765-#define GALLERY_PHOTO_EDIT_THREAD_H_
2766-
2767-#include "photo-caches.h"
2768-#include "photo-edit-command.h"
2769-
2770-// util
2771-#include "orientation.h"
2772-
2773-#include <QImage>
2774-#include <QThread>
2775-#include <QUrl>
2776-
2777-class PhotoData;
2778-
2779-/*!
2780- * \brief The PhotoEditThread class
2781- */
2782-class PhotoEditThread: public QThread
2783-{
2784- Q_OBJECT
2785-public:
2786- PhotoEditThread(PhotoData *photo, const PhotoEditCommand& command);
2787-
2788- const PhotoEditCommand& command() const;
2789-
2790-protected:
2791- void run() Q_DECL_OVERRIDE;
2792-
2793-private:
2794- QImage enhanceImage(const QImage& image);
2795- QImage compensateExposure(const QImage& image, qreal compansation);
2796- QImage doColorBalance(const QImage& image, qreal brightness, qreal contrast, qreal saturation, qreal hue);
2797- void handleSimpleMetadataRotation(const PhotoEditCommand& state);
2798-
2799- PhotoData *m_photo;
2800- PhotoEditCommand m_command;
2801-};
2802-
2803-#endif
2804
2805=== modified file 'modules/Ubuntu/Components/Extras/plugin/photoeditor/photo-metadata.cpp'
2806--- modules/Ubuntu/Components/Extras/plugin/photoeditor/photo-metadata.cpp 2015-06-12 13:08:47 +0000
2807+++ modules/Ubuntu/Components/Extras/plugin/photoeditor/photo-metadata.cpp 2015-06-12 13:08:47 +0000
2808@@ -1,5 +1,5 @@
2809 /*
2810- * Copyright (C) 2011 Canonical Ltd
2811+ * Copyright (C) 2015 Canonical Ltd
2812 *
2813 * This program is free software: you can redistribute it and/or modify
2814 * it under the terms of the GNU General Public License version 3 as
2815@@ -13,269 +13,106 @@
2816 * You should have received a copy of the GNU General Public License
2817 * along with this program. If not, see <http://www.gnu.org/licenses/>.
2818 *
2819- * Authors:
2820- * Lucas Beeler <lucas@yorba.org>
2821 */
2822
2823 #include "photo-metadata.h"
2824
2825-#include <cstdio>
2826 #include <QBuffer>
2827+#include <QtConcurrent/QtConcurrent>
2828
2829 namespace {
2830-const Orientation DEFAULT_ORIENTATION = TOP_LEFT_ORIGIN;
2831 const char* EXIF_ORIENTATION_KEY = "Exif.Image.Orientation";
2832-const char* EXIF_DATETIMEDIGITIZED_KEY = "Exif.Photo.DateTimeDigitized";
2833-const char* EXPOSURE_TIME_KEYS[] = {
2834- "Exif.Photo.DateTimeOriginal",
2835- "Xmp.exif.DateTimeOriginal",
2836- "Xmp.xmp.CreateDate",
2837- "Exif.Photo.DateTimeDigitized",
2838- "Xmp.exif.DateTimeDigitized",
2839- "Exif.Image.DateTime"
2840-};
2841-const size_t NUM_EXPOSURE_TIME_KEYS = 6;
2842-const char* EXIF_DATE_FORMATS[] = {
2843- "%d:%d:%d %d:%d:%d",
2844-
2845- // for Minolta DiMAGE E223 (colon, instead of space, separates day from
2846- // hour in exif)
2847- "%d:%d:%d:%d:%d:%d",
2848-
2849- // for Samsung NV10 (which uses a period instead of colons for the date
2850- // and two spaces between date and time)
2851- "%d.%d.%d %d:%d:%d"
2852-};
2853-const size_t NUM_EXIF_DATE_FORMATS = 3;
2854-const float THUMBNAIL_SCALE = 8.5;
2855-
2856-const char* get_first_matched(const char* keys[], size_t n_keys,
2857- const QSet<QString>& in) {
2858- for (size_t i = 0; i < n_keys; i++) {
2859- if (in.contains(keys[i]))
2860- return keys[i];
2861- }
2862-
2863- return NULL;
2864-}
2865-
2866-bool is_xmp_key(const char* key) {
2867- return (key != NULL) ? (std::strncmp("Xmp.", key, 4) == 0) : false;
2868-}
2869-
2870-bool is_exif_key(const char* key) {
2871- return (key != NULL) ? (std::strncmp("Exif.", key, 5) == 0) : false;
2872-}
2873-
2874-// caller should test if 's' could be successfully parsed by invoking the
2875-// isValid() method on the returned QDateTime instance; if isValid() == false,
2876-// 's' couldn't be parsed
2877-QDateTime parse_xmp_date_string(const char* s) {
2878- return QDateTime::fromString(s, Qt::ISODate);
2879-}
2880-
2881-// caller should test if 's' could be successfully parsed by invoking the
2882-// isValid() method on the returned QDateTime instance; if isValid() == false,
2883-// 's' couldn't be parsed
2884-QDateTime parse_exif_date_string(const char* s) {
2885- for (size_t i = 0; i < NUM_EXIF_DATE_FORMATS; i++) {
2886- int year, month, day, hour, minute, second;
2887- if (std::sscanf(s, EXIF_DATE_FORMATS[i], &year, &month, &day, &hour,
2888- &minute, &second) == 6) {
2889- // no need to check year, month, day, hour, minute and second variables
2890- // for bogus values before using them -- if the values are bogus, the
2891- // resulting QDateTime will be invalid, which is exactly what we want
2892- return QDateTime(QDate(year, month, day), QTime(hour, minute, second));
2893- }
2894- }
2895-
2896- // the no argument QDateTime constructor produces an invalid QDateTime,
2897- // which is what we want
2898- return QDateTime();
2899-}
2900-} // namespace
2901-
2902-/*!
2903- * \brief PhotoMetadata::PhotoMetadata
2904- * \param filepath
2905- */
2906-PhotoMetadata::PhotoMetadata(const char* filepath)
2907- : m_fileSourceInfo(filepath)
2908-{
2909- m_image = Exiv2::ImageFactory::open(filepath);
2910- m_image->readMetadata();
2911-}
2912-
2913-/*!
2914- * \brief PhotoMetadata::fromFile
2915- * \param filepath
2916- * \return
2917- */
2918-PhotoMetadata* PhotoMetadata::fromFile(const char* filepath)
2919-{
2920- PhotoMetadata* result = NULL;
2921- try {
2922- result = new PhotoMetadata(filepath);
2923-
2924- if (!result->m_image->good()) {
2925- qDebug("Invalid image metadata in %s", filepath);
2926- delete result;
2927- return NULL;
2928- }
2929-
2930- Exiv2::ExifData& exif_data = result->m_image->exifData();
2931- Exiv2::ExifData::const_iterator end = exif_data.end();
2932- for (Exiv2::ExifData::const_iterator i = exif_data.begin(); i != end; i++)
2933- result->m_keysPresent.insert(QString(i->key().c_str()));
2934-
2935- Exiv2::XmpData& xmp_data = result->m_image->xmpData();
2936- Exiv2::XmpData::const_iterator end1 = xmp_data.end();
2937- for (Exiv2::XmpData::const_iterator i = xmp_data.begin(); i != end1; i++)
2938- result->m_keysPresent.insert(QString(i->key().c_str()));
2939-
2940- return result;
2941- } catch (Exiv2::AnyError& e) {
2942- qDebug("Error loading image metadata: %s", e.what());
2943- delete result;
2944- return NULL;
2945- }
2946-}
2947-
2948-/*!
2949- * \brief PhotoMetadata::fromFile
2950- * \param file
2951- * \return
2952- */
2953-PhotoMetadata* PhotoMetadata::fromFile(const QFileInfo &file)
2954-{
2955- return PhotoMetadata::fromFile(file.absoluteFilePath().toStdString().c_str());
2956-}
2957-
2958-/*!
2959- * \brief PhotoMetadata::orientation
2960- * \return
2961- */
2962-Orientation PhotoMetadata::orientation() const
2963+
2964+} // anonymous namespace
2965+
2966+namespace PhotoEditor {
2967+
2968+PhotoMetadata::PhotoMetadata()
2969+{
2970+ connect(&m_saveWatcher, SIGNAL(finished()), this, SIGNAL(savingChanged()));
2971+}
2972+
2973+QString PhotoMetadata::fileName() const
2974+{
2975+ return m_fileName;
2976+}
2977+
2978+void PhotoMetadata::setFileName(QString fileName)
2979+{
2980+ if (fileName != m_fileName) {
2981+ m_fileName = fileName;
2982+ try {
2983+ m_image = Exiv2::ImageFactory::open(fileName.toStdString());
2984+ if (!m_image->good()) {
2985+ qDebug("Invalid image metadata in %s", fileName.toLocal8Bit().constData());
2986+ }
2987+ m_image->readMetadata();
2988+ } catch (Exiv2::AnyError& e) {
2989+ qDebug("Error loading image metadata: %s", e.what());
2990+ }
2991+ Q_EMIT fileNameChanged();
2992+ }
2993+}
2994+
2995+int PhotoMetadata::orientation() const
2996 {
2997 Exiv2::ExifData& exif_data = m_image->exifData();
2998
2999 if (exif_data.empty())
3000- return DEFAULT_ORIENTATION;
3001-
3002- if (m_keysPresent.find(EXIF_ORIENTATION_KEY) == m_keysPresent.end())
3003- return DEFAULT_ORIENTATION;
3004+ return TOP_LEFT_ORIGIN;
3005
3006 long orientation_code = exif_data[EXIF_ORIENTATION_KEY].toLong();
3007 if (orientation_code < MIN_ORIENTATION || orientation_code > MAX_ORIENTATION)
3008- return DEFAULT_ORIENTATION;
3009+ return TOP_LEFT_ORIGIN;
3010
3011 return static_cast<Orientation>(orientation_code);
3012 }
3013
3014-/*!
3015- * \brief PhotoMetadata::exposureTime
3016- * \return
3017- */
3018-QDateTime PhotoMetadata::exposureTime() const
3019-{
3020- const char* matched = get_first_matched(EXPOSURE_TIME_KEYS,
3021- NUM_EXPOSURE_TIME_KEYS, m_keysPresent);
3022- if (matched == NULL)
3023- return QDateTime();
3024-
3025- if (is_exif_key(matched))
3026- return parse_exif_date_string(m_image->exifData()[matched].toString().c_str());
3027-
3028- if (is_xmp_key(matched))
3029- return parse_xmp_date_string(m_image->xmpData()[matched].toString().c_str());
3030-
3031- // No valid/known tag for exposure date/time
3032- return QDateTime();
3033-}
3034-
3035-/*!
3036- * \brief PhotoMetadata::orientationCorrection
3037- * \return
3038- */
3039-OrientationCorrection PhotoMetadata::orientationCorrection() const
3040-{
3041- return OrientationCorrection::fromOrientation(orientation());
3042-}
3043-
3044-/*!
3045- * \brief PhotoMetadata::orientationTransform
3046- * \return
3047- */
3048-QTransform PhotoMetadata::orientationTransform() const
3049-{
3050- return orientationCorrection().toTransform();
3051-}
3052-
3053-/*!
3054- * \brief PhotoMetadata::setOrientation
3055- * \param orientation
3056- */
3057-void PhotoMetadata::setOrientation(Orientation orientation)
3058+void PhotoMetadata::setOrientation(int orientation)
3059 {
3060 Exiv2::ExifData& exif_data = m_image->exifData();
3061-
3062 exif_data[EXIF_ORIENTATION_KEY] = (Exiv2::UShortValue)orientation;
3063-
3064- if (!m_keysPresent.contains(EXIF_ORIENTATION_KEY))
3065- m_keysPresent.insert(EXIF_ORIENTATION_KEY);
3066-}
3067-
3068-/*!
3069- * \brief PhotoMetadata::setDateTimeDigitized
3070- * \param digitized
3071- */
3072-void PhotoMetadata::setDateTimeDigitized(const QDateTime& digitized)
3073-{
3074- try {
3075- if (!m_image->good()) {
3076- qDebug("Do not set DateTimeDigitized, invalid image metadata.");
3077- return;
3078- }
3079-
3080- Exiv2::ExifData& exif_data = m_image->exifData();
3081-
3082- exif_data[EXIF_DATETIMEDIGITIZED_KEY] = digitized.toString("yyyy:MM:dd hh:mm:ss").toStdString();
3083-
3084- if (!m_keysPresent.contains(EXIF_DATETIMEDIGITIZED_KEY))
3085- m_keysPresent.insert(EXIF_DATETIMEDIGITIZED_KEY);
3086-
3087- } catch (Exiv2::AnyError& e) {
3088- qDebug("Do not set DateTimeDigitized, error reading image metadata; %s", e.what());
3089- return;
3090- }
3091-}
3092-
3093-/*!
3094- * \brief PhotoMetadata::save
3095- * \return
3096- */
3097-bool PhotoMetadata::save() const
3098-{
3099- try {
3100- m_image->writeMetadata();
3101- return true;
3102- } catch (Exiv2::AnyError& e) {
3103- return false;
3104- }
3105-}
3106-
3107-void PhotoMetadata::copyTo(PhotoMetadata *other) const
3108-{
3109- other->m_image->setMetadata(*m_image);
3110+}
3111+
3112+bool PhotoMetadata::saving() const
3113+{
3114+ return m_saveWatcher.isRunning();
3115 }
3116
3117 void PhotoMetadata::updateThumbnail(QImage image)
3118 {
3119- QImage scaled = image.scaled(image.width() / THUMBNAIL_SCALE,
3120- image.height() / THUMBNAIL_SCALE);
3121+ QImage scaled = image.scaled(256, 256, Qt::KeepAspectRatio);
3122 QBuffer jpeg;
3123 jpeg.open(QIODevice::WriteOnly);
3124 scaled.save(&jpeg, "jpeg");
3125 Exiv2::ExifThumb thumb(m_image->exifData());
3126 thumb.setJpegThumbnail((Exiv2::byte*) jpeg.data().constData(), jpeg.size());
3127 }
3128+
3129+void PhotoMetadata::saveImage(QImage image)
3130+{
3131+ QFuture<void> future = QtConcurrent::run(this, &PhotoMetadata::doSaveImage, image);
3132+ m_saveWatcher.setFuture(future);
3133+ Q_EMIT savingChanged();
3134+}
3135+
3136+void PhotoMetadata::save()
3137+{
3138+ QFuture<void> future = QtConcurrent::run(this, &PhotoMetadata::doSave);
3139+ m_saveWatcher.setFuture(future);
3140+ Q_EMIT savingChanged();
3141+}
3142+
3143+void PhotoMetadata::doSaveImage(QImage image)
3144+{
3145+ image.save(m_fileName);
3146+ updateThumbnail(image);
3147+ m_image->writeMetadata();
3148+}
3149+
3150+
3151+void PhotoMetadata::doSave()
3152+{
3153+ m_image->writeMetadata();
3154+}
3155+}
3156
3157=== modified file 'modules/Ubuntu/Components/Extras/plugin/photoeditor/photo-metadata.h'
3158--- modules/Ubuntu/Components/Extras/plugin/photoeditor/photo-metadata.h 2015-03-02 20:25:09 +0000
3159+++ modules/Ubuntu/Components/Extras/plugin/photoeditor/photo-metadata.h 2015-06-12 13:08:47 +0000
3160@@ -1,5 +1,5 @@
3161 /*
3162- * Copyright (C) 2011 Canonical Ltd
3163+ * Copyright (C) 2015 Canonical Ltd
3164 *
3165 * This program is free software: you can redistribute it and/or modify
3166 * it under the terms of the GNU General Public License version 3 as
3167@@ -13,55 +13,68 @@
3168 * You should have received a copy of the GNU General Public License
3169 * along with this program. If not, see <http://www.gnu.org/licenses/>.
3170 *
3171- * Authors:
3172- * Lucas Beeler <lucas@yorba.org>
3173 */
3174
3175-#ifndef GALLERY_PHOTO_METADATA_H_
3176-#define GALLERY_PHOTO_METADATA_H_
3177-
3178-// util
3179-#include "orientation.h"
3180-
3181-#include <QDateTime>
3182-#include <QFileInfo>
3183-#include <QObject>
3184-#include <QString>
3185-#include <QSet>
3186-#include <QTransform>
3187-#include <QImage>
3188+#ifndef PHOTO_METADATA_H
3189+#define PHOTO_METADATA_H
3190+
3191+#include <QtCore/QObject>
3192+#include <QtCore/QString>
3193+#include <QtCore/QFutureWatcher>
3194+#include <QtGui/QImage>
3195
3196 #include <exiv2/exiv2.hpp>
3197
3198-/*!
3199- * \brief The PhotoMetadata class
3200- */
3201+enum Orientation {
3202+ ORIGINAL_ORIENTATION = 0,
3203+ MIN_ORIENTATION = 1,
3204+ TOP_LEFT_ORIGIN = 1,
3205+ TOP_RIGHT_ORIGIN = 2,
3206+ BOTTOM_RIGHT_ORIGIN = 3,
3207+ BOTTOM_LEFT_ORIGIN = 4,
3208+ LEFT_TOP_ORIGIN = 5,
3209+ RIGHT_TOP_ORIGIN = 6,
3210+ RIGHT_BOTTOM_ORIGIN = 7,
3211+ LEFT_BOTTOM_ORIGIN = 8,
3212+ MAX_ORIENTATION = 8
3213+};
3214+namespace PhotoEditor {
3215+
3216 class PhotoMetadata : public QObject
3217 {
3218 Q_OBJECT
3219
3220+ Q_PROPERTY(QString fileName READ fileName WRITE setFileName NOTIFY fileNameChanged)
3221+ Q_PROPERTY(int orientation READ orientation WRITE setOrientation NOTIFY orientationChanged)
3222+ Q_PROPERTY(bool saving READ saving NOTIFY savingChanged)
3223+
3224 public:
3225- static PhotoMetadata* fromFile(const char* filepath);
3226- static PhotoMetadata* fromFile(const QFileInfo& file);
3227-
3228- QDateTime exposureTime() const;
3229- Orientation orientation() const;
3230- QTransform orientationTransform() const;
3231- OrientationCorrection orientationCorrection() const;
3232-
3233- void setOrientation(Orientation orientation);
3234- void setDateTimeDigitized(const QDateTime& digitized);
3235-
3236+ PhotoMetadata();
3237+
3238+ QString fileName() const;
3239+ int orientation() const;
3240+ bool saving() const;
3241+
3242+ void setFileName(QString fileName);
3243+ void setOrientation(int orientation);
3244+
3245+ Q_INVOKABLE void saveImage(QImage image);
3246+ Q_INVOKABLE void save();
3247+
3248+Q_SIGNALS:
3249+ void fileNameChanged();
3250+ void orientationChanged();
3251+ void savingChanged();
3252+
3253+protected:
3254 void updateThumbnail(QImage image);
3255- void copyTo(PhotoMetadata* other) const;
3256- bool save() const;
3257+ void doSaveImage(QImage image);
3258+ void doSave();
3259
3260 private:
3261- PhotoMetadata(const char* filepath);
3262-
3263 Exiv2::Image::AutoPtr m_image;
3264- QSet<QString> m_keysPresent;
3265- QFileInfo m_fileSourceInfo;
3266+ QString m_fileName;
3267+ QFutureWatcher<void> m_saveWatcher;
3268 };
3269-
3270-#endif // GALLERY_PHOTO_METADATA_H_
3271+}
3272+#endif // PHOTO_METADATA_H
3273
3274=== renamed file 'tests/unittests/assets/croptest.png' => 'tests/unittests/assets/savetest.png'
3275=== modified file 'tests/unittests/sampledata.qrc'
3276--- tests/unittests/sampledata.qrc 2014-12-03 12:33:01 +0000
3277+++ tests/unittests/sampledata.qrc 2015-06-12 13:08:47 +0000
3278@@ -5,6 +5,6 @@
3279 <file>assets/windmill_rotated_90.jpg</file>
3280 <file>assets/windmill.jpg</file>
3281 <file>assets/thorns.jpg</file>
3282- <file>assets/croptest.png</file>
3283+ <file>assets/savetest.png</file>
3284 </qresource>
3285 </RCC>
3286
3287=== modified file 'tests/unittests/tst_PhotoEditorPhoto.cpp'
3288--- tests/unittests/tst_PhotoEditorPhoto.cpp 2014-12-03 12:50:39 +0000
3289+++ tests/unittests/tst_PhotoEditorPhoto.cpp 2015-06-12 13:08:47 +0000
3290@@ -14,8 +14,6 @@
3291 * along with this program. If not, see <http://www.gnu.org/licenses/>.
3292 */
3293
3294-#include "photo-data.h"
3295-
3296 #include <QColor>
3297 #include <QDebug>
3298 #include <QDir>
3299@@ -23,6 +21,9 @@
3300 #include <QSignalSpy>
3301 #include <QTemporaryDir>
3302 #include <QTest>
3303+#include "photo-metadata.h"
3304+
3305+using namespace PhotoEditor;
3306
3307 class PhotoEditorPhotoTest: public QObject
3308 {
3309@@ -32,9 +33,8 @@
3310
3311 void testBasicProperties();
3312 void testOrientation();
3313- void testRefresh();
3314- void testRotate();
3315- void testCrop();
3316+ void testSaveImage();
3317+ void testSetOrientationAndSave();
3318
3319 void cleanupTestCase();
3320
3321@@ -65,13 +65,11 @@
3322 QDir source = QDir(m_workingDir.path());
3323 QString path = source.absoluteFilePath("windmill.jpg");
3324
3325- PhotoData photo;
3326- photo.setPath(path);
3327- QVERIFY(photo.path() == path);
3328- QVERIFY(photo.file() == QFileInfo(path));
3329- QVERIFY(photo.fileFormat() == "jpeg");
3330- QVERIFY(photo.fileFormatHasMetadata() == true);
3331- QVERIFY(photo.fileFormatHasOrientation() == true);
3332+ PhotoMetadata photo;
3333+ photo.setFileName(path);
3334+ QVERIFY(photo.fileName() == path);
3335+ QVERIFY(photo.orientation() == TOP_LEFT_ORIGIN);
3336+ QVERIFY(photo.saving() == false);
3337 }
3338
3339 void PhotoEditorPhotoTest::testOrientation()
3340@@ -79,163 +77,68 @@
3341 QDir source = QDir(m_workingDir.path());
3342 QString path = source.absoluteFilePath("windmill.jpg");
3343
3344- PhotoData photo;
3345- photo.setPath(path);
3346+ PhotoMetadata photo;
3347+ photo.setFileName(path);
3348 QVERIFY(photo.orientation() == TOP_LEFT_ORIGIN);
3349
3350- PhotoData photo2;
3351+ PhotoMetadata photo2;
3352 QString path2 = source.absoluteFilePath("windmill_rotated_90.jpg");
3353- photo2.setPath(path2);
3354+ photo2.setFileName(path2);
3355 QVERIFY(photo2.orientation() == RIGHT_TOP_ORIGIN);
3356
3357- // Test updating the original PhotoData by changing path
3358- photo.setPath(path2);
3359+ // Test updating the original PhotoMetadata by changing path
3360+ photo.setFileName(path2);
3361 QVERIFY(photo.orientation() == RIGHT_TOP_ORIGIN);
3362 }
3363
3364-void PhotoEditorPhotoTest::testRefresh()
3365+void PhotoEditorPhotoTest::testSaveImage()
3366 {
3367 // Work on a copy to avoid disturbing other tests
3368 QDir source = QDir(m_workingDir.path());
3369- QString path = source.absoluteFilePath("testrefresh.jpg");
3370- qDebug() << QFile::copy(source.absoluteFilePath("windmill.jpg"), path);
3371+ QString path = source.absoluteFilePath("savetestcopy.png");
3372+ QFile::remove(path);
3373+ QFile::copy(source.absoluteFilePath("savetest.png"), path);
3374
3375 // Load the file.
3376- PhotoData photo;
3377- photo.setPath(path);
3378- Orientation orientation = photo.orientation();
3379-
3380- // Now overwrite it with another file with different rotation and verify
3381- // the new data is correct
3382- QFile::remove(path);
3383- QFile::copy(source.absoluteFilePath("windmill_rotated_90.jpg"), path);
3384-
3385- photo.refreshFromDisk();
3386- QVERIFY(orientation != photo.orientation());
3387+ PhotoMetadata photo;
3388+ photo.setFileName(path);
3389+
3390+ // Now overwrite it with another QImage and verify the new data is correct
3391+ QImage otherImage(source.absoluteFilePath("windmill.jpg"));
3392+
3393+ QVERIFY(photo.saving() == false);
3394+ photo.saveImage(otherImage);
3395+ QVERIFY(photo.saving() == true);
3396+ QTRY_VERIFY(photo.saving() == false);
3397+
3398+ QImage result(path);
3399+ QCOMPARE(result, otherImage);
3400 }
3401
3402-void PhotoEditorPhotoTest::testRotate()
3403+void PhotoEditorPhotoTest::testSetOrientationAndSave()
3404 {
3405 // Work on a copy to avoid disturbing other tests
3406 QDir source = QDir(m_workingDir.path());
3407- QString path = source.absoluteFilePath("testrotate.jpg");
3408+ QString path = source.absoluteFilePath("windmillcopy.jpg");
3409 QFile::remove(path);
3410 QFile::copy(source.absoluteFilePath("windmill.jpg"), path);
3411
3412- PhotoData photo;
3413- photo.setPath(path);
3414- QVERIFY(photo.path() == path);
3415+ PhotoMetadata photo;
3416+ photo.setFileName(path);
3417 QVERIFY(photo.orientation() == TOP_LEFT_ORIGIN);
3418
3419- QSignalSpy spy(&photo, SIGNAL(busyChanged()));
3420- photo.rotateRight();
3421- spy.wait(5000);
3422-
3423+ photo.setOrientation(RIGHT_TOP_ORIGIN);
3424 QVERIFY(photo.orientation() == RIGHT_TOP_ORIGIN);
3425
3426- spy.clear();
3427- photo.rotateRight();
3428- spy.wait(5000);
3429-
3430- QVERIFY(photo.orientation() == BOTTOM_RIGHT_ORIGIN);
3431-
3432- spy.clear();
3433- photo.rotateRight();
3434- spy.wait(5000);
3435-
3436- QVERIFY(photo.orientation() == LEFT_BOTTOM_ORIGIN);
3437-
3438- spy.clear();
3439- photo.rotateRight();
3440- spy.wait(5000);
3441-
3442- QVERIFY(photo.orientation() == TOP_LEFT_ORIGIN);
3443-}
3444-
3445-void PhotoEditorPhotoTest::testCrop()
3446-{
3447- QDir source = QDir(m_workingDir.path());
3448- QString path = source.absoluteFilePath("tmp.png");
3449-
3450- QFile::remove(path);
3451- QFile::copy(source.absoluteFilePath("croptest.png"), path);
3452- PhotoData photo;
3453- photo.setPath(path);
3454-
3455- // Verify cropping a vertical strip at the left edge
3456- QSignalSpy spy(&photo, SIGNAL(busyChanged()));
3457- photo.crop(QRectF(0, 0, 0.1, 1.0));
3458- spy.wait(5000);
3459-
3460- QImage cropped(path);
3461- QImage compare(QSize(10, 100), cropped.format());
3462- compare.fill(QColor(0, 0, 0));
3463- QVERIFY(compare == cropped);
3464-
3465- // Verify cropping a square from the center
3466- QFile::remove(path);
3467- QFile::copy(source.absoluteFilePath("croptest.png"), path);
3468- photo.setPath(path);
3469-
3470- spy.clear();
3471- photo.crop(QRectF(0.4, 0.4, 0.2, 0.2));
3472- spy.wait(5000);
3473-
3474- cropped = QImage(path);
3475- compare = QImage(QSize(20, 20), cropped.format());
3476- compare.fill(QColor(0, 255, 0));
3477- QVERIFY(compare == cropped);
3478-
3479- // Verify cropping a thin strip from the top after rotating the image.
3480- // Incidentally this also tests rotation of an image without EXIF tags
3481- QFile::remove(path);
3482- QFile::copy(source.absoluteFilePath("croptest.png"), path);
3483- photo.setPath(path);
3484-
3485- spy.clear();
3486- photo.rotateRight();
3487- spy.wait(5000);
3488-
3489- spy.clear();
3490- photo.crop(QRectF(0.0, 0.0, 1.0, 0.1));
3491- spy.wait(5000);
3492-
3493- cropped = QImage(path);
3494- compare = QImage(QSize(100, 10), cropped.format());
3495- compare.fill(QColor(0, 0, 0));
3496- QVERIFY(compare == cropped);
3497-
3498- // When rotating an image without EXIF, the orientation is fixed
3499- QVERIFY(photo.orientation() == TOP_LEFT_ORIGIN);
3500-
3501- // Test progressive cropping by carving off the bottom horizontal strip and
3502- // then cropping the center part of it
3503- QFile::remove(path);
3504- QFile::copy(source.absoluteFilePath("croptest.png"), path);
3505- photo.setPath(path);
3506-
3507- spy.clear();
3508- photo.crop(QRectF(0.0, 0.9, 1.0, 0.1));
3509- spy.wait(5000);
3510- spy.clear();
3511- photo.crop(QRectF(0.1, 0.0, 0.8, 1.0));
3512- spy.wait(5000);
3513-
3514- cropped = QImage(path);
3515- compare = QImage(QSize(80, 10), cropped.format());
3516- compare.fill(QColor(0, 0, 255));
3517- QVERIFY(compare == cropped);
3518-
3519- // Test a cropping rectangle outside of boundaries
3520- QFile::remove(path);
3521- QFile::copy(source.absoluteFilePath("croptest.png"), path);
3522- photo.setPath(path);
3523-
3524- spy.clear();
3525- photo.crop(QRectF(-1.0, -100.0, 2405.0, 2.0));
3526- spy.wait(5000);
3527- cropped = QImage(path);
3528- QVERIFY(cropped.size() == QSize(100, 100));
3529+ QVERIFY(photo.saving() == false);
3530+ photo.save();
3531+ QVERIFY(photo.saving() == true);
3532+ QTRY_VERIFY(photo.saving() == false);
3533+
3534+ // Reread the metadata and check the orientation
3535+ PhotoMetadata otherPhoto;
3536+ otherPhoto.setFileName(path);
3537+ QVERIFY(otherPhoto.orientation() == RIGHT_TOP_ORIGIN);
3538 }
3539
3540

Subscribers

People subscribed via source and target branches