Merge lp:~fboucault/ubuntu-ui-extras/editor_with_halide into lp:ubuntu-ui-extras
- editor_with_halide
- Merge into trunk
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 |
Related bugs: |
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.
Description of the change
- 76. By Florian Boucault
-
Fixed deb dependencies.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:76
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 77. By Florian Boucault
-
Fix for long loading when editing starts.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:77
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
- 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.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:79
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
system-apps-ci-bot (system-apps-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:79
https:/
Executed test runs:
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
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
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' |
1060 | Binary 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' |
1062 | Binary 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 |
Depends on https:/ /code.launchpad .net/~fboucault /camera- app/dynamic_ header_ actions/ +merge/ 261533