Merge lp:~phablet-team/gallery-app/gallery-app-photo-editor into lp:gallery-app

Proposed by Ugo Riboni on 2014-11-30
Status: Merged
Approved by: Bill Filler on 2015-03-18
Approved revision: 1153
Merged at revision: 1175
Proposed branch: lp:~phablet-team/gallery-app/gallery-app-photo-editor
Merge into: lp:gallery-app
Diff against target: 10279 lines (+4628/-4484)
88 files modified
rc/qml/AlbumViewer/AlbumInternals/FramePortrait.qml (+12/-3)
rc/qml/Components/MediaGrid.qml (+2/-2)
rc/qml/MainScreen.qml (+2/-2)
rc/qml/MediaViewer/CropCorner.qml (+0/-66)
rc/qml/MediaViewer/CropDragArea.qml (+0/-56)
rc/qml/MediaViewer/CropInteractor.qml (+0/-176)
rc/qml/MediaViewer/CropOverlay.qml (+0/-636)
rc/qml/MediaViewer/EditPopover.qml (+0/-99)
rc/qml/MediaViewer/EditPreview.qml (+0/-177)
rc/qml/MediaViewer/ExtrasPhotoEditorPage.qml (+43/-0)
rc/qml/MediaViewer/GalleryPhotoComponent.qml (+2/-2)
rc/qml/MediaViewer/GalleryPhotoEditorPage.qml (+42/-0)
rc/qml/MediaViewer/MediaViewer.qml (+21/-141)
rc/qml/MediaViewer/PhotoEditor.qml (+230/-0)
rc/qml/MediaViewer/PhotoEditor/ActionsBar.qml (+88/-0)
rc/qml/MediaViewer/PhotoEditor/BusyIndicator.qml (+56/-0)
rc/qml/MediaViewer/PhotoEditor/CropCorner.qml (+66/-0)
rc/qml/MediaViewer/PhotoEditor/CropDragArea.qml (+56/-0)
rc/qml/MediaViewer/PhotoEditor/CropInteractor.qml (+140/-0)
rc/qml/MediaViewer/PhotoEditor/CropOverlay.qml (+581/-0)
rc/qml/MediaViewer/PhotoEditor/EditStack.qml (+134/-0)
rc/qml/MediaViewer/PhotoEditor/ExposureAdjuster.qml (+104/-0)
rc/qml/MediaViewer/PhotoEditor/GraphicsRoutines.js (+108/-0)
rc/qml/MediaViewer/SingleMediaViewer.qml (+15/-4)
rc/qml/OrganicView/OrganicMediaList.qml (+2/-2)
src/CMakeLists.txt (+3/-0)
src/database/CMakeLists.txt (+0/-2)
src/database/database.cpp (+0/-12)
src/database/database.h (+0/-3)
src/database/photo-edit-table.cpp (+0/-124)
src/database/photo-edit-table.h (+0/-47)
src/gallery-application.cpp (+21/-4)
src/gallery-application.h (+2/-0)
src/gallery-manager.cpp (+0/-26)
src/gallery-manager.h (+0/-3)
src/media-object-factory.cpp (+1/-1)
src/media/media-source.cpp (+0/-44)
src/media/media-source.h (+0/-10)
src/medialoader/CMakeLists.txt (+0/-6)
src/medialoader/gallery-standard-image-provider.cpp (+0/-479)
src/medialoader/gallery-standard-image-provider.h (+0/-128)
src/medialoader/photo-metadata.cpp (+0/-285)
src/medialoader/photo-metadata.h (+0/-69)
src/photo/CMakeLists.txt (+1/-6)
src/photo/photo-caches.cpp (+0/-183)
src/photo/photo-caches.h (+0/-75)
src/photo/photo-edit-state.cpp (+0/-77)
src/photo/photo-edit-state.h (+0/-89)
src/photo/photo-edit-thread.cpp (+0/-287)
src/photo/photo-edit-thread.h (+0/-63)
src/photo/photo.cpp (+11/-574)
src/photo/photo.h (+0/-53)
src/photoeditor/CMakeLists.txt (+47/-0)
src/photoeditor/file-utils.cpp (+97/-0)
src/photoeditor/file-utils.h (+42/-0)
src/photoeditor/imaging.cpp (+363/-0)
src/photoeditor/imaging.h (+169/-0)
src/photoeditor/orientation.cpp (+152/-0)
src/photoeditor/orientation.h (+64/-0)
src/photoeditor/photo-caches.cpp (+183/-0)
src/photoeditor/photo-caches.h (+76/-0)
src/photoeditor/photo-data.cpp (+275/-0)
src/photoeditor/photo-data.h (+92/-0)
src/photoeditor/photo-edit-command.h (+58/-0)
src/photoeditor/photo-edit-thread.cpp (+185/-0)
src/photoeditor/photo-edit-thread.h (+56/-0)
src/photoeditor/photo-image-provider.cpp (+443/-0)
src/photoeditor/photo-image-provider.h (+124/-0)
src/photoeditor/photo-metadata.cpp (+281/-0)
src/photoeditor/photo-metadata.h (+67/-0)
tests/autopilot/gallery_app/emulators/album_view.py (+2/-2)
tests/autopilot/gallery_app/emulators/events_view.py (+27/-0)
tests/autopilot/gallery_app/emulators/photo_viewer.py (+55/-15)
tests/autopilot/gallery_app/tests/test_events_view.py (+1/-1)
tests/autopilot/gallery_app/tests/test_photo_viewer.py (+20/-40)
tests/autopilot/gallery_app/tests/test_picker_mode.py (+1/-1)
tests/unittests/CMakeLists.txt (+0/-1)
tests/unittests/gallerystandardimageprovider/CMakeLists.txt (+0/-36)
tests/unittests/gallerystandardimageprovider/media-collection.h (+0/-28)
tests/unittests/gallerystandardimageprovider/tst_gallerystandardimageprovidertest.cpp (+0/-78)
tests/unittests/mediaobjectfactory/CMakeLists.txt (+2/-6)
tests/unittests/stubs/database_stub.cpp (+0/-8)
tests/unittests/stubs/gallery-manager_stub.cpp (+0/-10)
tests/unittests/stubs/gallery-standard-image-provider_stub.cpp (+0/-70)
tests/unittests/stubs/photo-caches_stub.cpp (+0/-27)
tests/unittests/stubs/photo-edit-table_stub.cpp (+0/-47)
tests/unittests/stubs/photo_stub.cpp (+1/-98)
tests/unittests/video/CMakeLists.txt (+2/-0)
To merge this branch: bzr merge lp:~phablet-team/gallery-app/gallery-app-photo-editor
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Needs Fixing on 2015-03-10
Arthur Mello (community) 2014-11-30 Approve on 2015-01-15
Ugo Riboni 2015-03-02 Pending
Review via email: mp+243243@code.launchpad.net

Commit Message

Replace the built-in photo editor with the one from ubuntu-ui-extras

Description of the Change

Replace the built-in photo editor with the one from ubuntu-ui-extras

You need to have this branch installed for this to work:
https://code.launchpad.net/~phablet-team/ubuntu-ui-extras/photo-editor

To post a comment you must log in.
Arthur Mello (artmello) wrote :

It looks really nice but there is one thing with multiples crops that seems strange to me. Steps to reproduce:

1. Opens a photo;
2. Crop it;
3. Rotate it (or exec any other editing besides crop);
4. Try to crop again.

The photo preview displayed on the second crop will be the original one. If I confirm the second crop and undo the edits from the stack I will pass trough all the previews states.

That is how it should work or should I be able to exec incremental crops?

review: Needs Information
Arthur Mello (artmello) wrote :

There was some changes in Thumbnailer on the last image (r184). I retest the branch and some of the situations I mention on my last comment are gone, especially when you start a new crop just after another. But I am still able to reproduce the behavior with crop, other editing and crop again. But whit that this could be an issue with Thumbnailer and not on gallery.

Arthur Mello (artmello) wrote :

lgtm

review: Approve
1153. By Arthur Mello on 2015-03-10

Merge with trunk

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== removed file 'rc/img/crop-handle@20.png'
2Binary files rc/img/crop-handle@20.png 2013-02-13 16:29:00 +0000 and rc/img/crop-handle@20.png 1970-01-01 00:00:00 +0000 differ
3=== modified file 'rc/qml/AlbumViewer/AlbumInternals/FramePortrait.qml'
4--- rc/qml/AlbumViewer/AlbumInternals/FramePortrait.qml 2014-03-19 20:01:31 +0000
5+++ rc/qml/AlbumViewer/AlbumInternals/FramePortrait.qml 2015-03-10 14:05:41 +0000
6@@ -44,7 +44,7 @@
7 anchors.fill: parent
8 asynchronous: true
9 visible: fullImage.opacity < 1
10- source: load && mediaSource ? mediaSource.galleryPreviewPath : ""
11+ source: load && mediaSource ? "image://thumbnailer/" + mediaSource.path : ""
12 fillMode: fullImage.fillMode
13 sourceSize.width: 256
14
15@@ -53,7 +53,14 @@
16 onDataChanged: {
17 // data changed but filename didn't, so we need to bypass the qml image
18 // cache by tacking a timestamp to the filename so sees it as different.
19- preview.source = mediaSource.galleryPreviewPath + "?at=" + Date.now()
20+ preview.source = "image://thumbnailer/" + mediaSource.path + "?at=" + Date.now()
21+
22+ // reload full image
23+ var src = fullImage.source;
24+ fullImage.asynchronous = false;
25+ fullImage.source = "";
26+ fullImage.asynchronous = true;
27+ fullImage.source = src;
28 }
29 }
30 }
31@@ -61,8 +68,10 @@
32 id: fullImage
33 anchors.fill: parent
34 asynchronous: true
35+ cache: false
36 fillMode: Image.PreserveAspectCrop
37- source: (preview.status === Image.Ready && !isPreview) ? mediaSource.galleryPath : ""
38+ source: (preview.status === Image.Ready && !isPreview) ?
39+ "image://thumbnailer/" + mediaSource.path : ""
40
41 property int maxSize: Math.max(width, height)
42 sourceSize.width: maxSize
43
44=== modified file 'rc/qml/Components/MediaGrid.qml'
45--- rc/qml/Components/MediaGrid.qml 2015-02-09 18:26:03 +0000
46+++ rc/qml/Components/MediaGrid.qml 2015-03-10 14:05:41 +0000
47@@ -83,7 +83,7 @@
48
49 image: Image {
50 id: thumbImage
51- source: mediaSource.galleryThumbnailPath
52+ source: "image://thumbnailer/" + mediaSource.path
53 asynchronous: true
54 fillMode: Image.PreserveAspectCrop
55
56@@ -92,7 +92,7 @@
57 onDataChanged: {
58 // data changed but filename didn't, so we need to bypass the qml image
59 // cache by tacking a timestamp to the filename so sees it as different.
60- thumbImage.source = mediaSource.galleryThumbnailPath + "?at=" + Date.now()
61+ thumbImage.source = "image://thumbnailer/" + mediaSource.path + "?at=" + Date.now()
62 }
63 }
64 }
65
66=== modified file 'rc/qml/MainScreen.qml'
67--- rc/qml/MainScreen.qml 2015-02-17 11:58:34 +0000
68+++ rc/qml/MainScreen.qml 2015-03-10 14:05:41 +0000
69@@ -119,8 +119,8 @@
70 }
71 }
72
73- function pushPage(page) {
74- pageStack.push(page);
75+ function pushPage(page, properties) {
76+ return pageStack.push(page, properties);
77 }
78
79 function popPage() {
80
81=== removed file 'rc/qml/MediaViewer/CropCorner.qml'
82--- rc/qml/MediaViewer/CropCorner.qml 2013-06-13 12:13:00 +0000
83+++ rc/qml/MediaViewer/CropCorner.qml 1970-01-01 00:00:00 +0000
84@@ -1,66 +0,0 @@
85-/*
86- * Copyright (C) 2012 Canonical Ltd
87- *
88- * This program is free software: you can redistribute it and/or modify
89- * it under the terms of the GNU General Public License version 3 as
90- * published by the Free Software Foundation.
91- *
92- * This program is distributed in the hope that it will be useful,
93- * but WITHOUT ANY WARRANTY; without even the implied warranty of
94- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
95- * GNU General Public License for more details.
96- *
97- * You should have received a copy of the GNU General Public License
98- * along with this program. If not, see <http://www.gnu.org/licenses/>.
99- *
100- * Authors:
101- * Charles Lindsay <chaz@yorba.org>
102- */
103-
104-import QtQuick 2.0
105-import Ubuntu.Components 0.1
106-
107-// A corner of a CropFrame.
108-Item {
109- id: cropCorner
110-
111- /*!
112- */
113- signal dragged(real dx, real dy)
114- /*!
115- */
116- signal dragStarted()
117- /*!
118- */
119- signal dragCompleted()
120-
121- /*!
122- */
123- property bool isLeft: true
124- /*!
125- */
126- property bool isTop: true
127-
128- x: isLeft ? -(width/2) : parent.width - (width/2)
129- y: isTop ? -(width/2) : parent.height - (width/2)
130- width: handle.width
131- height: handle.height
132-
133- Image {
134- id: handle
135- anchors.centerIn: parent
136- source: Qt.resolvedUrl("../../img/crop-handle.png")
137- }
138-
139- CropDragArea {
140- anchors.centerIn: parent
141- width: handle.width + units.gu(2)
142- height: handle.height + units.gu(2)
143-
144- onDragged: cropCorner.dragged(dx, dy)
145-
146- onDragStarted: cropCorner.dragStarted()
147-
148- onDragCompleted: cropCorner.dragCompleted()
149- }
150-}
151
152=== removed file 'rc/qml/MediaViewer/CropDragArea.qml'
153--- rc/qml/MediaViewer/CropDragArea.qml 2013-06-13 12:13:00 +0000
154+++ rc/qml/MediaViewer/CropDragArea.qml 1970-01-01 00:00:00 +0000
155@@ -1,56 +0,0 @@
156-/*
157- * Copyright (C) 2012 Canonical Ltd
158- *
159- * This program is free software: you can redistribute it and/or modify
160- * it under the terms of the GNU General Public License version 3 as
161- * published by the Free Software Foundation.
162- *
163- * This program is distributed in the hope that it will be useful,
164- * but WITHOUT ANY WARRANTY; without even the implied warranty of
165- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
166- * GNU General Public License for more details.
167- *
168- * You should have received a copy of the GNU General Public License
169- * along with this program. If not, see <http://www.gnu.org/licenses/>.
170- *
171- * Authors:
172- * Charles Lindsay <chaz@yorba.org>
173- */
174-
175-import QtQuick 2.0
176-
177-// A MouseArea meant to drag a corner/edge of a crop area.
178-MouseArea {
179- id: cropDragArea
180-
181- /*!
182- */
183- signal dragged(real dx, real dy)
184- /*!
185- */
186- signal dragStarted()
187- /*!
188- */
189- signal dragCompleted()
190-
191- // Since we're usually moving this area with the mouse in response to
192- // dragging, we don't need to capture the last x/y, just where it was
193- // grabbed.
194- property real grabX: -1
195- /*!
196- */
197- property real grabY: -1
198-
199- onPressed: {
200- dragStarted();
201-
202- grabX = mouse.x;
203- grabY = mouse.y;
204- }
205-
206- onReleased: {
207- dragCompleted();
208- }
209-
210- onPositionChanged: cropDragArea.dragged(mouse.x - grabX, mouse.y - grabY)
211-}
212
213=== removed file 'rc/qml/MediaViewer/CropInteractor.qml'
214--- rc/qml/MediaViewer/CropInteractor.qml 2013-06-13 12:13:00 +0000
215+++ rc/qml/MediaViewer/CropInteractor.qml 1970-01-01 00:00:00 +0000
216@@ -1,176 +0,0 @@
217-/*
218- * Copyright (C) 2012 Canonical Ltd
219- *
220- * This program is free software: you can redistribute it and/or modify
221- * it under the terms of the GNU General Public License version 3 as
222- * published by the Free Software Foundation.
223- *
224- * This program is distributed in the hope that it will be useful,
225- * but WITHOUT ANY WARRANTY; without even the implied warranty of
226- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
227- * GNU General Public License for more details.
228- *
229- * You should have received a copy of the GNU General Public License
230- * along with this program. If not, see <http://www.gnu.org/licenses/>.
231- *
232- * Authors:
233- * Charles Lindsay <chaz@yorba.org>
234- * Lucas Beeler <lucas@yorba.org>
235- */
236-
237-import QtQuick 2.0
238-import Gallery 1.0
239-import Ubuntu.Components 0.1
240-import "../Components"
241-import "../../js/GraphicsRoutines.js" as GraphicsRoutines
242-
243-/*!
244-*/
245-Item {
246- id: cropInteractor
247- objectName: "cropInteractor"
248-
249- /*!
250- */
251- property string matteColor: "black"
252- /*!
253- */
254- property real matteOpacity: 0.6
255-
256- // Note: each element of the cropped rect will be in the range [0,1], since
257- // in the UI we aren't using direct photo pixel values.
258- signal cropped(variant rect)
259- /*!
260- */
261- signal canceled()
262-
263- // readonly
264- /*!
265- */
266- property variant photo // Set with enter().
267-
268- // internal
269- /*!
270- */
271- property variant ratio_crop_rect
272-
273- /*!
274- */
275- function computeRectSet(photo, relativeCropRect) {
276- var result = { };
277-
278- var wholePhotoPreviewRect = GraphicsRoutines.fitRect(viewport,
279- cropInteractor.photo);
280-
281- var unfitCropRect = { };
282- unfitCropRect.x = relativeCropRect.x * wholePhotoPreviewRect.width;
283- unfitCropRect.y = relativeCropRect.y * wholePhotoPreviewRect.height;
284- unfitCropRect.width = relativeCropRect.width * wholePhotoPreviewRect.width;
285- unfitCropRect.height = relativeCropRect.height *
286- wholePhotoPreviewRect.height;
287-
288- var cropFrameRect = GraphicsRoutines.fitRect(viewport, unfitCropRect);
289-
290- var photoExtentRect = { };
291- photoExtentRect.x = cropFrameRect.x - (cropFrameRect.scaleFactor *
292- wholePhotoPreviewRect.width * relativeCropRect.x);
293- photoExtentRect.y = cropFrameRect.y - (cropFrameRect.scaleFactor *
294- wholePhotoPreviewRect.height * relativeCropRect.y);
295- photoExtentRect.width = cropFrameRect.scaleFactor *
296- wholePhotoPreviewRect.width;
297- photoExtentRect.height = cropFrameRect.scaleFactor *
298- wholePhotoPreviewRect.height;
299- photoExtentRect.scaleFactor = cropFrameRect.scaleFactor;
300-
301- result.photoPreviewRect = wholePhotoPreviewRect;
302- result.cropFrameRect = cropFrameRect;
303- result.photoExtentRect = photoExtentRect;
304-
305- return result;
306- }
307-
308- /*!
309- */
310- function enter(photo, ratio_crop_rect) {
311- cropInteractor.photo = photo;
312- original.mediaSource = photo;
313- cropInteractor.ratio_crop_rect = ratio_crop_rect;
314-
315- var rects = computeRectSet(photo, ratio_crop_rect);
316-
317- overlay.initialFrameX = rects.cropFrameRect.x;
318- overlay.initialFrameY = rects.cropFrameRect.y;
319- overlay.initialFrameWidth = rects.cropFrameRect.width;
320- overlay.initialFrameHeight = rects.cropFrameRect.height;
321-
322- overlay.resetFor(rects);
323-
324- original.visible = true;
325- overlay.visible = true;
326- }
327-
328- Item {
329- id: viewport
330-
331- anchors.fill: parent
332- anchors.margins: units.gu(8)
333- z: 1
334- }
335-
336- CropOverlay {
337- id: overlay
338- objectName: "cropOverlay"
339-
340- property real minSize: units.gu(4)
341-
342- anchors.fill: parent;
343- visible: false;
344-
345- photo: original
346- viewport: viewport
347-
348- matteColor: cropInteractor.matteColor
349- matteOpacity: cropInteractor.matteOpacity
350-
351- z: 16
352-
353- onMatteRegionPressed: {
354- cropInteractor.canceled();
355- }
356-
357- onCropButtonPressed: {
358- original.visible = false;
359- overlay.visible = false;
360- original.scale = 1.0;
361- cropInteractor.cropped(overlay.getRelativeFrameRect());
362- }
363- }
364-
365- GalleryPhotoComponent {
366- id: original
367-
368- x: viewport.x;
369- y: viewport.y;
370- width: viewport.width;
371- height: viewport.height;
372- transformOrigin: Item.TopLeft;
373-
374- color: "black"
375- visible: false
376- load: true
377-
378- mediaSource: cropInteractor.photo
379- ownerName: "cropInteractor"
380-
381- onLoaded: {
382- var rects = cropInteractor.computeRectSet(cropInteractor.photo,
383- cropInteractor.ratio_crop_rect);
384-
385- x = rects.photoExtentRect.x;
386- y = rects.photoExtentRect.y;
387- width = rects.photoPreviewRect.width;
388- height = rects.photoPreviewRect.height;
389- scale = rects.photoExtentRect.scaleFactor;
390- }
391- }
392-}
393
394=== removed file 'rc/qml/MediaViewer/CropOverlay.qml'
395--- rc/qml/MediaViewer/CropOverlay.qml 2013-06-20 11:54:49 +0000
396+++ rc/qml/MediaViewer/CropOverlay.qml 1970-01-01 00:00:00 +0000
397@@ -1,636 +0,0 @@
398-/*
399- * Copyright (C) 2012 Canonical Ltd
400- *
401- * This program is free software: you can redistribute it and/or modify
402- * it under the terms of the GNU General Public License version 3 as
403- * published by the Free Software Foundation.
404- *
405- * This program is distributed in the hope that it will be useful,
406- * but WITHOUT ANY WARRANTY; without even the implied warranty of
407- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
408- * GNU General Public License for more details.
409- *
410- * You should have received a copy of the GNU General Public License
411- * along with this program. If not, see <http://www.gnu.org/licenses/>.
412- *
413- * Authors:
414- * Charles Lindsay <chaz@yorba.org>
415- * Lucas Beeler <lucas@yorba.org>
416- */
417-
418-import QtQuick 2.0
419-import Gallery 1.0
420-import Ubuntu.Components 0.1
421-import "../Components"
422-import "../Utility"
423-import "../../js/GraphicsRoutines.js" as GraphicsRoutines
424-
425-/* A CropOverlay is a semi-transparent surface that floats over the photo. It
426- * serves two purposes. First, it provides visual cueing as to what region of
427- * the photo's surface will be preserved when the crop operation is applied.
428- * The preserved region is the region that falls inside of the CropOverlay's
429- * frame. Second, the CropOverlay allows the user to manipulate the
430- * geometry of the crop frame, to chage its location, width, and height. The
431- * geometry of the crop frame is reinforced by a key visual cue: the region of
432- * the photo outside of the crop frame is drawn with a semi-transparent, smoked
433- * matte on top of it. This matte surrounds the crop frame.
434- */
435-Item {
436- id: cropOverlay
437-
438- // public properties
439- /*!
440- */
441- property Item viewport
442- /*!
443- */
444- property GalleryPhotoComponent photo
445- /*!
446- */
447- property string matteColor: "red"
448- /*!
449- */
450- property real matteOpacity: 0.85
451- /*!
452- */
453- property int initialFrameX: -1
454- /*!
455- */
456- property int initialFrameY: -1
457- /*!
458- */
459- property int initialFrameWidth: -1
460- /*!
461- */
462- property int initialFrameHeight: -1
463-
464- // private properties -- Frame Fit Animation parameters
465- property real interpolationFactor: 1.0
466- /*!
467- */
468- property variant startFrame
469- /*!
470- */
471- property variant endFrame
472- /*!
473- */
474- property variant startPhoto
475- /*!
476- */
477- property real referencePhotoWidth: -1
478- /*!
479- */
480- property real referencePhotoHeight: -1
481- /*!
482- */
483- property real endPhotoX
484- /*!
485- */
486- property real endPhotoY
487- /*!
488- */
489- property real endPhotoWidth
490- /*!
491- */
492- property real endPhotoHeight
493-
494- /*!
495- */
496- signal userAlteredFrame()
497- /*!
498- */
499- signal runFrameFitAnimation()
500- /*!
501- */
502- signal matteRegionPressed()
503- /*!
504- */
505- signal cropButtonPressed()
506-
507- /*!
508- */
509- function resetFor(rectSet) {
510- if (initialFrameX != -1 && initialFrameY != -1 && initialFrameWidth != -1 &&
511- initialFrameHeight != -1) {
512- frame.x = rectSet.cropFrameRect.x;
513- frame.y = rectSet.cropFrameRect.y;
514- frame.width = rectSet.cropFrameRect.width;
515- frame.height = rectSet.cropFrameRect.height;
516- photoExtent.x = rectSet.photoExtentRect.x;
517- photoExtent.y = rectSet.photoExtentRect.y;
518- photoExtent.width = rectSet.photoExtentRect.width;
519- photoExtent.height = rectSet.photoExtentRect.height;
520- referencePhotoWidth = rectSet.photoPreviewRect.width;
521- referencePhotoHeight = rectSet.photoPreviewRect.height;
522- }
523- }
524-
525- /* Return the (x, y) position and the width and height of the viewport
526- */
527- function getViewportExtentRect() {
528- return GraphicsRoutines.cloneRect(viewport);
529- }
530-
531- /* Return the (x, y) position and the width and height of the photoExtent.
532- * The photoExtent is the on-screen region that holds the original photo
533- * preview.
534- */
535- function getPhotoExtentRect() {
536- return GraphicsRoutines.cloneRect(photoExtent);
537- }
538-
539- /*!
540- */
541- function getRelativeFrameRect() {
542- return GraphicsRoutines.getRelativeRect(frame.getExtentRect(),
543- getPhotoExtentRect());
544- }
545-
546- anchors.fill: parent
547-
548- Item {
549- id: photoExtent
550-
551- property real panStartX
552- property real panStartY
553-
554- function startPan() {
555- panStartX = x;
556- panStartY = y;
557- }
558-
559- // 'deltaX' and 'deltaY' are offsets relative to the pan start point
560- function updatePan(deltaX, deltaY) {
561- var newX = panStartX + deltaX;
562- var newY = panStartY + deltaY;
563-
564- x = GraphicsRoutines.clamp(newX, frame.x + frame.width -
565- photoExtent.width, frame.x);
566- y = GraphicsRoutines.clamp(newY, frame.y + frame.height -
567- photoExtent.height, frame.y);
568- }
569-
570- function stopPan() {
571- }
572-
573- x: initialFrameX
574- y: initialFrameY
575- width: initialFrameWidth
576- height: initialFrameHeight
577- z: 1
578-
579- onXChanged: {
580- if (photo)
581- photo.x = x;
582- }
583-
584- onYChanged: {
585- if (photo)
586- photo.y = y;
587- }
588-
589- onWidthChanged: {
590- if (photo && referencePhotoWidth > 0)
591- photo.scale = width / referencePhotoWidth;
592- }
593-
594- onHeightChanged: {
595- if (photo && referencePhotoHeight > 0)
596- photo.scale = height / referencePhotoHeight;
597- }
598- }
599-
600- //
601- // The following four Rectangles are used to "matte out" the area of the photo
602- // preview that falls outside the frame. This "matting out" visual cue is
603- // accomplished by darkening the matted-out area with a translucent, smoked
604- // overlay.
605- //
606- Rectangle {
607- id: leftMatte
608-
609- color: cropOverlay.matteColor
610- opacity: cropOverlay.matteOpacity
611-
612- anchors.top: topMatte.bottom
613- anchors.bottom: frame.bottom
614- anchors.left: parent.left
615- anchors.right: frame.left
616-
617- MouseArea {
618- anchors.fill: parent;
619-
620- onPressed: cropOverlay.matteRegionPressed();
621- }
622- }
623-
624- Rectangle {
625- id: topMatte
626-
627- color: cropOverlay.matteColor
628- opacity: cropOverlay.matteOpacity
629-
630- anchors.top: parent.top
631- anchors.bottom: frame.top
632- anchors.left: parent.left
633- anchors.right: parent.right
634-
635- MouseArea {
636- anchors.fill: parent;
637-
638- onPressed: cropOverlay.matteRegionPressed();
639- }
640- }
641-
642- Rectangle {
643- id: rightMatte
644-
645- color: cropOverlay.matteColor
646- opacity: cropOverlay.matteOpacity
647-
648- anchors.top: topMatte.bottom
649- anchors.bottom: bottomMatte.top
650- anchors.left: frame.right
651- anchors.right: parent.right
652-
653- MouseArea {
654- anchors.fill: parent;
655-
656- onPressed: cropOverlay.matteRegionPressed();
657- }
658- }
659-
660- Rectangle {
661- id: bottomMatte
662-
663- color: cropOverlay.matteColor
664- opacity: cropOverlay.matteOpacity
665-
666- anchors.top: frame.bottom
667- anchors.bottom: parent.bottom
668- anchors.left: parent.left
669- anchors.right: parent.right
670-
671- MouseArea {
672- anchors.fill: parent;
673-
674- onPressed: cropOverlay.matteRegionPressed();
675- }
676- }
677-
678- //
679- // The frame is a grey rectangle with associated drag corners that
680- // frames the region of the photo that will remain when the crop operation is
681- // applied.
682- //
683- // NB: the frame can be in two states, although the QML state mechanism
684- // isn't sufficiently expressive to describe them. The frame can be
685- // in the FIT state, in which case it is optimally fit inside the
686- // frame constraint region (see getFrameConstraintRect( ) above for
687- // a description of the frame constraint region). Or, the frame can
688- // be in the USER state. In the user state, the user has the mouse button
689- // held down and is actively performing a drag operation to change the
690- // geometry of the frame.
691- //
692- Rectangle {
693- id: frame
694-
695- signal resizedX(bool left, real dx)
696- signal resizedY(bool top, real dy)
697-
698- property variant dragStartRect
699-
700- function getExtentRect() {
701- var result = { };
702-
703- result.x = x;
704- result.y = y;
705- result.width = width;
706- result.height = height;
707-
708- return result;
709- }
710-
711- x: cropOverlay.initialFrameX
712- y: cropOverlay.initialFrameY
713- width: cropOverlay.initialFrameWidth
714- height: cropOverlay.initialFrameHeight
715-
716- color: "transparent"
717-
718- border.width: units.gu(0.2)
719- border.color: "#19B6EE"
720-
721- MouseArea {
722- id: panArea
723-
724- property int dragStartX;
725- property int dragStartY;
726-
727- anchors.fill: parent
728- anchors.margins: 2
729-
730- onPressed: {
731- dragStartX = mouse.x;
732- dragStartY = mouse.y;
733-
734- photoExtent.startPan();
735- }
736-
737- onReleased: {
738- photoExtent.stopPan();
739- }
740-
741- onPositionChanged: {
742- photoExtent.updatePan(mouse.x - dragStartX, mouse.y - dragStartY);
743- }
744- }
745-
746- Button {
747- objectName: "centerCropIcon"
748- anchors.centerIn: parent
749- text: i18n.tr("Crop")
750- color: frame.border.color
751- opacity: 0.9
752- onClicked: cropOverlay.cropButtonPressed()
753- }
754-
755- // Left drag bar.
756- CropDragArea {
757- x: -units.gu(2)
758- width: units.gu(3)
759- anchors.verticalCenter: parent.center
760- height: parent.height - units.gu(2)
761-
762- onDragged: {
763- frame.resizedX(true, dx)
764- }
765-
766- onDragStarted: {
767- frame.dragStartRect = frame.getExtentRect();
768- }
769-
770- onDragCompleted: {
771- if (!GraphicsRoutines.areEqual(frame.getExtentRect(),
772- frame.dragStartRect)) {
773- cropOverlay.userAlteredFrame();
774- cropOverlay.runFrameFitAnimation();
775- }
776- }
777- }
778-
779- // Top drag bar.
780- CropDragArea {
781- y: -units.gu(2)
782- height: units.gu(3)
783- anchors.horizontalCenter: parent.center
784- width: parent.width - units.gu(2)
785-
786- onDragged: {
787- frame.resizedY(true, dy)
788- }
789-
790- onDragStarted: {
791- frame.dragStartRect = frame.getExtentRect();
792- }
793-
794- onDragCompleted: {
795- if (!GraphicsRoutines.areEqual(frame.getExtentRect(),
796- frame.dragStartRect)) {
797- cropOverlay.userAlteredFrame();
798- cropOverlay.runFrameFitAnimation();
799- }
800- }
801- }
802-
803- // Right drag bar.
804- CropDragArea {
805- x: parent.width - units.gu(1)
806- width: units.gu(3)
807- anchors.verticalCenter: parent.center
808- height: parent.height - units.gu(2)
809-
810- onDragged: {
811- frame.resizedX(false, dx)
812- }
813-
814- onDragStarted: {
815- frame.dragStartRect = frame.getExtentRect();
816- }
817-
818- onDragCompleted: {
819- if (!GraphicsRoutines.areEqual(frame.getExtentRect(),
820- frame.dragStartRect)) {
821- cropOverlay.userAlteredFrame();
822- cropOverlay.runFrameFitAnimation();
823- }
824- }
825- }
826-
827- // Bottom drag bar.
828- CropDragArea {
829- y: parent.height - units.gu(1)
830- height: units.gu(3)
831- anchors.horizontalCenter: parent.center
832- width: parent.width - units.gu(2)
833-
834- onDragged: {
835- frame.resizedY(false, dy)
836- }
837-
838- onDragStarted: {
839- frame.dragStartRect = frame.getExtentRect();
840- }
841-
842- onDragCompleted: {
843- if (!GraphicsRoutines.areEqual(frame.getExtentRect(),
844- frame.dragStartRect)) {
845- cropOverlay.userAlteredFrame();
846- cropOverlay.runFrameFitAnimation();
847- }
848- }
849- }
850-
851- // Top-left corner.
852- CropCorner {
853- objectName: "topLeftCropCorner"
854- isLeft: true
855- isTop: true
856-
857- onDragged: {
858- frame.resizedX(isLeft, dx);
859- frame.resizedY(isTop, dy);
860- }
861-
862- onDragStarted: {
863- frame.dragStartRect = frame.getExtentRect();
864- }
865-
866- onDragCompleted: {
867- if (!GraphicsRoutines.areEqual(frame.getExtentRect(),
868- frame.dragStartRect)) {
869- cropOverlay.userAlteredFrame();
870- cropOverlay.runFrameFitAnimation();
871- }
872- }
873- }
874-
875- // Top-right corner.
876- CropCorner {
877- objectName: "topRightCropCorner"
878- isLeft: false
879- isTop: true
880-
881- onDragged: {
882- frame.resizedX(isLeft, dx);
883- frame.resizedY(isTop, dy);
884- }
885-
886- onDragStarted: {
887- frame.dragStartRect = frame.getExtentRect();
888- }
889-
890- onDragCompleted: {
891- if (!GraphicsRoutines.areEqual(frame.getExtentRect(),
892- frame.dragStartRect)) {
893- cropOverlay.userAlteredFrame();
894- cropOverlay.runFrameFitAnimation();
895- }
896- }
897- }
898-
899- // Bottom-left corner.
900- CropCorner {
901- objectName: "bottonLeftCropCorner"
902- isLeft: true
903- isTop: false
904-
905- onDragged: {
906- frame.resizedX(isLeft, dx);
907- frame.resizedY(isTop, dy);
908- }
909-
910- onDragStarted: {
911- frame.dragStartRect = frame.getExtentRect();
912- }
913-
914- onDragCompleted: {
915- if (!GraphicsRoutines.areEqual(frame.getExtentRect(),
916- frame.dragStartRect)) {
917- cropOverlay.userAlteredFrame();
918- cropOverlay.runFrameFitAnimation();
919- }
920- }
921- }
922-
923- // Bottom-right corner.
924- CropCorner {
925- id: bottomRightCrop
926- objectName: "bottomRightCropCorner"
927- isLeft: false
928- isTop: false
929-
930- onDragged: {
931- frame.resizedX(isLeft, dx);
932- frame.resizedY(isTop, dy);
933- }
934-
935- onDragStarted: {
936- frame.dragStartRect = frame.getExtentRect();
937- }
938-
939- onDragCompleted: {
940- if (!GraphicsRoutines.areEqual(frame.getExtentRect(),
941- frame.dragStartRect)) {
942- cropOverlay.userAlteredFrame();
943- cropOverlay.runFrameFitAnimation();
944- }
945- }
946- }
947-
948- // This handles resizing in both dimensions. first is whether we're
949- // resizing the "first" edge, e.g. left or top (in which case we
950- // adjust both position and span) vs. right or bottom (where we just
951- // adjust the span). position should be either "x" or "y", and span
952- // is either "width" or "height". This is a little complicated, and
953- // coule probably be optimized with a little more thought.
954- function resizeFrame(first, delta, position, span) {
955- var constraintRegion = cropOverlay.getPhotoExtentRect();
956-
957- if (first) {
958- // Left/top side.
959- if (frame[position] + delta < constraintRegion[position])
960- delta = constraintRegion[position] - frame[position]
961-
962- if (frame[span] - delta < minSize)
963- delta = frame[span] - minSize;
964-
965- frame[position] += delta;
966- frame[span] -= delta;
967- } else {
968- // Right/bottom side.
969- if (frame[span] + delta < minSize)
970- delta = minSize - frame[span];
971-
972- if ((frame[position] + frame[span] + delta) >
973- (constraintRegion[position] + constraintRegion[span]))
974- delta = constraintRegion[position] + constraintRegion[span] -
975- frame[position] - frame[span];
976-
977- frame[span] += delta;
978- }
979- }
980-
981- onResizedX: resizeFrame(left, dx, "x", "width")
982- onResizedY: resizeFrame(top, dy, "y", "height")
983- }
984-
985- /* Invoked when the user has changed the geometry of the frame by dragging
986- * one of its corners or edges. Expressed in terms of the states of the
987- * frame described above, the userAlteredFrame signal is fired
988- * when the user stops dragging. This triggers a change of the frame
989- * from the USER state to the FIT state
990- */
991- onUserAlteredFrame: {
992- // since the geometry of the frame in the FIT state depends on both
993- // how the user resized the frame when it was in the USER state as well
994- // as the size of the frame constraint region, we have to recompute the
995- // geometry of of the frame for the FIT state every time.
996-
997- startFrame = GraphicsRoutines.cloneRect(frame);
998-
999- endFrame = GraphicsRoutines.fitRect(getViewportExtentRect(),
1000- frame.getExtentRect());
1001-
1002- startPhoto = GraphicsRoutines.cloneRect(photoExtent);
1003-
1004- var frameRelativeToPhoto = getRelativeFrameRect();
1005- var scaleFactor = endFrame.width / frame.width;
1006-
1007- endPhotoWidth = photoExtent.width * scaleFactor;
1008- endPhotoHeight = photoExtent.height * scaleFactor;
1009- endPhotoX = endFrame.x - (frameRelativeToPhoto.x * endPhotoWidth);
1010- endPhotoY = endFrame.y - (frameRelativeToPhoto.y * endPhotoHeight)
1011-
1012- photo.transformOrigin = Item.TopLeft;
1013- }
1014-
1015- onRunFrameFitAnimation: NumberAnimation { target: cropOverlay;
1016- property: "interpolationFactor"; from: 0.0; to: 1.0 }
1017-
1018- onInterpolationFactorChanged: {
1019- var endPhotoRect = { };
1020- endPhotoRect.x = endPhotoX;
1021- endPhotoRect.y = endPhotoY;
1022- endPhotoRect.width = endPhotoWidth;
1023- endPhotoRect.height = endPhotoHeight;
1024-
1025- var interpolatedRect = GraphicsRoutines.interpolateRect(startFrame,
1026- endFrame, interpolationFactor);
1027- GraphicsRoutines.sizeToRect(interpolatedRect, frame);
1028-
1029- interpolatedRect = GraphicsRoutines.interpolateRect(startPhoto,
1030- endPhotoRect, interpolationFactor);
1031- GraphicsRoutines.sizeToRect(interpolatedRect, photoExtent);
1032- }
1033-}
1034
1035=== removed file 'rc/qml/MediaViewer/EditPopover.qml'
1036--- rc/qml/MediaViewer/EditPopover.qml 2013-08-07 12:47:13 +0000
1037+++ rc/qml/MediaViewer/EditPopover.qml 1970-01-01 00:00:00 +0000
1038@@ -1,99 +0,0 @@
1039-/*
1040- * Copyright (C) 2011-2012 Canonical Ltd
1041- *
1042- * This program is free software: you can redistribute it and/or modify
1043- * it under the terms of the GNU General Public License version 3 as
1044- * published by the Free Software Foundation.
1045- *
1046- * This program is distributed in the hope that it will be useful,
1047- * but WITHOUT ANY WARRANTY; without even the implied warranty of
1048- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1049- * GNU General Public License for more details.
1050- *
1051- * You should have received a copy of the GNU General Public License
1052- * along with this program. If not, see <http://www.gnu.org/licenses/>.
1053- */
1054-
1055-import QtQuick 2.0
1056-import Ubuntu.Components.Popups 0.1
1057-import Ubuntu.Components.ListItems 0.1 as ListItem
1058-
1059-/*!
1060-*/
1061-Popover {
1062- id: root
1063- /*!
1064- */
1065- property variant photo: null
1066- /*!
1067- */
1068- property CropInteractor cropper
1069-
1070- ///
1071- signal buttonPressed()
1072-
1073- Column {
1074- anchors {
1075- left: parent.left
1076- right: parent.right
1077- top: parent.top
1078- }
1079- ListItem.Standard {
1080- text: i18n.tr("Rotate")
1081- objectName: "rotateListItem"
1082- onClicked: {
1083- hide();
1084- photo.rotateRight();
1085- root.buttonPressed();
1086- }
1087- }
1088- ListItem.Standard {
1089- text: i18n.tr("Crop")
1090- objectName: "cropListItem"
1091- onClicked: {
1092- hide();
1093- cropper.show(photo);
1094- root.buttonPressed();
1095- }
1096- }
1097- ListItem.Standard {
1098- text: i18n.tr("Auto enhance")
1099- objectName: "enhanceListItem"
1100- onClicked: {
1101- hide();
1102- photo.autoEnhance();
1103- root.buttonPressed();
1104- }
1105- }
1106- ListItem.Standard {
1107- text: i18n.tr("Undo")
1108- objectName: "undoListItem"
1109- enabled: photo.canUndo
1110- onClicked: {
1111- hide();
1112- photo.undo();
1113- root.buttonPressed();
1114- }
1115- }
1116- ListItem.Standard {
1117- text: i18n.tr("Redo")
1118- objectName: "redoListItem"
1119- enabled: photo.canRedo
1120- onClicked: {
1121- hide();
1122- photo.redo();
1123- root.buttonPressed();
1124- }
1125- }
1126- ListItem.Standard {
1127- text: i18n.tr("Revert to original")
1128- objectName: "revertListItem"
1129- enabled: !photo.isOriginal
1130- onClicked: {
1131- hide();
1132- photo.revertToOriginal();
1133- root.buttonPressed();
1134- }
1135- }
1136- }
1137-}
1138
1139=== removed file 'rc/qml/MediaViewer/EditPreview.qml'
1140--- rc/qml/MediaViewer/EditPreview.qml 2013-06-13 12:13:00 +0000
1141+++ rc/qml/MediaViewer/EditPreview.qml 1970-01-01 00:00:00 +0000
1142@@ -1,177 +0,0 @@
1143-/*
1144- * Copyright (C) 2013 Canonical Ltd
1145- *
1146- * This program is free software: you can redistribute it and/or modify
1147- * it under the terms of the GNU General Public License version 3 as
1148- * published by the Free Software Foundation.
1149- *
1150- * This program is distributed in the hope that it will be useful,
1151- * but WITHOUT ANY WARRANTY; without even the implied warranty of
1152- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1153- * GNU General Public License for more details.
1154- *
1155- * You should have received a copy of the GNU General Public License
1156- * along with this program. If not, see <http://www.gnu.org/licenses/>.
1157- */
1158-
1159-import QtQuick 2.0
1160-
1161-/*!
1162- The EditPreview item show the prevew of an edit function.
1163- As it is using shader functionality for that, it's extrememly fast.
1164- */
1165-Item {
1166- id: root
1167-
1168- /// URL to the image that is edited
1169- property alias source: previewImage.source
1170- /// Value of the exposure
1171- property alias exposure: effectItem.exposure
1172-
1173- /// brightnes for the color balance
1174- property alias brightness: effectItem.b
1175- /// contrast for the color balance
1176- property alias contrast: effectItem.c
1177- /// saturation for the color balance
1178- property alias saturation: effectItem.s
1179- /// hue for the color balance
1180- property alias hue: effectItem.h
1181-
1182- /*!
1183- Sets the exposure as active preview
1184- */
1185- function useExposure() {
1186- effectItem.fragmentShader = root.exposureShader
1187- }
1188- /*!
1189- Sets the color balance as active preview
1190- */
1191- function useColorBalance() {
1192- effectItem.fragmentShader = root.colorBalanceShader
1193- }
1194-
1195- Image {
1196- id: previewImage
1197- anchors.fill: parent
1198- fillMode: Image.PreserveAspectFit
1199- }
1200-
1201- ShaderEffect
1202- {
1203- id: effectItem
1204- anchors.centerIn: previewImage
1205- width: previewImage.paintedWidth
1206- height: previewImage.paintedHeight
1207- blending: false
1208-
1209- property var src: previewImage
1210-
1211- property double exposure: 0.0
1212-
1213- property real b: 1.0
1214- property real c: 1.0
1215- property real s: 1.0
1216- property real h: 0.0
1217-
1218- property real cos_h: Math.cos(h * (3.1415926 / 180.0))
1219- property real sin_h: Math.sin(h * (3.1415926 / 180.0))
1220- property variant h1:
1221- Qt.vector4d(0.333333 * (1.0 - cos_h) + cos_h,
1222- 0.333333 * (1.0 - cos_h) + 0.57735 * sin_h,
1223- 0.333333 * (1.0 - cos_h) - 0.57735 * sin_h,
1224- 0.0)
1225- property variant h2:
1226- Qt.vector4d(0.333333 * (1.0 - cos_h) - 0.57735 * sin_h,
1227- 0.333333 * (1.0 - cos_h) + cos_h,
1228- 0.333333 * (1.0 - cos_h) + 0.57735 * sin_h,
1229- 0.0)
1230- property variant h3:
1231- Qt.vector4d(0.333333 * (1.0 - cos_h) + 0.57735 * sin_h,
1232- 0.333333 * (1.0 - cos_h) - 0.57735 * sin_h,
1233- 0.333333 * (1.0 - cos_h) + cos_h,
1234- 0.0)
1235- property variant s1:
1236- Qt.vector4d((1.0 - s) * 0.3086 + s,
1237- (1.0 - s) * 0.6094,
1238- (1.0 - s) * 0.0820,
1239- 0.0)
1240- property variant s2:
1241- Qt.vector4d((1.0 - s) * 0.3086,
1242- (1.0 - s) * 0.6094 + s,
1243- (1.0 - s) * 0.0820,
1244- 0.0)
1245- property variant s3:
1246- Qt.vector4d((1.0 - s) * 0.3086,
1247- (1.0 - s) * 0.6094,
1248- (1.0 - s) * 0.0820 + s,
1249- 0.0)
1250- property variant b1:
1251- Qt.vector4d(b, 0.0, 0.0, 0.0)
1252- property variant b2:
1253- Qt.vector4d(0.0, b, 0.0, 0.0)
1254- property variant b3:
1255- Qt.vector4d(0.0, 0.0, b, 0.0)
1256-
1257- property variant c1:
1258- Qt.vector4d(c, 0.0, 0.0, c * -0.5 + 0.5)
1259- property variant c2:
1260- Qt.vector4d(0.0, c, 0.0, c * -0.5 + 0.5)
1261- property variant c3:
1262- Qt.vector4d(0.0, 0.0, c, c * -0.5 + 0.5)
1263-
1264- vertexShader: "
1265- uniform mat4 qt_Matrix;
1266- attribute vec4 qt_Vertex;
1267- attribute vec2 qt_MultiTexCoord0;
1268- varying mediump vec2 coord;
1269- void main() {
1270- coord = qt_MultiTexCoord0;
1271- gl_Position = qt_Matrix * qt_Vertex;
1272- }"
1273-
1274- fragmentShader: root.exposureShader
1275- }
1276-
1277- property string exposureShader: "
1278- varying mediump vec2 coord;
1279- uniform sampler2D src;
1280- uniform mediump float exposure;
1281- uniform mediump float qt_Opacity;
1282- void main() {
1283- mediump float exp = clamp(exposure, -1.0, 1.0);
1284- mediump vec4 tex = texture2D(src, coord);
1285- tex.rgb += exp;
1286- gl_FragColor = tex * qt_Opacity;
1287- }"
1288-
1289- property string colorBalanceShader: "
1290- varying mediump vec2 coord;
1291- uniform sampler2D src;
1292- uniform mediump vec4 h1;
1293- uniform mediump vec4 h2;
1294- uniform mediump vec4 h3;
1295- uniform mediump vec4 s1;
1296- uniform mediump vec4 s2;
1297- uniform mediump vec4 s3;
1298- uniform mediump vec4 b1;
1299- uniform mediump vec4 b2;
1300- uniform mediump vec4 b3;
1301- uniform mediump vec4 c1;
1302- uniform mediump vec4 c2;
1303- uniform mediump vec4 c3;
1304- void main(void) {
1305- mediump vec4 tmp1;
1306- mediump vec4 tmp2;
1307- tmp1 = texture2D(src, coord);
1308- tmp2 = vec4(dot(h1, tmp1), dot(h2, tmp1), dot(h3, tmp1), 1.0);
1309- tmp1 = vec4(dot(s1, tmp2), dot(s2, tmp2), dot(s3, tmp2), 1.0);
1310- tmp2 = vec4(dot(b1, tmp1), dot(b2, tmp1), dot(b3, tmp1), 1.0);
1311- tmp1 = vec4(dot(c1, tmp2), dot(c2, tmp2), dot(c3, tmp2), 1.0);
1312- gl_FragColor = tmp1;
1313- }"
1314-
1315- MouseArea {
1316- anchors.fill: parent
1317- onClicked: {}
1318- }
1319-}
1320
1321=== added file 'rc/qml/MediaViewer/ExtrasPhotoEditorPage.qml'
1322--- rc/qml/MediaViewer/ExtrasPhotoEditorPage.qml 1970-01-01 00:00:00 +0000
1323+++ rc/qml/MediaViewer/ExtrasPhotoEditorPage.qml 2015-03-10 14:05:41 +0000
1324@@ -0,0 +1,43 @@
1325+import QtQuick 2.0
1326+import Ubuntu.Components 1.1
1327+import Ubuntu.Components.Extras 0.2 as Extras
1328+
1329+/*
1330+ * Copyright (C) 2014 Canonical Ltd
1331+ *
1332+ * This program is free software: you can redistribute it and/or modify
1333+ * it under the terms of the GNU General Public License version 3 as
1334+ * published by the Free Software Foundation.
1335+ *
1336+ * This program is distributed in the hope that it will be useful,
1337+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1338+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1339+ * GNU General Public License for more details.
1340+ *
1341+ * You should have received a copy of the GNU General Public License
1342+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1343+ */
1344+
1345+Page {
1346+ id: page
1347+ property string photo
1348+ signal done(bool photoWasModified)
1349+
1350+ title: i18n.tr("Edit Photo")
1351+
1352+ head.backAction: Action {
1353+ iconName: "back"
1354+ onTriggered: editor.close(true)
1355+ }
1356+ head.actions: editor.actions
1357+
1358+ Extras.PhotoEditor {
1359+ id: editor
1360+ anchors.fill: parent
1361+ onClosed: page.done(photoWasModified)
1362+ }
1363+
1364+ onActiveChanged: {
1365+ if (active) editor.open(page.photo)
1366+ }
1367+}
1368
1369=== modified file 'rc/qml/MediaViewer/GalleryPhotoComponent.qml'
1370--- rc/qml/MediaViewer/GalleryPhotoComponent.qml 2014-09-02 13:53:01 +0000
1371+++ rc/qml/MediaViewer/GalleryPhotoComponent.qml 2015-03-10 14:05:41 +0000
1372@@ -47,8 +47,8 @@
1373 // are smaller than preview's and automatically use that instead of a
1374 // full image load
1375
1376- // Load image using the Gallery image provider to ensure EXIF orientation
1377- return isPreview ? mediaSource.galleryPreviewPath : mediaSource.galleryPath
1378+ // Load image using the photo image provider to ensure EXIF orientation
1379+ return "image://" + (isPreview ? "thumbnailer/" : "photo:/") + mediaSource.path;
1380 }
1381
1382 /*!
1383
1384=== added file 'rc/qml/MediaViewer/GalleryPhotoEditorPage.qml'
1385--- rc/qml/MediaViewer/GalleryPhotoEditorPage.qml 1970-01-01 00:00:00 +0000
1386+++ rc/qml/MediaViewer/GalleryPhotoEditorPage.qml 2015-03-10 14:05:41 +0000
1387@@ -0,0 +1,42 @@
1388+import QtQuick 2.0
1389+import Ubuntu.Components 1.1
1390+
1391+/*
1392+ * Copyright (C) 2014 Canonical Ltd
1393+ *
1394+ * This program is free software: you can redistribute it and/or modify
1395+ * it under the terms of the GNU General Public License version 3 as
1396+ * published by the Free Software Foundation.
1397+ *
1398+ * This program is distributed in the hope that it will be useful,
1399+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1400+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1401+ * GNU General Public License for more details.
1402+ *
1403+ * You should have received a copy of the GNU General Public License
1404+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1405+ */
1406+
1407+Page {
1408+ id: page
1409+ property string photo
1410+ signal done(bool photoWasModified)
1411+
1412+ title: i18n.tr("Edit Photo")
1413+
1414+ head.backAction: Action {
1415+ iconName: "back"
1416+ onTriggered: editor.close(true)
1417+ }
1418+ head.actions: editor.actions
1419+
1420+ PhotoEditor {
1421+ id: editor
1422+ anchors.fill: parent
1423+ onClosed: page.done(photoWasModified)
1424+ }
1425+
1426+ onActiveChanged: {
1427+ if (active) editor.open(page.photo)
1428+ }
1429+}
1430
1431=== modified file 'rc/qml/MediaViewer/MediaViewer.qml'
1432--- rc/qml/MediaViewer/MediaViewer.qml 2015-02-17 11:58:34 +0000
1433+++ rc/qml/MediaViewer/MediaViewer.qml 2015-03-10 14:05:41 +0000
1434@@ -168,13 +168,10 @@
1435 }
1436
1437 // Don't allow flicking while the chrome is actively displaying a popup
1438- // menu, or the image is zoomed, or we're cropping. When images are zoomed,
1439- // mouse drags should pan, not flick. Also don't flick during parameterized
1440- // HUD action to prevent photo from changing during the action
1441+ // menu, or the image is zoomed. When images are zoomed,
1442+ // mouse drags should pan, not flick.
1443 interactive: currentItem != null &&
1444- !currentItem.userInteracting &&
1445- cropper.state == "hidden" &&
1446- !editHUD.actionActive
1447+ !currentItem.userInteracting // FIXME: disable when editing ??
1448
1449 Component {
1450 id: contentItemComp
1451@@ -209,21 +206,6 @@
1452 }
1453
1454 Component {
1455- id: editPopoverComponent
1456- EditPopover {
1457- id: editPopover
1458- objectName: "editPopover"
1459- visible: false
1460- photo: galleryPhotoViewer.media
1461- cropper: viewerWrapper.cropper
1462-
1463- onButtonPressed: {
1464- viewerWrapper.tools.opened = false;
1465- }
1466- }
1467- }
1468-
1469- Component {
1470 id: deleteDialog
1471 Dialog {
1472 id: dialogue
1473@@ -312,118 +294,7 @@
1474 onNewAlbumPicked: {
1475 viewerWrapper.closeRequested();
1476 }
1477- }
1478-
1479- property alias cropper: cropper
1480- CropInteractor {
1481- id: cropper
1482-
1483- property var targetPhoto
1484-
1485- function show(photo) {
1486- targetPhoto = photo;
1487-
1488- fadeOutPhotoAnimation.running = true;
1489- }
1490-
1491- function hide() {
1492- state = "hidden";
1493- galleryPhotoViewer.opacity = 0.0;
1494- galleryPhotoViewer.visible = true;
1495- fadeInPhotoAnimation.running = true;
1496- }
1497-
1498- state: "hidden"
1499- states: [
1500- State { name: "shown";
1501- PropertyChanges { target: cropper; visible: true; }
1502- PropertyChanges { target: cropper; opacity: 1.0; }
1503- },
1504- State { name: "hidden";
1505- PropertyChanges { target: cropper; visible: false; }
1506- PropertyChanges { target: cropper; opacity: 0.0; }
1507- }
1508- ]
1509-
1510- Behavior on opacity {
1511- NumberAnimation { duration: Gallery.FAST_DURATION }
1512- }
1513-
1514- anchors.fill: parent
1515-
1516- MouseArea {
1517- id: blocker
1518-
1519- visible: cropper.state == "shown"
1520- anchors.fill: parent
1521-
1522- onClicked: { }
1523- }
1524-
1525- onCanceled: {
1526- photo.cancelCropping();
1527- hide();
1528- targetPhoto = null;
1529- }
1530-
1531- onCropped: {
1532- var qtRect = Qt.rect(rect.x, rect.y, rect.width, rect.height);
1533- photo.crop(qtRect);
1534- hide();
1535- targetPhoto = null;
1536- }
1537-
1538- onOpacityChanged: {
1539- if (opacity == 1.0)
1540- galleryPhotoViewer.visible = false
1541- }
1542-
1543- NumberAnimation {
1544- id: fadeOutPhotoAnimation
1545-
1546- from: 1.0
1547- to: 0.0
1548- target: galleryPhotoViewer
1549- property: "opacity"
1550- duration: Gallery.FAST_DURATION
1551- easing.type: Easing.InOutQuad
1552-
1553- onRunningChanged: {
1554- if (running == false) {
1555- var ratio_crop_rect = cropper.targetPhoto.prepareForCropping();
1556- cropper.enter(cropper.targetPhoto, ratio_crop_rect);
1557- cropper.state = "shown";
1558- }
1559- }
1560- }
1561-
1562- NumberAnimation {
1563- id: fadeInPhotoAnimation
1564-
1565- from: 0.0
1566- to: 1.0
1567- target: galleryPhotoViewer
1568- property: "opacity"
1569- duration: Gallery.FAST_DURATION
1570- easing.type: Easing.InOutQuad
1571- }
1572- }
1573-
1574- EditPreview {
1575- id: editPreview
1576- objectName: "editPreview"
1577- anchors.fill: parent
1578- source: galleryPhotoViewer.media ? galleryPhotoViewer.media.galleryPreviewPath : ""
1579-
1580- visible: editHUD.actionActive
1581-
1582- exposure: editHUD.exposureValue
1583-
1584- brightness: editHUD.brightness
1585- contrast: editHUD.contrast
1586- saturation: editHUD.saturation
1587- hue: editHUD.hue
1588- }
1589+ }
1590
1591 ActivityIndicator {
1592 id: busySpinner
1593@@ -433,13 +304,6 @@
1594 running: visible
1595 }
1596
1597- EditingHUD {
1598- id: editHUD
1599- photo: galleryPhotoViewer.media
1600- onExposureActivated: editPreview.useExposure()
1601- onColorBalanceActivated: editPreview.useColorBalance()
1602- }
1603-
1604 Item {
1605 id: d
1606
1607@@ -449,7 +313,23 @@
1608 text: i18n.tr("Edit")
1609 iconSource: "../../img/edit.png"
1610 enabled: galleryPhotoViewer.media.type === MediaSource.Photo && galleryPhotoViewer.media.canBeEdited
1611- onTriggered: PopupUtils.open(editPopoverComponent, null);
1612+ onTriggered: {
1613+ var path = galleryPhotoViewer.media.path.toString();
1614+ path = path.replace("file://", "")
1615+ var editor;
1616+ try {
1617+ Qt.createQmlObject('import QtQuick 2.0; import Ubuntu.Components.Extras 0.2; Item {}', viewerWrapper);
1618+ console.log("Loading PhotoEditor Components from Extras");
1619+ editor = overview.pushPage(Qt.resolvedUrl("ExtrasPhotoEditorPage.qml"), { photo: path });
1620+ } catch (e) {
1621+ console.log("Loading PhotoEditor Components from Gallery code");
1622+ editor = overview.pushPage(Qt.resolvedUrl("GalleryPhotoEditorPage.qml"), { photo: path });
1623+ }
1624+ editor.done.connect(function(photoWasModified) {
1625+ if (photoWasModified) galleryPhotoViewer.media.dataChanged();
1626+ overview.popPage();
1627+ });
1628+ }
1629 },
1630 Action {
1631 objectName: "addButton"
1632
1633=== added directory 'rc/qml/MediaViewer/PhotoEditor'
1634=== added file 'rc/qml/MediaViewer/PhotoEditor.qml'
1635--- rc/qml/MediaViewer/PhotoEditor.qml 1970-01-01 00:00:00 +0000
1636+++ rc/qml/MediaViewer/PhotoEditor.qml 2015-03-10 14:05:41 +0000
1637@@ -0,0 +1,230 @@
1638+/*
1639+ * Copyright (C) 2014 Canonical Ltd
1640+ *
1641+ * This program is free software: you can redistribute it and/or modify
1642+ * it under the terms of the GNU General Public License version 3 as
1643+ * published by the Free Software Foundation.
1644+ *
1645+ * This program is distributed in the hope that it will be useful,
1646+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1647+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1648+ * GNU General Public License for more details.
1649+ *
1650+ * You should have received a copy of the GNU General Public License
1651+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1652+ */
1653+
1654+import QtQuick 2.3
1655+import Ubuntu.Components 1.1
1656+import Ubuntu.Components.Popups 1.0
1657+import Gallery 1.0
1658+import "PhotoEditor"
1659+
1660+Item {
1661+ id: editor
1662+ property string photo
1663+ property bool modified: stack.modified
1664+
1665+ signal closed(bool photoWasModified)
1666+
1667+ property list<Action> actions
1668+ actions: [stack.undoAction, stack.redoAction]
1669+
1670+ EditStack {
1671+ id: stack
1672+ data: photoData
1673+ actionsEnabled: !exposureSelector.visible && !cropper.visible && !photoData.busy
1674+ onRevertRequested: PopupUtils.open(revertPromptComponent)
1675+ }
1676+
1677+ property list<Action> toolActions: [
1678+ Action {
1679+ objectName: "cropButton"
1680+ text: i18n.tr("Crop")
1681+ iconSource: Qt.resolvedUrl("PhotoEditor/assets/edit_crop.png")
1682+ onTriggered: {
1683+ photoData.isLongOperation = false;
1684+ cropper.start("image://photo/" + photoData.path);
1685+ }
1686+ },
1687+ Action {
1688+ objectName: "rotateButton"
1689+ text: i18n.tr("Rotate")
1690+ iconSource: Qt.resolvedUrl("PhotoEditor/assets/edit_rotate_left.png")
1691+ onTriggered: {
1692+ photoData.isLongOperation = false;
1693+ photoData.rotateRight()
1694+ }
1695+ }
1696+ ]
1697+
1698+ function close(saveIfModified) {
1699+ stack.endEditingSession(saveIfModified);
1700+ editor.closed(editor.modified);
1701+ }
1702+
1703+ function open(photo) {
1704+ editor.photo = photo;
1705+ stack.startEditingSession(photo);
1706+ photoData.path = stack.currentFile;
1707+ image.source = "image://photo/" + photoData.path;
1708+ }
1709+
1710+ Rectangle {
1711+ color: "black"
1712+ anchors.fill: parent
1713+ }
1714+
1715+ Image {
1716+ id: image
1717+ anchors.fill: parent
1718+ asynchronous: true
1719+ cache: false
1720+ source: photoData.path ? "image://photo/" + photoData.path : ""
1721+ fillMode: Image.PreserveAspectFit
1722+ sourceSize {
1723+ width: image.width
1724+ height: image.height
1725+ }
1726+
1727+ function reload() {
1728+ image.asynchronous = false;
1729+ image.source = "";
1730+ image.asynchronous = true;
1731+ image.source = "image://photo/" + photoData.path;
1732+ }
1733+ }
1734+
1735+ GalleryPhotoData {
1736+ id: photoData
1737+ onDataChanged: image.reload()
1738+ property bool isLongOperation: false
1739+
1740+ onEditFinished: {
1741+ console.log("Edit finished")
1742+ // If we are editing exposure we don't need to checkpoint at every
1743+ // edit, and the exposure UI will checkpoint when the user confirms.
1744+ if (exposureSelector.opacity > 0) exposureSelector.reload()
1745+ else stack.checkpoint()
1746+ }
1747+ }
1748+
1749+ Loader {
1750+ id: cropper
1751+
1752+ anchors.fill: parent
1753+
1754+ opacity: 0.0
1755+ visible: opacity > 0
1756+ Behavior on opacity { UbuntuNumberAnimation { } }
1757+
1758+ Connections {
1759+ target: cropper.item
1760+ ignoreUnknownSignals: true
1761+ onCropped: {
1762+ var qtRect = Qt.rect(rect.x, rect.y, rect.width, rect.height);
1763+ photoData.crop(qtRect);
1764+ cropper.opacity = 0.0;
1765+ cropper.source = ""
1766+ }
1767+ onCanceled: {
1768+ cropper.opacity = 0.0;
1769+ cropper.source = ""
1770+ }
1771+ }
1772+
1773+ function start(target) {
1774+ source = "PhotoEditor/CropInteractor.qml";
1775+ item.targetPhoto = target;
1776+ }
1777+
1778+ onLoaded: opacity = 1.0
1779+ }
1780+
1781+ ExposureAdjuster {
1782+ id: exposureSelector
1783+ anchors.fill: parent
1784+ opacity: 0.0
1785+ enabled: !photoData.busy
1786+ onExposureChanged: {
1787+ // Restore the starting version of the image, otherwise we will
1788+ // accumulate compensations over the previous ones.
1789+ stack.restoreSnapshot(stack.level)
1790+ photoData.exposureCompensation(exposure)
1791+ }
1792+ onConfirm: {
1793+ stack.checkpoint();
1794+ exposureSelector.opacity = 0.0
1795+ }
1796+ onCancel: {
1797+ stack.restoreSnapshot(stack.level)
1798+ exposureSelector.opacity = 0.0
1799+ }
1800+ visible: opacity > 0
1801+ }
1802+
1803+ ActionsBar {
1804+ id: actionsBar
1805+ objectName: "editorActionsBar"
1806+ anchors.bottom: parent.bottom
1807+ anchors.left: parent.left
1808+ anchors.right: parent.right
1809+
1810+ visible: opacity > 0.0
1811+ opacity: (exposureSelector.opacity == 0 && cropper.opacity == 0) ? 1.0 : 0.0
1812+
1813+ enabled: !photoData.busy
1814+ toolActions: {
1815+ // This is necessary because QML does not let us declare a list with
1816+ // mixed component declarations and identifiers, like this:
1817+ // property list<Action> foo: { Action{}, someOtherAction }
1818+ var list = [];
1819+ for (var i = 0; i < editor.toolActions.length; i++)
1820+ list.push(editor.toolActions[i]);
1821+ list.push(stack.revertAction);
1822+ return list;
1823+ }
1824+
1825+ Behavior on opacity { UbuntuNumberAnimation {} }
1826+ }
1827+
1828+ Component {
1829+ id: revertPromptComponent
1830+ Dialog {
1831+ id: revertPrompt
1832+ objectName: "revertPromptDialog"
1833+ title: i18n.tr("Revert to original")
1834+ text: i18n.tr("This will undo all edits, including those from previous sessions.")
1835+
1836+ Row {
1837+ id: row
1838+ width: parent.width
1839+ spacing: units.gu(1)
1840+ Button {
1841+ objectName: "cancelRevertButton"
1842+ width: parent.width/2
1843+ text: i18n.tr("Cancel")
1844+ onClicked: PopupUtils.close(revertPrompt)
1845+ }
1846+ Button {
1847+ objectName: "confirmRevertButton"
1848+ width: parent.width/2
1849+ text: i18n.tr("Revert Photo")
1850+ color: UbuntuColors.green
1851+ onClicked: {
1852+ PopupUtils.close(revertPrompt)
1853+ stack.revertToPristine()
1854+ }
1855+ }
1856+ }
1857+ }
1858+ }
1859+
1860+ BusyIndicator {
1861+ id: busyIndicator
1862+ anchors.centerIn: parent
1863+ text: i18n.tr("Enhancing photo...")
1864+ running: photoData.busy
1865+ longOperation: photoData.isLongOperation
1866+ }
1867+}
1868
1869=== added file 'rc/qml/MediaViewer/PhotoEditor/ActionsBar.qml'
1870--- rc/qml/MediaViewer/PhotoEditor/ActionsBar.qml 1970-01-01 00:00:00 +0000
1871+++ rc/qml/MediaViewer/PhotoEditor/ActionsBar.qml 2015-03-10 14:05:41 +0000
1872@@ -0,0 +1,88 @@
1873+/*
1874+ * Copyright (C) 2014 Canonical Ltd
1875+ *
1876+ * This program is free software: you can redistribute it and/or modify
1877+ * it under the terms of the GNU General Public License version 3 as
1878+ * published by the Free Software Foundation.
1879+ *
1880+ * This program is distributed in the hope that it will be useful,
1881+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1882+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1883+ * GNU General Public License for more details.
1884+ *
1885+ * You should have received a copy of the GNU General Public License
1886+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1887+ */
1888+
1889+import QtQuick 2.3
1890+import Ubuntu.Components 1.1
1891+import Ubuntu.Components.ListItems 1.0 as ListItem
1892+
1893+Column {
1894+ id: bar
1895+ property list<Action> toolActions
1896+ property list<Action> filterActions
1897+ property bool enabled
1898+
1899+ height: (filtersBar.visible) ? units.gu(20) : units.gu(6)
1900+
1901+ Item {
1902+ anchors.left: parent.left
1903+ anchors.right: parent.right
1904+ height: units.gu(6)
1905+
1906+ Rectangle {
1907+ anchors.fill: parent
1908+ color: "black"
1909+ opacity: 0.6
1910+ }
1911+
1912+ ListView {
1913+ id: toolsBar
1914+ anchors.fill: parent
1915+ orientation: ListView.Horizontal
1916+ model: toolActions
1917+
1918+ delegate: AbstractButton {
1919+ width: units.gu(8)
1920+ anchors.top: parent.top
1921+ anchors.bottom: parent.bottom
1922+ action: modelData
1923+ enabled: bar.enabled
1924+
1925+ Icon {
1926+ anchors.centerIn: parent
1927+ name: modelData.iconName
1928+ source: modelData.iconSource
1929+ width: units.gu(3)
1930+ height: units.gu(3)
1931+ opacity: modelData.enabled && parent.enabled ? 1.0 : 0.5
1932+ }
1933+ }
1934+ }
1935+ }
1936+
1937+ Rectangle {
1938+ anchors.left: parent.left
1939+ anchors.right: parent.right
1940+ height: units.gu(14)
1941+ color: "black"
1942+
1943+ ListView {
1944+ id: filtersBar
1945+ visible: filterActions.length > 0
1946+
1947+ orientation: ListView.Horizontal
1948+ model: filterActions
1949+
1950+ delegate: ListItem.Standard {
1951+ width: parent.height
1952+ anchors.top: parent.top
1953+ anchors.bottom: parent.bottom
1954+ action: modelData
1955+ iconFrame: false
1956+ enabled: bar.enabled
1957+ }
1958+ }
1959+ }
1960+}
1961
1962=== added file 'rc/qml/MediaViewer/PhotoEditor/BusyIndicator.qml'
1963--- rc/qml/MediaViewer/PhotoEditor/BusyIndicator.qml 1970-01-01 00:00:00 +0000
1964+++ rc/qml/MediaViewer/PhotoEditor/BusyIndicator.qml 2015-03-10 14:05:41 +0000
1965@@ -0,0 +1,56 @@
1966+/*
1967+ * Copyright (C) 2015 Canonical Ltd
1968+ *
1969+ * This program is free software: you can redistribute it and/or modify
1970+ * it under the terms of the GNU General Public License version 3 as
1971+ * published by the Free Software Foundation.
1972+ *
1973+ * This program is distributed in the hope that it will be useful,
1974+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1975+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1976+ * GNU General Public License for more details.
1977+ *
1978+ * You should have received a copy of the GNU General Public License
1979+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1980+ */
1981+
1982+import QtQuick 2.0
1983+import Ubuntu.Components 0.1
1984+
1985+Item {
1986+ id: busy
1987+ width: childrenRect.width
1988+ height: childrenRect.height
1989+ property alias text: label.text
1990+ property alias running: spinner.running
1991+ property bool longOperation: false
1992+
1993+ visible: running
1994+
1995+ UbuntuShape {
1996+ color: "white"
1997+ anchors.centerIn: parent
1998+ width: parent.width + units.gu(4)
1999+ height: parent.height + units.gu(4)
2000+ opacity: longOperation ? 0.75 : 0
2001+ }
2002+
2003+ Column {
2004+ id: column
2005+ anchors.centerIn: parent
2006+ width: childrenRect.width
2007+ spacing: units.gu(2)
2008+
2009+ ActivityIndicator {
2010+ id: spinner
2011+ anchors.horizontalCenter: parent.horizontalCenter
2012+ }
2013+
2014+ Label {
2015+ id: label
2016+ anchors.horizontalCenter: parent.horizontalCenter
2017+ horizontalAlignment: Text.AlignHCenter
2018+ visible: longOperation
2019+ }
2020+ }
2021+}
2022
2023=== added file 'rc/qml/MediaViewer/PhotoEditor/CropCorner.qml'
2024--- rc/qml/MediaViewer/PhotoEditor/CropCorner.qml 1970-01-01 00:00:00 +0000
2025+++ rc/qml/MediaViewer/PhotoEditor/CropCorner.qml 2015-03-10 14:05:41 +0000
2026@@ -0,0 +1,66 @@
2027+/*
2028+ * Copyright (C) 2012 Canonical Ltd
2029+ *
2030+ * This program is free software: you can redistribute it and/or modify
2031+ * it under the terms of the GNU General Public License version 3 as
2032+ * published by the Free Software Foundation.
2033+ *
2034+ * This program is distributed in the hope that it will be useful,
2035+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2036+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2037+ * GNU General Public License for more details.
2038+ *
2039+ * You should have received a copy of the GNU General Public License
2040+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2041+ *
2042+ * Authors:
2043+ * Charles Lindsay <chaz@yorba.org>
2044+ */
2045+
2046+import QtQuick 2.3
2047+import Ubuntu.Components 1.1
2048+
2049+// A corner of a CropFrame.
2050+Item {
2051+ id: cropCorner
2052+
2053+ /*!
2054+ */
2055+ signal dragged(real dx, real dy)
2056+ /*!
2057+ */
2058+ signal dragStarted()
2059+ /*!
2060+ */
2061+ signal dragCompleted()
2062+
2063+ /*!
2064+ */
2065+ property bool isLeft: true
2066+ /*!
2067+ */
2068+ property bool isTop: true
2069+
2070+ x: isLeft ? -(width/2) : parent.width - (width/2)
2071+ y: isTop ? -(width/2) : parent.height - (width/2)
2072+ width: handle.width
2073+ height: handle.height
2074+
2075+ Image {
2076+ id: handle
2077+ anchors.centerIn: parent
2078+ source: Qt.resolvedUrl("assets/crop-handle.png")
2079+ }
2080+
2081+ CropDragArea {
2082+ anchors.centerIn: parent
2083+ width: handle.width + units.gu(2)
2084+ height: handle.height + units.gu(2)
2085+
2086+ onDragged: cropCorner.dragged(dx, dy)
2087+
2088+ onDragStarted: cropCorner.dragStarted()
2089+
2090+ onDragCompleted: cropCorner.dragCompleted()
2091+ }
2092+}
2093
2094=== added file 'rc/qml/MediaViewer/PhotoEditor/CropDragArea.qml'
2095--- rc/qml/MediaViewer/PhotoEditor/CropDragArea.qml 1970-01-01 00:00:00 +0000
2096+++ rc/qml/MediaViewer/PhotoEditor/CropDragArea.qml 2015-03-10 14:05:41 +0000
2097@@ -0,0 +1,56 @@
2098+/*
2099+ * Copyright (C) 2012 Canonical Ltd
2100+ *
2101+ * This program is free software: you can redistribute it and/or modify
2102+ * it under the terms of the GNU General Public License version 3 as
2103+ * published by the Free Software Foundation.
2104+ *
2105+ * This program is distributed in the hope that it will be useful,
2106+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2107+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2108+ * GNU General Public License for more details.
2109+ *
2110+ * You should have received a copy of the GNU General Public License
2111+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2112+ *
2113+ * Authors:
2114+ * Charles Lindsay <chaz@yorba.org>
2115+ */
2116+
2117+import QtQuick 2.3
2118+
2119+// A MouseArea meant to drag a corner/edge of a crop area.
2120+MouseArea {
2121+ id: cropDragArea
2122+
2123+ /*!
2124+ */
2125+ signal dragged(real dx, real dy)
2126+ /*!
2127+ */
2128+ signal dragStarted()
2129+ /*!
2130+ */
2131+ signal dragCompleted()
2132+
2133+ // Since we're usually moving this area with the mouse in response to
2134+ // dragging, we don't need to capture the last x/y, just where it was
2135+ // grabbed.
2136+ property real grabX: -1
2137+ /*!
2138+ */
2139+ property real grabY: -1
2140+
2141+ onPressed: {
2142+ dragStarted();
2143+
2144+ grabX = mouse.x;
2145+ grabY = mouse.y;
2146+ }
2147+
2148+ onReleased: {
2149+ dragCompleted();
2150+ }
2151+
2152+ onPositionChanged: cropDragArea.dragged(mouse.x - grabX, mouse.y - grabY)
2153+}
2154
2155=== added file 'rc/qml/MediaViewer/PhotoEditor/CropInteractor.qml'
2156--- rc/qml/MediaViewer/PhotoEditor/CropInteractor.qml 1970-01-01 00:00:00 +0000
2157+++ rc/qml/MediaViewer/PhotoEditor/CropInteractor.qml 2015-03-10 14:05:41 +0000
2158@@ -0,0 +1,140 @@
2159+/*
2160+ * Copyright (C) 2012 Canonical Ltd
2161+ *
2162+ * This program is free software: you can redistribute it and/or modify
2163+ * it under the terms of the GNU General Public License version 3 as
2164+ * published by the Free Software Foundation.
2165+ *
2166+ * This program is distributed in the hope that it will be useful,
2167+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2168+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2169+ * GNU General Public License for more details.
2170+ *
2171+ * You should have received a copy of the GNU General Public License
2172+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2173+ *
2174+ * Authors:
2175+ * Charles Lindsay <chaz@yorba.org>
2176+ * Lucas Beeler <lucas@yorba.org>
2177+ */
2178+
2179+import QtQuick 2.3
2180+import Ubuntu.Components 1.1
2181+import "GraphicsRoutines.js" as GraphicsRoutines
2182+
2183+/*!
2184+*/
2185+Rectangle {
2186+ id: cropInteractor
2187+ objectName: "cropInteractor"
2188+
2189+ color: "black"
2190+
2191+ property alias targetPhoto: original.source
2192+
2193+ property string matteColor: "black"
2194+ property real matteOpacity: 0.6
2195+
2196+ // Note: each element of the cropped rect will be in the range [0,1], since
2197+ // in the UI we aren't using direct photo pixel values.
2198+ signal cropped(variant rect)
2199+ signal canceled()
2200+
2201+ function computeRectSet() {
2202+ var actualImage = Qt.rect(
2203+ (original.width - original.paintedWidth) / 2.0,
2204+ (original.height - original.paintedHeight) / 2.0,
2205+ original.paintedWidth,
2206+ original.paintedHeight
2207+ );
2208+ var photoPreview = GraphicsRoutines.fitRect(viewport, actualImage);
2209+
2210+ var unfitCrop = Qt.rect(0, 0, photoPreview.width, photoPreview.height);
2211+ var cropFrame = GraphicsRoutines.fitRect(viewport, unfitCrop);
2212+
2213+ var photoExtent = Qt.rect(cropFrame.x, cropFrame.y,
2214+ cropFrame.scaleFactor * photoPreview.width,
2215+ cropFrame.scaleFactor * photoPreview.height);
2216+
2217+ return {
2218+ photoPreview: photoPreview,
2219+ cropFrame: cropFrame,
2220+ photoExtent: photoExtent,
2221+ photoExtentScale: cropFrame.scaleFactor
2222+ };
2223+ }
2224+
2225+ Item {
2226+ id: viewport
2227+
2228+ anchors.fill: parent
2229+ anchors.margins: units.gu(6)
2230+ z: 1
2231+ }
2232+
2233+ CropOverlay {
2234+ id: overlay
2235+ objectName: "cropOverlay"
2236+
2237+ property real minSize: units.gu(4)
2238+
2239+ anchors.fill: parent;
2240+ visible: false;
2241+
2242+ photo: original
2243+ viewport: viewport
2244+
2245+ matteColor: cropInteractor.matteColor
2246+ matteOpacity: cropInteractor.matteOpacity
2247+
2248+ z: 16
2249+
2250+ onMatteRegionPressed: {
2251+ cropInteractor.canceled();
2252+ }
2253+
2254+ onCropButtonPressed: {
2255+ original.visible = false;
2256+ overlay.visible = false;
2257+ original.scale = 1.0;
2258+ var r = overlay.getRelativeFrameRect()
2259+ cropInteractor.cropped(overlay.getRelativeFrameRect());
2260+ }
2261+ }
2262+
2263+ Image {
2264+ id: original
2265+
2266+ x: viewport.x
2267+ y: viewport.y
2268+ width: viewport.width
2269+ height: viewport.height
2270+ transformOrigin: Item.TopLeft
2271+ fillMode: Image.PreserveAspectFit
2272+ cache: false
2273+ sourceSize {
2274+ width: original.width
2275+ height: original.height
2276+ }
2277+
2278+ onStatusChanged: {
2279+ if (status == Image.Ready) {
2280+ var rects = computeRectSet();
2281+
2282+ overlay.initialFrameX = rects.cropFrame.x;
2283+ overlay.initialFrameY = rects.cropFrame.y;
2284+ overlay.initialFrameWidth = rects.cropFrame.width;
2285+ overlay.initialFrameHeight = rects.cropFrame.height;
2286+
2287+ overlay.resetFor(rects);
2288+ overlay.visible = true;
2289+
2290+ x = rects.photoExtent.x;
2291+ y = rects.photoExtent.y;
2292+ width = rects.photoPreview.width;
2293+ height = rects.photoPreview.height;
2294+ scale = rects.photoExtentScale;
2295+ }
2296+ }
2297+ }
2298+}
2299
2300=== added file 'rc/qml/MediaViewer/PhotoEditor/CropOverlay.qml'
2301--- rc/qml/MediaViewer/PhotoEditor/CropOverlay.qml 1970-01-01 00:00:00 +0000
2302+++ rc/qml/MediaViewer/PhotoEditor/CropOverlay.qml 2015-03-10 14:05:41 +0000
2303@@ -0,0 +1,581 @@
2304+/*
2305+ * Copyright (C) 2012 Canonical Ltd
2306+ *
2307+ * This program is free software: you can redistribute it and/or modify
2308+ * it under the terms of the GNU General Public License version 3 as
2309+ * published by the Free Software Foundation.
2310+ *
2311+ * This program is distributed in the hope that it will be useful,
2312+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2313+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2314+ * GNU General Public License for more details.
2315+ *
2316+ * You should have received a copy of the GNU General Public License
2317+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2318+ *
2319+ * Authors:
2320+ * Charles Lindsay <chaz@yorba.org>
2321+ * Lucas Beeler <lucas@yorba.org>
2322+ */
2323+
2324+import QtQuick 2.3
2325+import Ubuntu.Components 1.1
2326+import "GraphicsRoutines.js" as GraphicsRoutines
2327+
2328+/* A CropOverlay is a semi-transparent surface that floats over the photo. It
2329+ * serves two purposes. First, it provides visual cueing as to what region of
2330+ * the photo's surface will be preserved when the crop operation is applied.
2331+ * The preserved region is the region that falls inside of the CropOverlay's
2332+ * frame. Second, the CropOverlay allows the user to manipulate the
2333+ * geometry of the crop frame, to chage its location, width, and height. The
2334+ * geometry of the crop frame is reinforced by a key visual cue: the region of
2335+ * the photo outside of the crop frame is drawn with a semi-transparent, smoked
2336+ * matte on top of it. This matte surrounds the crop frame.
2337+ */
2338+Item {
2339+ id: cropOverlay
2340+
2341+ // public properties
2342+ /*!
2343+ */
2344+ property Item viewport
2345+ /*!
2346+ */
2347+ property Item photo
2348+ /*!
2349+ */
2350+ property string matteColor: "red"
2351+ /*!
2352+ */
2353+ property real matteOpacity: 0.85
2354+ /*!
2355+ */
2356+ property int initialFrameX: -1
2357+ /*!
2358+ */
2359+ property int initialFrameY: -1
2360+ /*!
2361+ */
2362+ property int initialFrameWidth: -1
2363+ /*!
2364+ */
2365+ property int initialFrameHeight: -1
2366+
2367+ // private properties -- Frame Fit Animation parameters
2368+ property real interpolationFactor: 1.0
2369+ /*!
2370+ */
2371+ property variant startFrame
2372+ /*!
2373+ */
2374+ property variant endFrame
2375+ /*!
2376+ */
2377+ property variant startPhoto
2378+ /*!
2379+ */
2380+ property real referencePhotoWidth: -1
2381+ /*!
2382+ */
2383+ property real referencePhotoHeight: -1
2384+ /*!
2385+ */
2386+ property real endPhotoX
2387+ /*!
2388+ */
2389+ property real endPhotoY
2390+ /*!
2391+ */
2392+ property real endPhotoWidth
2393+ /*!
2394+ */
2395+ property real endPhotoHeight
2396+
2397+ /*!
2398+ */
2399+ signal userAlteredFrame()
2400+ /*!
2401+ */
2402+ signal runFrameFitAnimation()
2403+ /*!
2404+ */
2405+ signal matteRegionPressed()
2406+ /*!
2407+ */
2408+ signal cropButtonPressed()
2409+
2410+ /*!
2411+ */
2412+ function resetFor(rectSet) {
2413+ if (initialFrameX != -1 && initialFrameY != -1 && initialFrameWidth != -1 &&
2414+ initialFrameHeight != -1) {
2415+ frame.x = rectSet.cropFrame.x;
2416+ frame.y = rectSet.cropFrame.y;
2417+ frame.width = rectSet.cropFrame.width;
2418+ frame.height = rectSet.cropFrame.height;
2419+ photoExtent.x = rectSet.photoExtent.x;
2420+ photoExtent.y = rectSet.photoExtent.y;
2421+ photoExtent.width = rectSet.photoExtent.width;
2422+ photoExtent.height = rectSet.photoExtent.height;
2423+ referencePhotoWidth = rectSet.photoPreview.width;
2424+ referencePhotoHeight = rectSet.photoPreview.height;
2425+ }
2426+ }
2427+
2428+ /* Return the (x, y) position and the width and height of the viewport
2429+ */
2430+ function getViewportExtentRect() {
2431+ return GraphicsRoutines.cloneRect(viewport);
2432+ }
2433+
2434+ /* Return the (x, y) position and the width and height of the photoExtent.
2435+ * The photoExtent is the on-screen region that holds the original photo
2436+ * preview.
2437+ */
2438+ function getPhotoExtentRect() {
2439+ return GraphicsRoutines.cloneRect(photoExtent);
2440+ }
2441+
2442+ /*!
2443+ */
2444+ function getRelativeFrameRect() {
2445+ return GraphicsRoutines.getRelativeRect(frame.getExtentRect(),
2446+ getPhotoExtentRect());
2447+ }
2448+
2449+ anchors.fill: parent
2450+
2451+ Item {
2452+ id: photoExtent
2453+
2454+ property real panStartX
2455+ property real panStartY
2456+
2457+ function startPan() {
2458+ panStartX = x;
2459+ panStartY = y;
2460+ }
2461+
2462+ // 'deltaX' and 'deltaY' are offsets relative to the pan start point
2463+ function updatePan(deltaX, deltaY) {
2464+ var newX = panStartX + deltaX;
2465+ var newY = panStartY + deltaY;
2466+
2467+ x = GraphicsRoutines.clamp(newX, frame.x + frame.width -
2468+ photoExtent.width, frame.x);
2469+ y = GraphicsRoutines.clamp(newY, frame.y + frame.height -
2470+ photoExtent.height, frame.y);
2471+ }
2472+
2473+ function stopPan() {
2474+ }
2475+
2476+ x: initialFrameX
2477+ y: initialFrameY
2478+ width: initialFrameWidth
2479+ height: initialFrameHeight
2480+ z: 1
2481+
2482+ onXChanged: {
2483+ if (photo)
2484+ photo.x = x;
2485+ }
2486+
2487+ onYChanged: {
2488+ if (photo)
2489+ photo.y = y;
2490+ }
2491+
2492+ onWidthChanged: {
2493+ if (photo && referencePhotoWidth > 0)
2494+ photo.scale = width / referencePhotoWidth;
2495+ }
2496+
2497+ onHeightChanged: {
2498+ if (photo && referencePhotoHeight > 0)
2499+ photo.scale = height / referencePhotoHeight;
2500+ }
2501+ }
2502+
2503+ //
2504+ // The following four Rectangles are used to "matte out" the area of the photo
2505+ // preview that falls outside the frame. This "matting out" visual cue is
2506+ // accomplished by darkening the matted-out area with a translucent, smoked
2507+ // overlay.
2508+ //
2509+ Rectangle {
2510+ id: leftMatte
2511+
2512+ color: cropOverlay.matteColor
2513+ opacity: cropOverlay.matteOpacity
2514+
2515+ anchors.top: topMatte.bottom
2516+ anchors.bottom: frame.bottom
2517+ anchors.left: parent.left
2518+ anchors.right: frame.left
2519+
2520+ MouseArea {
2521+ anchors.fill: parent;
2522+
2523+ onPressed: cropOverlay.matteRegionPressed();
2524+ }
2525+ }
2526+
2527+ Rectangle {
2528+ id: topMatte
2529+
2530+ color: cropOverlay.matteColor
2531+ opacity: cropOverlay.matteOpacity
2532+
2533+ anchors.top: parent.top
2534+ anchors.bottom: frame.top
2535+ anchors.left: parent.left
2536+ anchors.right: parent.right
2537+
2538+ MouseArea {
2539+ anchors.fill: parent;
2540+
2541+ onPressed: cropOverlay.matteRegionPressed();
2542+ }
2543+ }
2544+
2545+ Rectangle {
2546+ id: rightMatte
2547+
2548+ color: cropOverlay.matteColor
2549+ opacity: cropOverlay.matteOpacity
2550+
2551+ anchors.top: topMatte.bottom
2552+ anchors.bottom: bottomMatte.top
2553+ anchors.left: frame.right
2554+ anchors.right: parent.right
2555+
2556+ MouseArea {
2557+ anchors.fill: parent;
2558+
2559+ onPressed: cropOverlay.matteRegionPressed();
2560+ }
2561+ }
2562+
2563+ Rectangle {
2564+ id: bottomMatte
2565+
2566+ color: cropOverlay.matteColor
2567+ opacity: cropOverlay.matteOpacity
2568+
2569+ anchors.top: frame.bottom
2570+ anchors.bottom: parent.bottom
2571+ anchors.left: parent.left
2572+ anchors.right: parent.right
2573+
2574+ MouseArea {
2575+ anchors.fill: parent;
2576+
2577+ onPressed: cropOverlay.matteRegionPressed();
2578+ }
2579+ }
2580+
2581+ //
2582+ // The frame is a grey rectangle with associated drag corners that
2583+ // frames the region of the photo that will remain when the crop operation is
2584+ // applied.
2585+ //
2586+ // NB: the frame can be in two states, although the QML state mechanism
2587+ // isn't sufficiently expressive to describe them. The frame can be
2588+ // in the FIT state, in which case it is optimally fit inside the
2589+ // frame constraint region (see getFrameConstraintRect( ) above for
2590+ // a description of the frame constraint region). Or, the frame can
2591+ // be in the USER state. In the user state, the user has the mouse button
2592+ // held down and is actively performing a drag operation to change the
2593+ // geometry of the frame.
2594+ //
2595+ Rectangle {
2596+ id: frame
2597+
2598+ signal resizedX(bool left, real dx)
2599+ signal resizedY(bool top, real dy)
2600+
2601+ property variant dragStartRect
2602+
2603+ function getExtentRect() {
2604+ var result = { };
2605+
2606+ result.x = x;
2607+ result.y = y;
2608+ result.width = width;
2609+ result.height = height;
2610+
2611+ return result;
2612+ }
2613+
2614+ x: cropOverlay.initialFrameX
2615+ y: cropOverlay.initialFrameY
2616+ width: cropOverlay.initialFrameWidth
2617+ height: cropOverlay.initialFrameHeight
2618+
2619+ color: "transparent"
2620+
2621+ border.width: units.gu(0.2)
2622+ border.color: "#19B6EE"
2623+
2624+ MouseArea {
2625+ id: panArea
2626+
2627+ property int dragStartX;
2628+ property int dragStartY;
2629+
2630+ anchors.fill: parent
2631+ anchors.margins: 2
2632+
2633+ onPressed: {
2634+ dragStartX = mouse.x;
2635+ dragStartY = mouse.y;
2636+
2637+ photoExtent.startPan();
2638+ }
2639+
2640+ onReleased: {
2641+ photoExtent.stopPan();
2642+ }
2643+
2644+ onPositionChanged: {
2645+ photoExtent.updatePan(mouse.x - dragStartX, mouse.y - dragStartY);
2646+ }
2647+ }
2648+
2649+ Button {
2650+ objectName: "centerCropIcon"
2651+ anchors.centerIn: parent
2652+ text: i18n.tr("Crop")
2653+ color: frame.border.color
2654+ opacity: 0.9
2655+ onClicked: cropOverlay.cropButtonPressed()
2656+ }
2657+
2658+ // Left drag bar.
2659+ CropDragArea {
2660+ x: -units.gu(2)
2661+ width: units.gu(3)
2662+ anchors.verticalCenter: parent.center
2663+ height: parent.height - units.gu(2)
2664+
2665+ onDragged: {
2666+ frame.resizedX(true, dx);
2667+ frame.updateOnAltered(false);
2668+ }
2669+
2670+ onDragStarted: frame.dragStartRect = frame.getExtentRect();
2671+ onDragCompleted: frame.updateOnAltered(true);
2672+ }
2673+
2674+ // Top drag bar.
2675+ CropDragArea {
2676+ y: -units.gu(2)
2677+ height: units.gu(3)
2678+ anchors.horizontalCenter: parent.center
2679+ width: parent.width - units.gu(2)
2680+
2681+ onDragged: {
2682+ frame.resizedY(true, dy);
2683+ frame.updateOnAltered(false);
2684+ }
2685+
2686+ onDragStarted: frame.dragStartRect = frame.getExtentRect();
2687+ onDragCompleted: frame.updateOnAltered(true);
2688+ }
2689+
2690+ // Right drag bar.
2691+ CropDragArea {
2692+ x: parent.width - units.gu(1)
2693+ width: units.gu(3)
2694+ anchors.verticalCenter: parent.center
2695+ height: parent.height - units.gu(2)
2696+
2697+ onDragged: {
2698+ frame.resizedX(false, dx);
2699+ frame.updateOnAltered(false);
2700+ }
2701+
2702+ onDragStarted: frame.dragStartRect = frame.getExtentRect();
2703+ onDragCompleted: frame.updateOnAltered(true);
2704+ }
2705+
2706+ // Bottom drag bar.
2707+ CropDragArea {
2708+ y: parent.height - units.gu(1)
2709+ height: units.gu(3)
2710+ anchors.horizontalCenter: parent.center
2711+ width: parent.width - units.gu(2)
2712+
2713+ onDragged: {
2714+ frame.resizedY(false, dy);
2715+ frame.updateOnAltered(false);
2716+ }
2717+
2718+ onDragStarted: frame.dragStartRect = frame.getExtentRect();
2719+ onDragCompleted: frame.updateOnAltered(true);
2720+ }
2721+
2722+ // Top-left corner.
2723+ CropCorner {
2724+ objectName: "topLeftCropCorner"
2725+ isLeft: true
2726+ isTop: true
2727+
2728+ onDragged: {
2729+ frame.resizedX(isLeft, dx);
2730+ frame.resizedY(isTop, dy);
2731+ frame.updateOnAltered(false);
2732+ }
2733+
2734+ onDragStarted: frame.dragStartRect = frame.getExtentRect();
2735+ onDragCompleted: frame.updateOnAltered(true);
2736+ }
2737+
2738+ // Top-right corner.
2739+ CropCorner {
2740+ objectName: "topRightCropCorner"
2741+ isLeft: false
2742+ isTop: true
2743+
2744+ onDragged: {
2745+ frame.resizedX(isLeft, dx);
2746+ frame.resizedY(isTop, dy);
2747+ frame.updateOnAltered(false);
2748+ }
2749+
2750+ onDragStarted: frame.dragStartRect = frame.getExtentRect();
2751+ onDragCompleted: frame.updateOnAltered(true);
2752+ }
2753+
2754+ // Bottom-left corner.
2755+ CropCorner {
2756+ objectName: "bottonLeftCropCorner"
2757+ isLeft: true
2758+ isTop: false
2759+
2760+ onDragged: {
2761+ frame.resizedX(isLeft, dx);
2762+ frame.resizedY(isTop, dy);
2763+ frame.updateOnAltered(false);
2764+ }
2765+
2766+ onDragStarted: frame.dragStartRect = frame.getExtentRect();
2767+ onDragCompleted: frame.updateOnAltered(true);
2768+ }
2769+
2770+ // Bottom-right corner.
2771+ CropCorner {
2772+ id: bottomRightCrop
2773+ objectName: "bottomRightCropCorner"
2774+ isLeft: false
2775+ isTop: false
2776+
2777+ onDragged: {
2778+ frame.resizedX(isLeft, dx);
2779+ frame.resizedY(isTop, dy);
2780+ frame.updateOnAltered(false);
2781+ }
2782+
2783+ onDragStarted: frame.dragStartRect = frame.getExtentRect();
2784+ onDragCompleted: frame.updateOnAltered(true);
2785+ }
2786+
2787+ // This handles resizing in both dimensions. first is whether we're
2788+ // resizing the "first" edge, e.g. left or top (in which case we
2789+ // adjust both position and span) vs. right or bottom (where we just
2790+ // adjust the span). position should be either "x" or "y", and span
2791+ // is either "width" or "height". This is a little complicated, and
2792+ // coule probably be optimized with a little more thought.
2793+ function resizeFrame(first, delta, position, span) {
2794+ var constraintRegion = cropOverlay.getPhotoExtentRect();
2795+
2796+ if (first) {
2797+ // Left/top side.
2798+ if (frame[position] + delta < constraintRegion[position])
2799+ delta = constraintRegion[position] - frame[position]
2800+
2801+ if (frame[span] - delta < minSize)
2802+ delta = frame[span] - minSize;
2803+
2804+ frame[position] += delta;
2805+ frame[span] -= delta;
2806+ } else {
2807+ // Right/bottom side.
2808+ if (frame[span] + delta < minSize)
2809+ delta = minSize - frame[span];
2810+
2811+ if ((frame[position] + frame[span] + delta) >
2812+ (constraintRegion[position] + constraintRegion[span]))
2813+ delta = constraintRegion[position] + constraintRegion[span] -
2814+ frame[position] - frame[span];
2815+
2816+ frame[span] += delta;
2817+ }
2818+ }
2819+
2820+ onResizedX: resizeFrame(left, dx, "x", "width")
2821+ onResizedY: resizeFrame(top, dy, "y", "height")
2822+
2823+ function updateOnAltered(finalUpdate) {
2824+ var start = frame.dragStartRect;
2825+ var end = frame.getExtentRect();
2826+ if (!GraphicsRoutines.areEqual(end, start)) {
2827+ if (finalUpdate ||
2828+ (end.width * end.height >= start.width * start.height)) {
2829+ cropOverlay.userAlteredFrame();
2830+ cropOverlay.runFrameFitAnimation();
2831+ }
2832+ }
2833+ }
2834+ }
2835+
2836+ /* Invoked when the user has changed the geometry of the frame by dragging
2837+ * one of its corners or edges. Expressed in terms of the states of the
2838+ * frame described above, the userAlteredFrame signal is fired
2839+ * when the user stops dragging. This triggers a change of the frame
2840+ * from the USER state to the FIT state
2841+ */
2842+ onUserAlteredFrame: {
2843+ // since the geometry of the frame in the FIT state depends on both
2844+ // how the user resized the frame when it was in the USER state as well
2845+ // as the size of the frame constraint region, we have to recompute the
2846+ // geometry of of the frame for the FIT state every time.
2847+
2848+ startFrame = GraphicsRoutines.cloneRect(frame);
2849+
2850+ endFrame = GraphicsRoutines.fitRect(getViewportExtentRect(),
2851+ frame.getExtentRect());
2852+
2853+ startPhoto = GraphicsRoutines.cloneRect(photoExtent);
2854+
2855+ var frameRelativeToPhoto = getRelativeFrameRect();
2856+ var scaleFactor = endFrame.width / frame.width;
2857+
2858+ endPhotoWidth = photoExtent.width * scaleFactor;
2859+ endPhotoHeight = photoExtent.height * scaleFactor;
2860+ endPhotoX = endFrame.x - (frameRelativeToPhoto.x * endPhotoWidth);
2861+ endPhotoY = endFrame.y - (frameRelativeToPhoto.y * endPhotoHeight)
2862+
2863+ photo.transformOrigin = Item.TopLeft;
2864+ }
2865+
2866+ onRunFrameFitAnimation: NumberAnimation { target: cropOverlay;
2867+ property: "interpolationFactor"; from: 0.0; to: 1.0 }
2868+
2869+ onInterpolationFactorChanged: {
2870+ var endPhotoRect = { };
2871+ endPhotoRect.x = endPhotoX;
2872+ endPhotoRect.y = endPhotoY;
2873+ endPhotoRect.width = endPhotoWidth;
2874+ endPhotoRect.height = endPhotoHeight;
2875+
2876+ var interpolatedRect = GraphicsRoutines.interpolateRect(startFrame,
2877+ endFrame, interpolationFactor);
2878+ GraphicsRoutines.sizeToRect(interpolatedRect, frame);
2879+
2880+ interpolatedRect = GraphicsRoutines.interpolateRect(startPhoto,
2881+ endPhotoRect, interpolationFactor);
2882+ GraphicsRoutines.sizeToRect(interpolatedRect, photoExtent);
2883+ }
2884+}
2885
2886=== added file 'rc/qml/MediaViewer/PhotoEditor/EditStack.qml'
2887--- rc/qml/MediaViewer/PhotoEditor/EditStack.qml 1970-01-01 00:00:00 +0000
2888+++ rc/qml/MediaViewer/PhotoEditor/EditStack.qml 2015-03-10 14:05:41 +0000
2889@@ -0,0 +1,134 @@
2890+/*
2891+ * Copyright (C) 2014 Canonical Ltd
2892+ *
2893+ * This program is free software: you can redistribute it and/or modify
2894+ * it under the terms of the GNU General Public License version 3 as
2895+ * published by the Free Software Foundation.
2896+ *
2897+ * This program is distributed in the hope that it will be useful,
2898+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
2899+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
2900+ * GNU General Public License for more details.
2901+ *
2902+ * You should have received a copy of the GNU General Public License
2903+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
2904+ */
2905+
2906+import QtQuick 2.3
2907+import Ubuntu.Components 1.1
2908+import Gallery 1.0
2909+
2910+Item {
2911+ property GalleryPhotoData data
2912+ property bool actionsEnabled: true
2913+ property var items: []
2914+ property int level: 0
2915+ property string editingSessionPath
2916+ property string currentFile
2917+ property string originalFile
2918+ property string pristineFile
2919+ property bool modified: level > 0 || _revertedInThisSession
2920+
2921+ property bool _revertedInThisSession
2922+ property bool _pristineFileExists
2923+
2924+ signal revertRequested
2925+
2926+ function startEditingSession(original) {
2927+ var originalFileName = GalleryFileUtils.nameFromPath(original);
2928+ var baseName = GalleryFileUtils.parentDirectory(original) +
2929+ "/.photo_editing." + originalFileName + ".";
2930+ editingSessionPath = GalleryFileUtils.createTemporaryDirectory(baseName);
2931+ if (editingSessionPath == "") return false;
2932+
2933+ originalFile = original;
2934+ currentFile = editingSessionPath + "/current";
2935+
2936+ pristineFile = GalleryFileUtils.parentDirectory(original) +
2937+ "/.original/" + originalFileName
2938+ _revertedInThisSession = false;
2939+ _pristineFileExists = GalleryFileUtils.exists(pristineFile)
2940+
2941+ GalleryFileUtils.copy(originalFile, currentFile)
2942+
2943+ items = [createSnapshot(0)];
2944+ level = 0;
2945+ return true;
2946+ }
2947+
2948+ function endEditingSession(saveIfModified) {
2949+ if (saveIfModified && modified) { // file modified
2950+ // if we don't have a copy of the very first original, create one
2951+ if (!_pristineFileExists) {
2952+ GalleryFileUtils.createDirectory(GalleryFileUtils.parentDirectory(pristineFile));
2953+ GalleryFileUtils.copy(originalFile, pristineFile);
2954+ } else {
2955+ // if we reverted to original (and made no other changes)
2956+ // we don't need to keep the pristine copy around
2957+ if (_revertedInThisSession && level <= 0) {
2958+ GalleryFileUtils.remove(pristineFile);
2959+ }
2960+ }
2961+
2962+ GalleryFileUtils.copy(currentFile, originalFile); // actually save
2963+ }
2964+
2965+ GalleryFileUtils.removeDirectory(editingSessionPath, true); // clear editing cache
2966+ editingSessionPath = originalFile = pristineFile = currentFile = "";
2967+ }
2968+
2969+ function createSnapshot(name) {
2970+ var snapshotFile = editingSessionPath + "/edit." + name;
2971+ GalleryFileUtils.copy(currentFile, snapshotFile);
2972+ return snapshotFile;
2973+ }
2974+
2975+ function restoreSnapshot(name) {
2976+ var snapshotFile = editingSessionPath + "/edit." + name;
2977+ GalleryFileUtils.copy(snapshotFile, currentFile);
2978+ data.refreshFromDisk();
2979+ }
2980+
2981+ function checkpoint() {
2982+ level++;
2983+ items = items.slice(0, level);
2984+ items.push(createSnapshot(items.length));
2985+ }
2986+
2987+ function revertToPristine() {
2988+ if (!GalleryFileUtils.exists(pristineFile)) {
2989+ restoreSnapshot(0);
2990+ items = items.slice(0, 1);
2991+ level = 0;
2992+ } else {
2993+ GalleryFileUtils.copy(pristineFile, currentFile);
2994+ data.refreshFromDisk();
2995+ items = [];
2996+ checkpoint();
2997+ level = 0;
2998+ _revertedInThisSession = true;
2999+ }
3000+ }
3001+
3002+ property Action undoAction: Action {
3003+ text: i18n.tr("Undo")
3004+ iconName: "undo"
3005+ enabled: items.length > 0 && level > 0 && actionsEnabled
3006+ onTriggered: restoreSnapshot(--level);
3007+ }
3008+
3009+ property Action redoAction: Action {
3010+ text: i18n.tr("Redo")
3011+ iconName: "redo"
3012+ enabled: level < items.length - 1 && actionsEnabled
3013+ onTriggered: restoreSnapshot(++level);
3014+ }
3015+
3016+ property Action revertAction: Action {
3017+ text: i18n.tr("Revert to Original")
3018+ iconSource: Qt.resolvedUrl("assets/edit_revert.png")
3019+ enabled: actionsEnabled &&
3020+ (level > 0 || (!_revertedInThisSession && _pristineFileExists))
3021+ onTriggered: revertRequested()
3022+ }
3023+}
3024
3025=== added file 'rc/qml/MediaViewer/PhotoEditor/ExposureAdjuster.qml'
3026--- rc/qml/MediaViewer/PhotoEditor/ExposureAdjuster.qml 1970-01-01 00:00:00 +0000
3027+++ rc/qml/MediaViewer/PhotoEditor/ExposureAdjuster.qml 2015-03-10 14:05:41 +0000
3028@@ -0,0 +1,104 @@
3029+/*
3030+ * Copyright (C) 2014 Canonical Ltd
3031+ *
3032+ * This program is free software: you can redistribute it and/or modify
3033+ * it under the terms of the GNU General Public License version 3 as
3034+ * published by the Free Software Foundation.
3035+ *
3036+ * This program is distributed in the hope that it will be useful,
3037+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3038+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3039+ * GNU General Public License for more details.
3040+ *
3041+ * You should have received a copy of the GNU General Public License
3042+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3043+ */
3044+
3045+import QtQuick 2.3
3046+import Ubuntu.Components 1.1
3047+
3048+// When the photo editor uses a proper PageStack this will switch back to being
3049+// an Item as it will not need to cover what is below it.
3050+Rectangle {
3051+ id: adjuster
3052+ color:"black"
3053+
3054+ property alias exposure: exposureSelector.value
3055+ property bool enabled
3056+
3057+ signal confirm()
3058+ signal cancel()
3059+
3060+ Image {
3061+ id: targetImage
3062+ anchors.fill: parent
3063+ fillMode: Image.PreserveAspectFit
3064+ asynchronous: true
3065+ cache: false
3066+ sourceSize {
3067+ width: targetImage.width
3068+ height: targetImage.height
3069+ }
3070+ }
3071+
3072+ Column {
3073+ anchors.left: parent.left
3074+ anchors.right: parent.right
3075+ anchors.bottom: parent.bottom
3076+ anchors.margins: units.gu(2)
3077+ spacing: units.gu(2)
3078+
3079+ Slider {
3080+ id: exposureSelector
3081+ live: false
3082+ minimumValue: -1.0
3083+ maximumValue: +1.0
3084+ value: 0.0
3085+ enabled: adjuster.enabled
3086+
3087+ anchors.left: parent.left
3088+ anchors.right: parent.right
3089+ height: units.gu(2)
3090+
3091+ function formatValue(value) {
3092+ return (Math.round(value * 100) / 100).toString()
3093+ }
3094+ }
3095+ Row {
3096+ anchors.horizontalCenter: parent.horizontalCenter
3097+ spacing: units.gu(2)
3098+ Button {
3099+ text: i18n.tr("Done")
3100+ color: UbuntuColors.green
3101+ enabled: adjuster.enabled
3102+ onTriggered: {
3103+ targetImage.source = "";
3104+ confirm();
3105+ }
3106+ }
3107+ Button {
3108+ text: i18n.tr("Cancel")
3109+ color: UbuntuColors.red
3110+ enabled: adjuster.enabled
3111+ onTriggered: {
3112+ targetImage.source = "";
3113+ cancel();
3114+ }
3115+ }
3116+ }
3117+ }
3118+
3119+ function start(target) {
3120+ targetImage.source = target;
3121+ exposure = 0.0;
3122+ opacity = 1.0;
3123+ }
3124+
3125+ function reload() {
3126+ var path = targetImage.source;
3127+ targetImage.asynchronous = false;
3128+ targetImage.source = "";
3129+ targetImage.asynchronous = true;
3130+ targetImage.source = path;
3131+ }
3132+}
3133
3134=== added file 'rc/qml/MediaViewer/PhotoEditor/GraphicsRoutines.js'
3135--- rc/qml/MediaViewer/PhotoEditor/GraphicsRoutines.js 1970-01-01 00:00:00 +0000
3136+++ rc/qml/MediaViewer/PhotoEditor/GraphicsRoutines.js 2015-03-10 14:05:41 +0000
3137@@ -0,0 +1,108 @@
3138+/*
3139+ * Copyright (C) 2012 Canonical Ltd
3140+ *
3141+ * This program is free software: you can redistribute it and/or modify
3142+ * it under the terms of the GNU General Public License version 3 as
3143+ * published by the Free Software Foundation.
3144+ *
3145+ * This program is distributed in the hope that it will be useful,
3146+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3147+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3148+ * GNU General Public License for more details.
3149+ *
3150+ * You should have received a copy of the GNU General Public License
3151+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3152+ *
3153+ * Authors:
3154+ * Lucas Beeler <lucas@yorba.org>
3155+ */
3156+
3157+/* Given 'input', constrain the value of 'input' to range between
3158+ * 'lowConstraint' and 'highConstraint', inclusive. Return the constrained
3159+ * value without modifying 'input'.
3160+ */
3161+function clamp(input, lowConstraint, highConstraint) {
3162+ if (input < lowConstraint)
3163+ return lowConstraint;
3164+ else if (input > highConstraint)
3165+ return highConstraint;
3166+ else
3167+ return input;
3168+}
3169+
3170+function cloneRect(source) {
3171+ var ret = { };
3172+ ret.x = source.x;
3173+ ret.y = source.y;
3174+ ret.width = source.width;
3175+ ret.height = source.height;
3176+
3177+ return ret;
3178+}
3179+
3180+function interpolateRect(start, end, factor) {
3181+ var result = { };
3182+
3183+ result.x = start.x + factor * (end.x - start.x);
3184+ result.y = start.y + factor * (end.y - start.y);
3185+ result.width = start.width + factor * (end.width - start.width);
3186+ result.height = start.height + factor * (end.height - start.height);
3187+
3188+ return result;
3189+}
3190+
3191+/* Forces Geometry object 'item' to fit centered inside Geometry object
3192+ * 'viewport', preserving the aspect of ratio of 'item' but potentially scaling
3193+ * and translating it so that it snugly fits centered inside of 'viewport'.
3194+ * Return the new scaled-up and translated Geometry for 'item'.
3195+ */
3196+function fitRect(viewport, item) {
3197+ if (item.width == 0 || item.height == 0) {
3198+ return viewport;
3199+ }
3200+
3201+ var itemAspectRatio = item.width / item.height;
3202+ var viewportAspectRatio = viewport.width / viewport.height;
3203+
3204+ var result = { };
3205+ if (itemAspectRatio > viewportAspectRatio) {
3206+ var scaleFactor = viewport.width / item.width;
3207+ result.width = viewport.width;
3208+ result.height = item.height * scaleFactor
3209+ } else {
3210+ scaleFactor = viewport.height / item.height;
3211+ result.width = item.width * scaleFactor
3212+ result.height = viewport.height;
3213+ }
3214+
3215+ result.width = clamp(result.width, 0, viewport.width);
3216+ result.height = clamp(result.height, 0, viewport.height);
3217+ result.x = viewport.x + (viewport.width - result.width) / 2;
3218+ result.y = viewport.y + (viewport.height - result.height) / 2;
3219+ result.scaleFactor = scaleFactor;
3220+
3221+ return result;
3222+}
3223+
3224+function getRelativeRect(geom, relativeTo) {
3225+ var result = { };
3226+
3227+ result.x = (geom.x - relativeTo.x) / relativeTo.width;
3228+ result.y = (geom.y - relativeTo.y) / relativeTo.height;
3229+ result.width = geom.width / relativeTo.width;
3230+ result.height = geom.height / relativeTo.height;
3231+
3232+ return result;
3233+}
3234+
3235+function sizeToRect(rect, qmlItem) {
3236+ qmlItem.x = rect.x;
3237+ qmlItem.y = rect.y;
3238+ qmlItem.width = rect.width;
3239+ qmlItem.height = rect.height;
3240+}
3241+
3242+function areEqual(geom1, geom2) {
3243+ return (geom1.x === geom2.x && geom1.y === geom2.y && geom1.width ===
3244+ geom2.width && geom1.height === geom2.height);
3245+}
3246
3247=== added directory 'rc/qml/MediaViewer/PhotoEditor/assets'
3248=== added file 'rc/qml/MediaViewer/PhotoEditor/assets/crop-handle@20.png'
3249Binary files rc/qml/MediaViewer/PhotoEditor/assets/crop-handle@20.png 1970-01-01 00:00:00 +0000 and rc/qml/MediaViewer/PhotoEditor/assets/crop-handle@20.png 2015-03-10 14:05:41 +0000 differ
3250=== added file 'rc/qml/MediaViewer/PhotoEditor/assets/edit_autocorrect@27.png'
3251Binary files rc/qml/MediaViewer/PhotoEditor/assets/edit_autocorrect@27.png 1970-01-01 00:00:00 +0000 and rc/qml/MediaViewer/PhotoEditor/assets/edit_autocorrect@27.png 2015-03-10 14:05:41 +0000 differ
3252=== added file 'rc/qml/MediaViewer/PhotoEditor/assets/edit_crop@27.png'
3253Binary files rc/qml/MediaViewer/PhotoEditor/assets/edit_crop@27.png 1970-01-01 00:00:00 +0000 and rc/qml/MediaViewer/PhotoEditor/assets/edit_crop@27.png 2015-03-10 14:05:41 +0000 differ
3254=== added file 'rc/qml/MediaViewer/PhotoEditor/assets/edit_exposure@27.png'
3255Binary files rc/qml/MediaViewer/PhotoEditor/assets/edit_exposure@27.png 1970-01-01 00:00:00 +0000 and rc/qml/MediaViewer/PhotoEditor/assets/edit_exposure@27.png 2015-03-10 14:05:41 +0000 differ
3256=== added file 'rc/qml/MediaViewer/PhotoEditor/assets/edit_revert@27.png'
3257Binary files rc/qml/MediaViewer/PhotoEditor/assets/edit_revert@27.png 1970-01-01 00:00:00 +0000 and rc/qml/MediaViewer/PhotoEditor/assets/edit_revert@27.png 2015-03-10 14:05:41 +0000 differ
3258=== added file 'rc/qml/MediaViewer/PhotoEditor/assets/edit_rotate_left@27.png'
3259Binary files rc/qml/MediaViewer/PhotoEditor/assets/edit_rotate_left@27.png 1970-01-01 00:00:00 +0000 and rc/qml/MediaViewer/PhotoEditor/assets/edit_rotate_left@27.png 2015-03-10 14:05:41 +0000 differ
3260=== modified file 'rc/qml/MediaViewer/SingleMediaViewer.qml'
3261--- rc/qml/MediaViewer/SingleMediaViewer.qml 2015-02-10 13:35:46 +0000
3262+++ rc/qml/MediaViewer/SingleMediaViewer.qml 2015-03-10 14:05:41 +0000
3263@@ -120,12 +120,23 @@
3264 width: flickable.width * flickable.sizeScale
3265 height: flickable.height * flickable.sizeScale
3266
3267- Image {
3268+ Connections {
3269+ target: mediaSource
3270+ onDataChanged: {
3271+ image.source = "";
3272+ image.source = "image://photo/" + mediaSource.path
3273+
3274+ rightResolutionImage.source = "";
3275+ rightResolutionImage.source = "image://photo/" + mediaSource.path
3276+ }
3277+ }
3278+
3279+ Image {
3280 id: image
3281 anchors.fill: parent
3282 asynchronous: true
3283 cache: false
3284- source: mediaSource.galleryPath
3285+ source: "image://photo/" + mediaSource.path
3286 sourceSize {
3287 width: viewer.maxDimension
3288 height: viewer.maxDimension
3289@@ -142,8 +153,8 @@
3290 anchors.fill: parent
3291 asynchronous: true
3292 cache: false
3293- // Load image using the GalleryStandardImageProvider to ensure EXIF orientation
3294- source: flickable.sizeScale > 1.0 ? mediaSource.galleryPath : ""
3295+ // Load image using the photo image provider to ensure EXIF orientation
3296+ source: flickable.sizeScale > 1.0 ? "image://photo/" + mediaSource.path : ""
3297 sourceSize {
3298 width: width
3299 height: height
3300
3301=== modified file 'rc/qml/OrganicView/OrganicMediaList.qml'
3302--- rc/qml/OrganicView/OrganicMediaList.qml 2015-02-09 19:48:24 +0000
3303+++ rc/qml/OrganicView/OrganicMediaList.qml 2015-03-10 14:05:41 +0000
3304@@ -170,7 +170,7 @@
3305
3306 image: Image {
3307 id: thumbImage
3308- source: model.mediaSource.galleryThumbnailPath
3309+ source: "image://thumbnailer/" + model.mediaSource.path
3310 asynchronous: true
3311
3312 /* The SDK thumbnailer respects the freedesktop.org standard and uses 128 for the small
3313@@ -186,7 +186,7 @@
3314 onDataChanged: {
3315 // data changed but filename didn't, so we need to bypass the qml image
3316 // cache by tacking a timestamp to the filename so sees it as different.
3317- thumbImage.source = model.mediaSource.galleryThumbnailPath + "?at=" + Date.now()
3318+ thumbImage.source = "image://thumbnailer/" + model.mediaSource.path + "?at=" + Date.now()
3319 }
3320 }
3321
3322
3323=== modified file 'src/CMakeLists.txt'
3324--- src/CMakeLists.txt 2014-03-27 22:38:39 +0000
3325+++ src/CMakeLists.txt 2015-03-10 14:05:41 +0000
3326@@ -6,6 +6,7 @@
3327 add_subdirectory(database)
3328 add_subdirectory(event)
3329 add_subdirectory(media)
3330+add_subdirectory(photoeditor)
3331 add_subdirectory(medialoader)
3332 add_subdirectory(photo)
3333 add_subdirectory(qml)
3334@@ -26,6 +27,7 @@
3335 ${gallery_event_src_SOURCE_DIR}
3336 ${gallery_album_src_SOURCE_DIR}
3337 ${gallery_photo_src_SOURCE_DIR}
3338+ ${gallery_photoeditor_src_SOURCE_DIR}
3339 ${gallery_video_src_SOURCE_DIR}
3340 ${gallery_qml_src_SOURCE_DIR}
3341 ${CMAKE_BINARY_DIR}
3342@@ -60,6 +62,7 @@
3343 gallery-media
3344 gallery-medialoader
3345 gallery-photo
3346+ gallery-photoeditor
3347 gallery-qml
3348 gallery-util
3349 gallery-video
3350
3351=== modified file 'src/database/CMakeLists.txt'
3352--- src/database/CMakeLists.txt 2013-08-15 06:40:15 +0000
3353+++ src/database/CMakeLists.txt 2015-03-10 14:05:41 +0000
3354@@ -17,14 +17,12 @@
3355 album-table.h
3356 database.h
3357 media-table.h
3358- photo-edit-table.h
3359 )
3360
3361 set(gallery_database_SRCS
3362 album-table.cpp
3363 database.cpp
3364 media-table.cpp
3365- photo-edit-table.cpp
3366 )
3367
3368 add_library(${GALLERY_DATABASE_LIB}
3369
3370=== modified file 'src/database/database.cpp'
3371--- src/database/database.cpp 2014-09-18 22:09:01 +0000
3372+++ src/database/database.cpp 2015-03-10 14:05:41 +0000
3373@@ -21,7 +21,6 @@
3374 #include "database.h"
3375 #include "album-table.h"
3376 #include "media-table.h"
3377-#include "photo-edit-table.h"
3378
3379 #include <QFile>
3380 #include <QSqlTableModel>
3381@@ -49,7 +48,6 @@
3382
3383 m_albumTable = new AlbumTable(this, this);
3384 m_mediaTable = new MediaTable(this, this);
3385- m_photoEditTable = new PhotoEditTable(this, this);
3386
3387 // Open the database.
3388 if (!openDB())
3389@@ -86,7 +84,6 @@
3390 {
3391 delete m_albumTable;
3392 delete m_mediaTable;
3393- delete m_photoEditTable;
3394 delete m_db;
3395
3396 createBackup();
3397@@ -227,15 +224,6 @@
3398 }
3399
3400 /*!
3401- * \brief Database::getPhotoEditTable
3402- * \return
3403- */
3404-PhotoEditTable* Database::getPhotoEditTable() const
3405-{
3406- return m_photoEditTable;
3407-}
3408-
3409-/*!
3410 * \brief Database::getDB
3411 * \return
3412 */
3413
3414=== modified file 'src/database/database.h'
3415--- src/database/database.h 2013-06-10 07:37:09 +0000
3416+++ src/database/database.h 2015-03-10 14:05:41 +0000
3417@@ -26,7 +26,6 @@
3418
3419 class AlbumTable;
3420 class MediaTable;
3421-class PhotoEditTable;
3422
3423 class QSqlDatabase;
3424 class QSqlQuery;
3425@@ -50,7 +49,6 @@
3426
3427 AlbumTable* getAlbumTable() const;
3428 MediaTable* getMediaTable() const;
3429- PhotoEditTable* getPhotoEditTable() const;
3430
3431 private:
3432 bool openDB();
3433@@ -75,7 +73,6 @@
3434 QSqlDatabase* m_db;
3435 AlbumTable* m_albumTable;
3436 MediaTable* m_mediaTable;
3437- PhotoEditTable* m_photoEditTable;
3438 };
3439
3440 #endif // DATABASE_H
3441
3442=== removed file 'src/database/photo-edit-table.cpp'
3443--- src/database/photo-edit-table.cpp 2013-06-10 07:37:09 +0000
3444+++ src/database/photo-edit-table.cpp 1970-01-01 00:00:00 +0000
3445@@ -1,124 +0,0 @@
3446-/*
3447- * Copyright (C) 2012 Canonical Ltd
3448- *
3449- * This program is free software: you can redistribute it and/or modify
3450- * it under the terms of the GNU General Public License version 3 as
3451- * published by the Free Software Foundation.
3452- *
3453- * This program is distributed in the hope that it will be useful,
3454- * but WITHOUT ANY WARRANTY; without even the implied warranty of
3455- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3456- * GNU General Public License for more details.
3457- *
3458- * You should have received a copy of the GNU General Public License
3459- * along with this program. If not, see <http://www.gnu.org/licenses/>.
3460- *
3461- * Authors:
3462- * Charles Lindsay <chaz@yorba.org>
3463- */
3464-
3465-#include "photo-edit-table.h"
3466-#include "database.h"
3467-
3468-// photo
3469-#include "photo-edit-state.h"
3470-
3471-#include <QSqlQuery>
3472-#include <QStringList>
3473-#include <QVariant>
3474-
3475-/*!
3476- * \brief PhotoEditTable::PhotoEditTable
3477- * \param db
3478- * \param parent
3479- */
3480-PhotoEditTable::PhotoEditTable(Database* db, QObject* parent)
3481- : QObject(parent), m_db(db)
3482-{
3483-}
3484-
3485-/*!
3486- * \brief PhotoEditTable::editState
3487- * \param mediaId
3488- * \return
3489- */
3490-PhotoEditState PhotoEditTable::editState(qint64 mediaId) const
3491-{
3492- PhotoEditState edit_state;
3493-
3494- QSqlQuery query(*m_db->getDB());
3495- query.prepare("SELECT crop_rectangle, is_enhanced, orientation "
3496- "FROM PhotoEditTable "
3497- "WHERE media_id = :media_id");
3498-
3499- query.bindValue(":media_id", mediaId);
3500- if (!query.exec())
3501- m_db->logSqlError(query);
3502-
3503- if (query.next()) {
3504- QStringList parts = query.value(0).toString().split(',');
3505- if (parts.count() == 4) {
3506- int x = parts[0].toInt();
3507- int y = parts[1].toInt();
3508- int width = parts[2].toInt();
3509- int height = parts[3].toInt();
3510-
3511- edit_state.crop_rectangle_ = QRect(x, y, width, height);
3512- }
3513-
3514- edit_state.is_enhanced_ = query.value(1).toBool();
3515- edit_state.orientation_ = static_cast<Orientation>(query.value(2).toInt());
3516- }
3517-
3518- return edit_state;
3519-}
3520-
3521-/*!
3522- * \brief PhotoEditTable::setEditState
3523- * \param mediaId
3524- * \param editState
3525- */
3526-void PhotoEditTable::setEditState(qint64 mediaId,
3527- const PhotoEditState& editState)
3528-{
3529- if (mediaId == INVALID_ID)
3530- return;
3531-
3532- prepareRow(mediaId);
3533-
3534- QString crop_rect_string = QString("%1,%2,%3,%4")
3535- .arg(editState.crop_rectangle_.x())
3536- .arg(editState.crop_rectangle_.y())
3537- .arg(editState.crop_rectangle_.width())
3538- .arg(editState.crop_rectangle_.height());
3539-
3540- QSqlQuery query(*m_db->getDB());
3541-
3542- query.prepare("UPDATE PhotoEditTable "
3543- "SET crop_rectangle = :crop_rect_string, "
3544- "is_enhanced = :is_enhanced, "
3545- "orientation = :orientation "
3546- "WHERE media_id = :media_id");
3547- query.bindValue(":media_id", mediaId);
3548- query.bindValue(":crop_rect_string", crop_rect_string);
3549- query.bindValue(":is_enhanced", editState.is_enhanced_);
3550- query.bindValue(":orientation", static_cast<int>(editState.orientation_));
3551-
3552- if (!query.exec())
3553- m_db->logSqlError(query);
3554-}
3555-
3556-/*!
3557- * \brief PhotoEditTable::prepareRow
3558- * \param mediaId
3559- */
3560-void PhotoEditTable::prepareRow(qint64 mediaId)
3561-{
3562- QSqlQuery query(*m_db->getDB());
3563- query.prepare("INSERT OR IGNORE INTO PhotoEditTable "
3564- "(media_id) VALUES (:media_id)");
3565-
3566- query.bindValue(":media_id", mediaId);
3567- if (!query.exec())
3568- m_db->logSqlError(query);
3569-}
3570
3571=== removed file 'src/database/photo-edit-table.h'
3572--- src/database/photo-edit-table.h 2013-06-10 07:37:09 +0000
3573+++ src/database/photo-edit-table.h 1970-01-01 00:00:00 +0000
3574@@ -1,47 +0,0 @@
3575-/*
3576- * Copyright (C) 2012 Canonical Ltd
3577- *
3578- * This program is free software: you can redistribute it and/or modify
3579- * it under the terms of the GNU General Public License version 3 as
3580- * published by the Free Software Foundation.
3581- *
3582- * This program is distributed in the hope that it will be useful,
3583- * but WITHOUT ANY WARRANTY; without even the implied warranty of
3584- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3585- * GNU General Public License for more details.
3586- *
3587- * You should have received a copy of the GNU General Public License
3588- * along with this program. If not, see <http://www.gnu.org/licenses/>.
3589- *
3590- * Authors:
3591- * Charles Lindsay <chaz@yorba.org>
3592- */
3593-
3594-#ifndef PHOTOEDITTABLE_H
3595-#define PHOTOEDITTABLE_H
3596-
3597-#include <QObject>
3598-
3599-class Database;
3600-class PhotoEditState;
3601-
3602-/*!
3603- * \brief The PhotoEditTable class
3604- */
3605-class PhotoEditTable : public QObject
3606-{
3607- Q_OBJECT
3608-
3609-public:
3610- explicit PhotoEditTable(Database* db, QObject *parent = 0);
3611-
3612- PhotoEditState editState(qint64 mediaId) const;
3613- void setEditState(qint64 mediaId, const PhotoEditState& editState);
3614-
3615-private:
3616- void prepareRow(qint64 mediaId);
3617-
3618- Database* m_db;
3619-};
3620-
3621-#endif // PHOTOEDITTABLE_H
3622
3623=== modified file 'src/gallery-application.cpp'
3624--- src/gallery-application.cpp 2015-02-02 18:54:14 +0000
3625+++ src/gallery-application.cpp 2015-03-10 14:05:41 +0000
3626@@ -36,8 +36,12 @@
3627 // photo
3628 #include "photo.h"
3629
3630+// photoeditor
3631+#include "photo-data.h"
3632+#include "file-utils.h"
3633+#include "photo-image-provider.h"
3634+
3635 // qml
3636-#include "gallery-standard-image-provider.h"
3637 #include "qml-album-collection-model.h"
3638 #include "qml-event-collection-model.h"
3639 #include "qml-event-overview-model.h"
3640@@ -107,7 +111,6 @@
3641 registerQML();
3642
3643 m_galleryManager = new GalleryManager(isDesktopMode(), m_cmdLineParser->picturesDir());
3644- m_galleryManager->logImageLoading(m_cmdLineParser->logImageLoading());
3645 if (m_cmdLineParser->pickModeEnabled())
3646 setDefaultUiMode(GalleryApplication::PickContentMode);
3647
3648@@ -172,6 +175,8 @@
3649 qmlRegisterType<QmlEventCollectionModel>("Gallery", 1, 0, "EventCollectionModel");
3650 qmlRegisterType<QmlEventOverviewModel>("Gallery", 1, 0, "EventOverviewModel");
3651 qmlRegisterType<QmlMediaCollectionModel>("Gallery", 1, 0, "MediaCollectionModel");
3652+ qmlRegisterType<PhotoData>("Gallery", 1, 0, "GalleryPhotoData");
3653+ qmlRegisterSingletonType<FileUtils>("Gallery", 1, 0, "GalleryFileUtils", exportFileUtilsSingleton);
3654
3655 qRegisterMetaType<QList<MediaSource*> >("MediaSourceList");
3656 qRegisterMetaType<QSet<DataObject*> >("QSet<DataObject*>");
3657@@ -218,6 +223,11 @@
3658 {
3659 m_view->setTitle("Gallery");
3660
3661+ PhotoImageProvider* provider = new PhotoImageProvider();
3662+ provider->setLogging(true);
3663+ m_view->engine()->addImageProvider(PhotoImageProvider::PROVIDER_ID,
3664+ provider);
3665+
3666 QSize size = m_formFactors[m_cmdLineParser->formFactor()];
3667
3668 if (m_cmdLineParser->isPortrait())
3669@@ -259,6 +269,15 @@
3670 setMediaFile(m_cmdLineParser->mediaFile());
3671 }
3672
3673+QObject* GalleryApplication::exportFileUtilsSingleton(QQmlEngine *engine,
3674+ QJSEngine *scriptEngine)
3675+{
3676+ Q_UNUSED(engine);
3677+ Q_UNUSED(scriptEngine);
3678+
3679+ return new FileUtils();
3680+}
3681+
3682 /*!
3683 * \brief GalleryApplication::initCollections
3684 */
3685@@ -267,8 +286,6 @@
3686 QApplication::processEvents();
3687
3688 m_galleryManager->postInit();
3689- m_view->engine()->addImageProvider(GalleryStandardImageProvider::PROVIDER_ID,
3690- m_galleryManager->takeGalleryStandardImageProvider());
3691
3692 QApplication::processEvents();
3693 if (m_cmdLineParser->startupTimer())
3694
3695=== modified file 'src/gallery-application.h'
3696--- src/gallery-application.h 2015-01-23 20:00:50 +0000
3697+++ src/gallery-application.h 2015-03-10 14:05:41 +0000
3698@@ -93,6 +93,8 @@
3699 private:
3700 void registerQML();
3701 void createView();
3702+ static QObject* exportFileUtilsSingleton(QQmlEngine *engine,
3703+ QJSEngine *scriptEngine);
3704
3705 QQuickView *m_view;
3706 GalleryManager *m_galleryManager;
3707
3708=== modified file 'src/gallery-manager.cpp'
3709--- src/gallery-manager.cpp 2015-01-25 16:36:04 +0000
3710+++ src/gallery-manager.cpp 2015-03-10 14:05:41 +0000
3711@@ -37,7 +37,6 @@
3712 #include "media-monitor.h"
3713
3714 // qml
3715-#include "gallery-standard-image-provider.h"
3716 #include "qml-media-collection-model.h"
3717
3718 // util
3719@@ -59,7 +58,6 @@
3720 const QString& picturesDir)
3721 : collectionsInitialised(false),
3722 m_resource(new Resource(desktopMode, picturesDir)),
3723- m_standardImageProvider(new GalleryStandardImageProvider()),
3724 m_database(0),
3725 m_defaultTemplate(0),
3726 m_mediaCollection(0),
3727@@ -94,7 +92,6 @@
3728 delete m_defaultTemplate;
3729 delete m_resource;
3730 delete m_mediaCollection;
3731- delete m_standardImageProvider;
3732 }
3733
3734 /*!
3735@@ -210,16 +207,6 @@
3736 }
3737
3738 /*!
3739- * \brief GalleryManager::logImageLoading enabled or disbaled logging image load
3740- * times to stdout
3741- * \param log
3742- */
3743-void GalleryManager::logImageLoading(bool log)
3744-{
3745- m_standardImageProvider->setLogging(log);
3746-}
3747-
3748-/*!
3749 * \brief GalleryManager::fillMediaCollection fills the MediaCollection with
3750 * the content of the picture directory
3751 */
3752@@ -293,16 +280,3 @@
3753
3754 startFileMonitoring();
3755 }
3756-
3757-/*!
3758- * \brief GalleryManager::takeGalleryStandardImageProvider returns the standard image provider
3759- * and gives up the owndership
3760- */
3761-GalleryStandardImageProvider* GalleryManager::takeGalleryStandardImageProvider()
3762-{
3763- m_standardImageProvider->setMaxLoadResolution(2048);
3764-
3765- GalleryStandardImageProvider *provider = m_standardImageProvider;
3766- m_standardImageProvider = 0;
3767- return provider;
3768-}
3769
3770=== modified file 'src/gallery-manager.h'
3771--- src/gallery-manager.h 2015-01-25 16:36:04 +0000
3772+++ src/gallery-manager.h 2015-03-10 14:05:41 +0000
3773@@ -35,7 +35,6 @@
3774 class Database;
3775 class EventCollection;
3776 class GalleryManager;
3777-class GalleryStandardImageProvider;
3778 class MediaCollection;
3779 class MediaMonitor;
3780 class MediaObjectFactory;
3781@@ -65,7 +64,6 @@
3782 AlbumCollection *albumCollection();
3783 EventCollection *eventCollection();
3784 Resource *resource() { return m_resource; }
3785- GalleryStandardImageProvider *takeGalleryStandardImageProvider();
3786
3787 void logImageLoading(bool log);
3788
3789@@ -94,7 +92,6 @@
3790 bool collectionsInitialised;
3791
3792 Resource* m_resource;
3793- GalleryStandardImageProvider* m_standardImageProvider;
3794 Database* m_database;
3795 AlbumDefaultTemplate* m_defaultTemplate;
3796 MediaCollection* m_mediaCollection;
3797
3798=== modified file 'src/media-object-factory.cpp'
3799--- src/media-object-factory.cpp 2015-01-26 18:37:49 +0000
3800+++ src/media-object-factory.cpp 2015-03-10 14:05:41 +0000
3801@@ -212,7 +212,7 @@
3802 if (id == INVALID_ID) {
3803 bool metadataOk;
3804 if (mediaType == MediaSource::Photo) {
3805- metadataOk = readPhotoMetadata(photo->pristineFile());
3806+ metadataOk = readPhotoMetadata(photo->file());
3807 } else {
3808 metadataOk = readVideoMetadata(file);
3809 }
3810
3811=== modified file 'src/media/media-source.cpp'
3812--- src/media/media-source.cpp 2014-02-25 15:16:24 +0000
3813+++ src/media/media-source.cpp 2015-03-10 14:05:41 +0000
3814@@ -23,9 +23,6 @@
3815 #include "database.h"
3816 #include "media-table.h"
3817
3818-// qml
3819-#include "gallery-standard-image-provider.h"
3820-
3821 #include <QUrl>
3822
3823 /*!
3824@@ -80,47 +77,6 @@
3825 }
3826
3827 /*!
3828- * \brief MediaSource::galleryPath
3829- * \return
3830- */
3831-QUrl MediaSource::galleryPath() const
3832-{
3833- /* At the moment the only video files we recognize are mp4
3834- * files. This was maintained hardcoded from the previous
3835- * version of gallery app, and should be fixed in the future.
3836- * For those files the full image is the thumbnail/preview itself */
3837- if (m_file.suffix().toLower() == "mp4") {
3838- QString path("image://thumbnailer/");
3839- path.append(m_file.absoluteFilePath());
3840- return QUrl(path);
3841- } else {
3842- return GalleryStandardImageProvider::toURL(m_file);
3843- }
3844-}
3845-
3846-/*!
3847- * \brief MediaSource::galleryPreviewPath
3848- * \return
3849- */
3850-QUrl MediaSource::galleryPreviewPath() const
3851-{
3852- QString path("image://thumbnailer/");
3853- path.append(m_file.absoluteFilePath());
3854- return QUrl(path);
3855-}
3856-
3857-/*!
3858- * \brief MediaSource::galleryThumbnailPath
3859- * \return
3860- */
3861-QUrl MediaSource::galleryThumbnailPath() const
3862-{
3863- QString path("image://thumbnailer/");
3864- path.append(m_file.absoluteFilePath());
3865- return QUrl(path);
3866-}
3867-
3868-/*!
3869 * \brief MediaSource::Image
3870 * \param respectOrientation
3871 * \return
3872
3873=== modified file 'src/media/media-source.h'
3874--- src/media/media-source.h 2013-07-26 15:43:53 +0000
3875+++ src/media/media-source.h 2015-03-10 14:05:41 +0000
3876@@ -47,9 +47,6 @@
3877 Q_OBJECT
3878 Q_PROPERTY(MediaType type READ type NOTIFY typeChanged)
3879 Q_PROPERTY(QUrl path READ path NOTIFY pathChanged)
3880- Q_PROPERTY(QUrl galleryPath READ galleryPath NOTIFY galleryPathChanged)
3881- Q_PROPERTY(QUrl galleryPreviewPath READ galleryPreviewPath NOTIFY galleryPreviewPathChanged)
3882- Q_PROPERTY(QUrl galleryThumbnailPath READ galleryThumbnailPath NOTIFY galleryThumbnailPathChanged)
3883 Q_PROPERTY(int orientation READ orientation NOTIFY orientationChanged)
3884 Q_PROPERTY(QDate exposureDate READ exposureDate NOTIFY exposureDateTimeChanged)
3885 Q_PROPERTY(QTime exposureTimeOfDay READ exposureTimeOfDay NOTIFY exposureDateTimeChanged)
3886@@ -62,9 +59,6 @@
3887 signals:
3888 void typeChanged();
3889 void pathChanged();
3890- void galleryPathChanged();
3891- void galleryPreviewPathChanged();
3892- void galleryThumbnailPathChanged();
3893 void orientationChanged();
3894 void exposureDateTimeChanged();
3895 void dataChanged();
3896@@ -85,10 +79,6 @@
3897
3898 QFileInfo file() const;
3899 QUrl path() const;
3900- virtual QUrl galleryPath() const;
3901-
3902- virtual QUrl galleryPreviewPath() const;
3903- virtual QUrl galleryThumbnailPath() const;
3904
3905 virtual QImage image(bool respectOrientation = true, const QSize &scaleSize=QSize());
3906 virtual Orientation orientation() const;
3907
3908=== modified file 'src/medialoader/CMakeLists.txt'
3909--- src/medialoader/CMakeLists.txt 2014-08-12 18:55:10 +0000
3910+++ src/medialoader/CMakeLists.txt 2015-03-10 14:05:41 +0000
3911@@ -9,20 +9,15 @@
3912 ${gallery_src_SOURCE_DIR}/media
3913 ${gallery_util_src_SOURCE_DIR}
3914 ${CMAKE_BINARY_DIR}
3915- ${EXIV2_INCLUDEDIR}
3916 ${GSTLIB_INCLUDE_DIRS}
3917 ${MEDIAINFO_INCLUDE_DIR}
3918 )
3919
3920 set(gallery_medialoader_HDRS
3921- gallery-standard-image-provider.h
3922- photo-metadata.h
3923 video-metadata.h
3924 )
3925
3926 set(gallery_medialoader_SRCS
3927- gallery-standard-image-provider.cpp
3928- photo-metadata.cpp
3929 video-metadata.cpp
3930 )
3931
3932@@ -45,7 +40,6 @@
3933 endif(CLICK_MODE)
3934
3935 target_link_libraries( ${GALLERY_MEDIALOADER_LIB}
3936- ${EXIV2_LIBRARIES}
3937 ${GSTLIB_LDFLAGS}
3938 ${MEDIAINFO_LIBRARIES}
3939 gallery-util
3940
3941=== removed file 'src/medialoader/gallery-standard-image-provider.cpp'
3942--- src/medialoader/gallery-standard-image-provider.cpp 2014-02-25 15:16:24 +0000
3943+++ src/medialoader/gallery-standard-image-provider.cpp 1970-01-01 00:00:00 +0000
3944@@ -1,479 +0,0 @@
3945-/*
3946- * Copyright (C) 2011 Canonical Ltd
3947- *
3948- * This program is free software: you can redistribute it and/or modify
3949- * it under the terms of the GNU General Public License version 3 as
3950- * published by the Free Software Foundation.
3951- *
3952- * This program is distributed in the hope that it will be useful,
3953- * but WITHOUT ANY WARRANTY; without even the implied warranty of
3954- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3955- * GNU General Public License for more details.
3956- *
3957- * You should have received a copy of the GNU General Public License
3958- * along with this program. If not, see <http://www.gnu.org/licenses/>.
3959- *
3960- * Authors:
3961- * Lucas Beeler <lucas@yorba.org>
3962- * Jim Nelson <jim@yorba.org>
3963- */
3964-
3965-#include "gallery-standard-image-provider.h"
3966-#include "photo-metadata.h"
3967-
3968-#include <QDebug>
3969-#include <QElapsedTimer>
3970-#include <QImageReader>
3971-#include <QSize>
3972-#include <QUrlQuery>
3973-
3974-const char* GalleryStandardImageProvider::PROVIDER_ID = "gallery-standard";
3975-const char* GalleryStandardImageProvider::PROVIDER_ID_SCHEME = "image://gallery-standard/";
3976-
3977-const char* GalleryStandardImageProvider::REVISION_PARAM_NAME = "edit";
3978-const char* GalleryStandardImageProvider::ORIENTATION_PARAM_NAME = "orientation";
3979-
3980-const char* GalleryStandardImageProvider::SIZE_KEY = "size_level";
3981-
3982-const long MAX_CACHE_BYTES = 20L * 1024L * 1024L;
3983-
3984-const int SCALED_LOAD_FLOOR_DIM_PIXELS = 360;
3985-
3986-/*!
3987- * \brief GalleryStandardImageProvider::GalleryStandardImageProvider
3988- */
3989-GalleryStandardImageProvider::GalleryStandardImageProvider()
3990- : QQuickImageProvider(QQuickImageProvider::Image),
3991- m_cachedBytes(0),
3992- m_logImageLoading(false),
3993- m_maxLoadResolution(INT_MAX)
3994-{
3995-}
3996-
3997-/*!
3998- * \brief GalleryStandardImageProvider::~GalleryStandardImageProvider
3999- */
4000-GalleryStandardImageProvider::~GalleryStandardImageProvider()
4001-{
4002- // NOTE: This assumes that the GSIP is not receiving any requests any longer
4003- while (!m_fifo.isEmpty())
4004- delete m_cache.value(m_fifo.takeFirst());
4005-}
4006-
4007-/*!
4008- * \brief GalleryStandardImageProvider::toURL
4009- * \param file
4010- * \return
4011- */
4012-QUrl GalleryStandardImageProvider::toURL(const QFileInfo& file)
4013-{
4014- return QUrl::fromUserInput(PROVIDER_ID_SCHEME + file.absoluteFilePath());
4015-}
4016-
4017-#define LOG_IMAGE_STATUS(status) { \
4018- if (m_logImageLoading) \
4019- loggingStr += status; \
4020- }
4021-
4022-/*!
4023- * \brief GalleryStandardImageProvider::requestImage
4024- * \param id
4025- * \param size
4026- * \param requestedSize
4027- * \return
4028- */
4029-QImage GalleryStandardImageProvider::requestImage(const QString& id,
4030- QSize* size, const QSize& requestedSize)
4031-{
4032- // for LOG_IMAGE_STATUS
4033- QString loggingStr = "";
4034- QElapsedTimer timer;
4035- timer.start();
4036-
4037- QUrl url(id);
4038- QFileInfo photoFile(url.path());
4039-
4040- QImage readyImage;
4041- uint bytesLoaded = 0;
4042- long currentCachedBytes = 0;
4043- int currentCacheEntries = 0;
4044-
4045- CachedImage* cachedImage = claimCachedImageEntry(id, loggingStr);
4046- Q_ASSERT(cachedImage != NULL);
4047-
4048- readyImage = fetchCachedImage(cachedImage, requestedSize, &bytesLoaded,
4049- loggingStr);
4050- if (readyImage.isNull())
4051- LOG_IMAGE_STATUS("load-failure ");
4052-
4053- releaseCachedImageEntry(cachedImage, bytesLoaded, &currentCachedBytes, &currentCacheEntries);
4054-
4055- if (m_logImageLoading) {
4056- if (bytesLoaded > 0) {
4057- qDebug("%s %s req:%dx%d ret:%dx%d cache:%ldb/%d loaded:%db time:%lldms", qPrintable(loggingStr),
4058- qPrintable(id), requestedSize.width(), requestedSize.height(), readyImage.width(),
4059- readyImage.height(), currentCachedBytes, currentCacheEntries, bytesLoaded,
4060- timer.elapsed());
4061- } else {
4062- qDebug("%s %s req:%dx%d ret:%dx%d cache:%ldb/%d time:%lldms", qPrintable(loggingStr),
4063- qPrintable(id), requestedSize.width(), requestedSize.height(), readyImage.width(),
4064- readyImage.height(), currentCachedBytes, currentCacheEntries, timer.elapsed());
4065- }
4066- }
4067-
4068- if (size != NULL)
4069- *size = readyImage.size();
4070-
4071- return readyImage;
4072-}
4073-
4074-/*!
4075- * \brief GalleryStandardImageProvider::setLogging enables to print photo loading
4076- * times to stout
4077- * \param enableLogging
4078- */
4079-void GalleryStandardImageProvider::setLogging(bool enableLogging)
4080-{
4081- m_logImageLoading = enableLogging;
4082-}
4083-
4084-/*!
4085- * \brief GalleryStandardImageProvider::setMaxLoadResolution sets the maximal size of the loaded
4086- * images. Images loaded are limited to a max width/height, but keep their aspect ratio.
4087- * Limiting the size is useful to not exceed the texture size limit of the GPU. Or to limit for
4088- * performance reasons.
4089- * Default is to have no limit (INT_MAX).
4090- * \param resolution maxiaml length in pixel
4091- */
4092-void GalleryStandardImageProvider::setMaxLoadResolution(int resolution)
4093-{
4094- if (resolution > 0)
4095- m_maxLoadResolution = resolution;
4096-}
4097-
4098-/*!
4099- * \brief GalleryStandardImageProvider::claim_cached_image_entry
4100- * Returns a CachedImage with an inUseCount > 0, meaning it cannot be
4101- * removed from the cache until released
4102- * \param id
4103- * \param loggingStr
4104- * \return
4105- */
4106-GalleryStandardImageProvider::CachedImage* GalleryStandardImageProvider::claimCachedImageEntry(
4107- const QString& id, QString& loggingStr)
4108-{
4109- // lock the cache table and retrieve the element for the cached image; if
4110- // not found, create one as a placeholder
4111- m_cacheMutex.lock();
4112-
4113- CachedImage* cachedImage = m_cache.value(id, NULL);
4114- if (cachedImage != NULL) {
4115- // remove CachedImage before prepending to FIFO
4116- m_fifo.removeOne(id);
4117- } else {
4118- cachedImage = new CachedImage(id, idToFile(id));
4119- m_cache.insert(id, cachedImage);
4120- LOG_IMAGE_STATUS("new-cache-entry ");
4121- }
4122-
4123- // add to front of FIFO
4124- m_fifo.prepend(id);
4125-
4126- // should be the same size, always
4127- Q_ASSERT(m_cache.size() == m_fifo.size());
4128-
4129- // claim the CachedImage *while cacheMutex_ is locked* ... this prevents the
4130- // CachedImage from being removed from the cache while its being filled
4131- cachedImage->inUseCount++;
4132-
4133- m_cacheMutex.unlock();
4134-
4135- return cachedImage;
4136-}
4137-
4138-/*!
4139- * \brief GalleryStandardImageProvider::fetch_cached_image Inspects and loads a proper image
4140- * Inspects and loads a proper image for this request into the CachedImage
4141- * \param cachedImage
4142- * \param requestedSize
4143- * \param bytesLoaded
4144- * \param loggingStr
4145- * \return
4146- */
4147-QImage GalleryStandardImageProvider::fetchCachedImage(CachedImage *cachedImage,
4148- const QSize& requestedSize, uint* bytesLoaded, QString& loggingStr)
4149-{
4150- Q_ASSERT(cachedImage != NULL);
4151-
4152- // the final image returned to the user
4153- QImage readyImage;
4154- Q_ASSERT(readyImage.isNull());
4155-
4156- // lock the cached image itself to access
4157- cachedImage->imageMutex.lock();
4158-
4159- // if image is available, see if a fit
4160- if (cachedImage->isCacheHit(requestedSize)) {
4161- readyImage = cachedImage->image;
4162- LOG_IMAGE_STATUS("cache-hit ");
4163- } else if (cachedImage->isReady()) {
4164- LOG_IMAGE_STATUS("cache-miss ");
4165- }
4166-
4167- if (bytesLoaded != NULL)
4168- *bytesLoaded = 0;
4169-
4170- // if not available, load now
4171- if (readyImage.isNull()) {
4172- QImageReader reader(cachedImage->file);
4173-
4174- // load file's original size
4175- QSize fullSize = reader.size();
4176- QSize loadSize(fullSize);
4177-
4178- // use scaled load-and-decode if size has been requested
4179- if (fullSize.isValid() && (requestedSize.width() > 0 || requestedSize.height() > 0)) {
4180- // adjust requested size if necessary, but if small enough, just load the
4181- // whole thing once and be done with it
4182- if (fullSize.width() > SCALED_LOAD_FLOOR_DIM_PIXELS
4183- && fullSize.height() > SCALED_LOAD_FLOOR_DIM_PIXELS) {
4184- loadSize.scale(requestedSize, Qt::KeepAspectRatio);
4185- if (loadSize.width() > fullSize.width() || loadSize.height() > fullSize.height())
4186- loadSize = fullSize;
4187- }
4188- }
4189-
4190- if (loadSize.width() > m_maxLoadResolution || loadSize.height() > m_maxLoadResolution) {
4191- loadSize.scale(m_maxLoadResolution, m_maxLoadResolution, Qt::KeepAspectRatio);
4192- }
4193-
4194- if (loadSize != fullSize) {
4195- LOG_IMAGE_STATUS("scaled-load ");
4196-
4197- // configure reader for scaled load-and-decode
4198- reader.setScaledSize(loadSize);
4199- } else {
4200- LOG_IMAGE_STATUS("full-load ");
4201- }
4202-
4203- readyImage = reader.read();
4204- if (!readyImage.isNull()) {
4205- if (!fullSize.isValid())
4206- fullSize = readyImage.size();
4207-
4208- // If orientation not supplied in URI, load from file and save in cache
4209- Orientation orientation = TOP_LEFT_ORIGIN;
4210- if (cachedImage->hasOrientation) {
4211- orientation = cachedImage->orientation;
4212- } else {
4213- std::auto_ptr<PhotoMetadata> metadata(PhotoMetadata::fromFile(
4214- cachedImage->file));
4215- if (metadata.get() != NULL)
4216- orientation = metadata->orientation();
4217- }
4218-
4219- // rotate image if not TOP LEFT
4220- if (orientation != TOP_LEFT_ORIGIN) {
4221- readyImage = readyImage.transformed(
4222- OrientationCorrection::fromOrientation(orientation).toTransform());
4223- }
4224-
4225- cachedImage->storeImage(readyImage, fullSize, orientation);
4226-
4227- if (bytesLoaded != NULL)
4228- *bytesLoaded = readyImage.byteCount();
4229- } else {
4230- qDebug("Unable to load %s: %s", qPrintable(cachedImage->id),
4231- qPrintable(reader.errorString()));
4232- }
4233- }
4234-
4235- cachedImage->imageMutex.unlock();
4236-
4237- return readyImage;
4238-}
4239-
4240-/*!
4241- * \brief GalleryStandardImageProvider::release_cached_image_entry
4242- * Releases a CachedImage to the cache; takes its bytes loaded (0 if nothing
4243- * was loaded) and returns the current cached byte total
4244- * \param cachedImage
4245- * \param bytesLoaded
4246- * \param currentCachedBytes
4247- * \param currentCacheEntries
4248- */
4249-void GalleryStandardImageProvider::releaseCachedImageEntry
4250-(CachedImage *cachedImage, uint bytesLoaded,
4251- long *currentCachedBytes, int *currentCacheEntries)
4252-{
4253- Q_ASSERT(cachedImage != NULL);
4254-
4255- // update total cached bytes and remove excess bytes
4256- m_cacheMutex.lock();
4257-
4258- m_cachedBytes += bytesLoaded;
4259-
4260- // update the CachedImage use count and byte count inside of *cachedMutex_ lock*
4261- Q_ASSERT(cachedImage->inUseCount > 0);
4262- cachedImage->inUseCount--;
4263- if (bytesLoaded != 0)
4264- cachedImage->byteCount = bytesLoaded;
4265-
4266- // trim the cache
4267- QList<CachedImage*> dropList;
4268- while (m_cachedBytes > MAX_CACHE_BYTES && !m_fifo.isEmpty()) {
4269- QString droppedFile = m_fifo.takeLast();
4270-
4271- CachedImage* droppedCachedImage = m_cache.value(droppedFile);
4272- Q_ASSERT(droppedCachedImage != NULL);
4273-
4274- // for simplicity, stop when dropped item is in use or doesn't contain
4275- // an image (which it won't for too long) ... will clean up next time
4276- // through
4277- if (droppedCachedImage->inUseCount > 0) {
4278- m_fifo.append(droppedFile);
4279-
4280- break;
4281- }
4282-
4283- // remove from map
4284- m_cache.remove(droppedFile);
4285-
4286- // decrement total cached size
4287- m_cachedBytes -= droppedCachedImage->byteCount;
4288- Q_ASSERT(m_cachedBytes >= 0);
4289-
4290- dropList.append(droppedCachedImage);
4291- }
4292-
4293- // coherency is good
4294- Q_ASSERT(m_cache.size() == m_fifo.size());
4295-
4296- if (currentCachedBytes != NULL)
4297- *currentCachedBytes = m_cachedBytes;
4298-
4299- if (currentCacheEntries != NULL)
4300- *currentCacheEntries = m_cache.size();
4301-
4302- m_cacheMutex.unlock();
4303-
4304- // perform actual deletion outside of lock
4305- while (!dropList.isEmpty())
4306- delete dropList.takeFirst();
4307-}
4308-
4309-/*!
4310- * \brief GalleryStandardImageProvider::orientSize
4311- * \param size
4312- * \param orientation
4313- * \return
4314- */
4315-QSize GalleryStandardImageProvider::orientSize(const QSize& size, Orientation orientation)
4316-{
4317- switch (orientation) {
4318- case LEFT_TOP_ORIGIN:
4319- case RIGHT_TOP_ORIGIN:
4320- case RIGHT_BOTTOM_ORIGIN:
4321- case LEFT_BOTTOM_ORIGIN:
4322- return QSize(size.height(), size.width());
4323- break;
4324-
4325- default:
4326- // no change
4327- return size;
4328- }
4329-}
4330-
4331-/*!
4332- * \brief GalleryStandardImageProvider::idToFile
4333- * \param id
4334- * \return
4335- */
4336-QString GalleryStandardImageProvider::idToFile(const QString& id) const
4337-{
4338- QUrl url = QUrl(id);
4339- QString fileName = url.path();
4340- return fileName;
4341-}
4342-
4343-/*!
4344- * \brief GalleryStandardImageProvider::CachedImage::CachedImage
4345- * \param id the full URI of the image
4346- * \param fileName the filename for the URI (the file itself)
4347- */
4348-GalleryStandardImageProvider::CachedImage::CachedImage(const QString& fileId,
4349- const QString& filename)
4350- : id(fileId), uri(fileId), file(filename), hasOrientation(false),
4351- orientation(TOP_LEFT_ORIGIN), inUseCount(0), byteCount(0)
4352-{
4353- QUrlQuery query(uri);
4354- if (query.hasQueryItem(ORIENTATION_PARAM_NAME)) {
4355- QString value = query.queryItemValue(ORIENTATION_PARAM_NAME);
4356- if (!value.isEmpty()) {
4357- bool ok = false;
4358- int toInt = value.toInt(&ok);
4359- if (ok && toInt >= MIN_ORIENTATION && toInt <= MAX_ORIENTATION) {
4360- orientation = (Orientation) toInt;
4361- hasOrientation = true;
4362- }
4363- }
4364- }
4365-}
4366-
4367-/*!
4368- * \brief GalleryStandardImageProvider::CachedImage::storeImage
4369- * Importand: the following should only be called when imageMutex_ is locked
4370- * \param image
4371- * \param fullSize
4372- * \param orientation
4373- */
4374-void GalleryStandardImageProvider::CachedImage::storeImage(const QImage& newImage,
4375- const QSize& newFullSize,
4376- Orientation newOrientation)
4377-{
4378- image = newImage;
4379- fullSize = orientSize(newFullSize, orientation);
4380- hasOrientation = true;
4381- orientation = newOrientation;
4382-}
4383-
4384-/*!
4385- * \brief GalleryStandardImageProvider::CachedImage::isReady
4386- * \return
4387- */
4388-bool GalleryStandardImageProvider::CachedImage::isReady() const
4389-{
4390- return !image.isNull();
4391-}
4392-
4393-/*!
4394- * \brief GalleryStandardImageProvider::CachedImage::isFullSized
4395- * \return
4396- */
4397-bool GalleryStandardImageProvider::CachedImage::isFullSized() const
4398-{
4399- return isReady() && (image.size() == fullSize);
4400-}
4401-
4402-/*!
4403- * \brief GalleryStandardImageProvider::CachedImage::isCacheHit
4404- * \param requestedSize
4405- * \return
4406- */
4407-bool GalleryStandardImageProvider::CachedImage::isCacheHit(const QSize& requestedSize) const
4408-{
4409- if (!isReady())
4410- return false;
4411-
4412- if (isFullSized())
4413- return true;
4414-
4415- QSize properRequestedSize = orientSize(requestedSize, orientation);
4416-
4417- if ((properRequestedSize.width() != 0 && image.width() >= properRequestedSize.width())
4418- || (properRequestedSize.height() != 0 && image.height() >= properRequestedSize.height())) {
4419- return true;
4420- }
4421-
4422- return false;
4423-}
4424
4425=== removed file 'src/medialoader/gallery-standard-image-provider.h'
4426--- src/medialoader/gallery-standard-image-provider.h 2014-02-25 15:16:24 +0000
4427+++ src/medialoader/gallery-standard-image-provider.h 1970-01-01 00:00:00 +0000
4428@@ -1,128 +0,0 @@
4429-/*
4430- * Copyright (C) 2011 Canonical Ltd
4431- *
4432- * This program is free software: you can redistribute it and/or modify
4433- * it under the terms of the GNU General Public License version 3 as
4434- * published by the Free Software Foundation.
4435- *
4436- * This program is distributed in the hope that it will be useful,
4437- * but WITHOUT ANY WARRANTY; without even the implied warranty of
4438- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4439- * GNU General Public License for more details.
4440- *
4441- * You should have received a copy of the GNU General Public License
4442- * along with this program. If not, see <http://www.gnu.org/licenses/>.
4443- *
4444- * Authors:
4445- * Lucas Beeler <lucas@yorba.org>
4446- * Jim Nelson <jim@yorba.org>
4447-*/
4448-
4449-#ifndef GALLERY_GALLERY_STANDARD_IMAGE_PROVIDER_H_
4450-#define GALLERY_GALLERY_STANDARD_IMAGE_PROVIDER_H_
4451-
4452-// util
4453-#include "orientation.h"
4454-
4455-#include <QFileInfo>
4456-#include <QImage>
4457-#include <QMap>
4458-#include <QMutex>
4459-#include <QObject>
4460-#include <QQuickImageProvider>
4461-#include <QSize>
4462-#include <QString>
4463-#include <QUrl>
4464-
4465-/*!
4466- * Gallery uses a custom image provider for three reasons:
4467- *
4468- * 1. QML's image loader does not respect EXIF orientation. This provider will
4469- * rotate and mirror the image as necessary.
4470- *
4471- * 2. QML's image caching appears to be directly related to image size (scaling)
4472- * which leads to thrashing when animating a thumbnail or loading images at
4473- * various sizes (such as in the album viewer versus the photo viewer).
4474- * The strategy here is to cache the largest requested size of the image
4475- * and allow QML to downscale it as necessary. This minimizes expensive
4476- * JPEG load-and-decodes.
4477- *
4478- * 3. Logging (enabled with --log-image-loading) allows for monitoring of
4479- * all image I/O, useful when debugging and optimizing.
4480- */
4481-class GalleryStandardImageProvider : public QObject, public QQuickImageProvider
4482-{
4483- Q_OBJECT
4484-
4485-public:
4486- static const char* PROVIDER_ID;
4487- static const char* PROVIDER_ID_SCHEME;
4488-
4489- static const char* REVISION_PARAM_NAME;
4490- static const char* ORIENTATION_PARAM_NAME;
4491-
4492- static const char* SIZE_KEY;
4493-
4494- GalleryStandardImageProvider();
4495- virtual ~GalleryStandardImageProvider();
4496-
4497- static QUrl toURL(const QFileInfo& file);
4498-
4499- virtual QImage requestImage(const QString& id, QSize* size,
4500- const QSize& requestedSize);
4501-
4502- void setLogging(bool enableLogging);
4503- void setMaxLoadResolution(int resolution);
4504-
4505-private:
4506- class CachedImage {
4507- public:
4508- const QString id;
4509- const QUrl uri;
4510- const QString file;
4511- QMutex imageMutex;
4512-
4513- // these fields should only be accessed when imageMutex_ is locked
4514- bool hasOrientation;
4515- QImage image;
4516- QSize fullSize;
4517- Orientation orientation;
4518-
4519- // the following should only be accessed when cacheMutex_ is locked; the
4520- // counter controls removing a CachedImage entry from the cache table
4521- int inUseCount;
4522- uint byteCount;
4523-
4524- CachedImage(const QString& fileId, const QString& filename);
4525-
4526- void storeImage(const QImage& newImage, const QSize& newFullSize,
4527- Orientation newOrientation);
4528- bool isFullSized() const;
4529- bool isReady() const;
4530- bool isCacheHit(const QSize& requestedSize) const;
4531- };
4532-
4533- QMap<QString, CachedImage*> m_cache;
4534- QList<QString> m_fifo;
4535- QMutex m_cacheMutex;
4536- long m_cachedBytes;
4537- bool m_logImageLoading;
4538- int m_maxLoadResolution;
4539-
4540- static QSize orientSize(const QSize& size, Orientation orientation);
4541-
4542- CachedImage* claimCachedImageEntry(const QString& id, QString& loggingStr);
4543-
4544- QImage fetchCachedImage(CachedImage* cachedImage, const QSize& requestedSize,
4545- uint* bytesLoaded, QString& loggingStr);
4546-
4547- void releaseCachedImageEntry(CachedImage* cachedImage, uint bytesLoaded,
4548- long* currentCachedBytes, int* currentCacheEntries);
4549-
4550- QString idToFile(const QString& id) const;
4551-
4552- //Allow our test access to private variables.
4553- friend class tst_GalleryStandardImageProvider;
4554-};
4555-
4556-#endif // GALLERY_GALLERY_STANDARD_IMAGE_PROVIDER_H_
4557
4558=== removed file 'src/medialoader/photo-metadata.cpp'
4559--- src/medialoader/photo-metadata.cpp 2015-01-20 20:17:49 +0000
4560+++ src/medialoader/photo-metadata.cpp 1970-01-01 00:00:00 +0000
4561@@ -1,285 +0,0 @@
4562-/*
4563- * Copyright (C) 2011 Canonical Ltd
4564- *
4565- * This program is free software: you can redistribute it and/or modify
4566- * it under the terms of the GNU General Public License version 3 as
4567- * published by the Free Software Foundation.
4568- *
4569- * This program is distributed in the hope that it will be useful,
4570- * but WITHOUT ANY WARRANTY; without even the implied warranty of
4571- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4572- * GNU General Public License for more details.
4573- *
4574- * You should have received a copy of the GNU General Public License
4575- * along with this program. If not, see <http://www.gnu.org/licenses/>.
4576- *
4577- * Authors:
4578- * Lucas Beeler <lucas@yorba.org>
4579- */
4580-
4581-#include "photo-metadata.h"
4582-
4583-#include <cstdio>
4584-
4585-namespace {
4586-const Orientation DEFAULT_ORIENTATION = TOP_LEFT_ORIGIN;
4587-const char* EXIF_ORIENTATION_KEY = "Exif.Image.Orientation";
4588-const char* EXIF_DATETIMEDIGITIZED_KEY = "Exif.Photo.DateTimeDigitized";
4589-const char* EXPOSURE_TIME_KEYS[] = {
4590- "Exif.Photo.DateTimeDigitized",
4591- "Xmp.exif.DateTimeDigitized",
4592- "Exif.Photo.DateTimeOriginal",
4593- "Xmp.exif.DateTimeOriginal",
4594- "Xmp.xmp.CreateDate",
4595- "Exif.Image.DateTime"
4596-};
4597-const size_t NUM_EXPOSURE_TIME_KEYS = 6;
4598-const char* EXIF_DATE_FORMATS[] = {
4599- "%d:%d:%d %d:%d:%d",
4600-
4601- // for Minolta DiMAGE E223 (colon, instead of space, separates day from
4602- // hour in exif)
4603- "%d:%d:%d:%d:%d:%d",
4604-
4605- // for Samsung NV10 (which uses a period instead of colons for the date
4606- // and two spaces between date and time)
4607- "%d.%d.%d %d:%d:%d"
4608-};
4609-const size_t NUM_EXIF_DATE_FORMATS = 3;
4610-
4611-const char* get_first_matched(const char* keys[], size_t n_keys,
4612- const QSet<QString>& in) {
4613- for (size_t i = 0; i < n_keys; i++) {
4614- if (in.contains(keys[i]))
4615- return keys[i];
4616- }
4617-
4618- return NULL;
4619-}
4620-
4621-bool is_xmp_key(const char* key) {
4622- return (key != NULL) ? (std::strncmp("Xmp.", key, 4) == 0) : false;
4623-}
4624-
4625-bool is_exif_key(const char* key) {
4626- return (key != NULL) ? (std::strncmp("Exif.", key, 5) == 0) : false;
4627-}
4628-
4629-// caller should test if 's' could be successfully parsed by invoking the
4630-// isValid() method on the returned QDateTime instance; if isValid() == false,
4631-// 's' couldn't be parsed
4632-QDateTime parse_xmp_date_string(const char* s) {
4633- return QDateTime::fromString(s, Qt::ISODate);
4634-}
4635-
4636-// caller should test if 's' could be successfully parsed by invoking the
4637-// isValid() method on the returned QDateTime instance; if isValid() == false,
4638-// 's' couldn't be parsed
4639-QDateTime parse_exif_date_string(const char* s) {
4640- for (size_t i = 0; i < NUM_EXIF_DATE_FORMATS; i++) {
4641- int year, month, day, hour, minute, second;
4642- if (std::sscanf(s, EXIF_DATE_FORMATS[i], &year, &month, &day, &hour,
4643- &minute, &second) == 6) {
4644- // no need to check year, month, day, hour, minute and second variables
4645- // for bogus values before using them -- if the values are bogus, the
4646- // resulting QDateTime will be invalid, which is exactly what we want
4647- return QDateTime(QDate(year, month, day), QTime(hour, minute, second));
4648- }
4649- }
4650-
4651- // the no argument QDateTime constructor produces an invalid QDateTime,
4652- // which is what we want
4653- return QDateTime();
4654-}
4655-} // namespace
4656-
4657-/*!
4658- * \brief PhotoMetadata::PhotoMetadata
4659- * \param filepath
4660- */
4661-PhotoMetadata::PhotoMetadata(const char* filepath)
4662- : m_fileSourceInfo(filepath)
4663-{
4664- m_image = Exiv2::ImageFactory::open(filepath);
4665- m_image->readMetadata();
4666-}
4667-
4668-/*!
4669- * \brief PhotoMetadata::fromFile
4670- * \param filepath
4671- * \return
4672- */
4673-PhotoMetadata* PhotoMetadata::fromFile(const char* filepath)
4674-{
4675- PhotoMetadata* result = NULL;
4676- try {
4677- result = new PhotoMetadata(filepath);
4678-
4679- if (!result->m_image->good()) {
4680- qDebug("Invalid image metadata in %s", filepath);
4681- delete result;
4682- return NULL;
4683- }
4684-
4685- Exiv2::ExifData& exif_data = result->m_image->exifData();
4686- Exiv2::ExifData::const_iterator end = exif_data.end();
4687- for (Exiv2::ExifData::const_iterator i = exif_data.begin(); i != end; i++)
4688- result->m_keysPresent.insert(QString(i->key().c_str()));
4689-
4690- Exiv2::XmpData& xmp_data = result->m_image->xmpData();
4691- Exiv2::XmpData::const_iterator end1 = xmp_data.end();
4692- for (Exiv2::XmpData::const_iterator i = xmp_data.begin(); i != end1; i++)
4693- result->m_keysPresent.insert(QString(i->key().c_str()));
4694-
4695- return result;
4696- } catch (Exiv2::AnyError& e) {
4697- qDebug("Error loading image metadata: %s", e.what());
4698- delete result;
4699- return NULL;
4700- }
4701-}
4702-
4703-/*!
4704- * \brief PhotoMetadata::fromFile
4705- * \param file
4706- * \return
4707- */
4708-PhotoMetadata* PhotoMetadata::fromFile(const QFileInfo &file)
4709-{
4710- return PhotoMetadata::fromFile(file.absoluteFilePath().toStdString().c_str());
4711-}
4712-
4713-/*!
4714- * \brief PhotoMetadata::orientation
4715- * \return
4716- */
4717-Orientation PhotoMetadata::orientation() const
4718-{
4719- Exiv2::ExifData& exif_data = m_image->exifData();
4720-
4721- if (exif_data.empty())
4722- return DEFAULT_ORIENTATION;
4723-
4724- if (m_keysPresent.find(EXIF_ORIENTATION_KEY) == m_keysPresent.end())
4725- return DEFAULT_ORIENTATION;
4726-
4727- long orientation_code = exif_data[EXIF_ORIENTATION_KEY].toLong();
4728- if (orientation_code < MIN_ORIENTATION || orientation_code > MAX_ORIENTATION)
4729- return DEFAULT_ORIENTATION;
4730-
4731- return static_cast<Orientation>(orientation_code);
4732-}
4733-
4734-/*!
4735- * \brief PhotoMetadata::exposureTime
4736- * \return
4737- */
4738-QDateTime PhotoMetadata::exposureTime() const
4739-{
4740- const char* matched = get_first_matched(EXPOSURE_TIME_KEYS,
4741- NUM_EXPOSURE_TIME_KEYS, m_keysPresent);
4742- if (matched == NULL)
4743- return QDateTime();
4744-
4745- if (is_exif_key(matched))
4746- return parse_exif_date_string(m_image->exifData()[matched].toString().c_str());
4747-
4748- if (is_xmp_key(matched))
4749- return parse_xmp_date_string(m_image->xmpData()[matched].toString().c_str());
4750-
4751- // No valid/known tag for exposure date/time
4752- return QDateTime();
4753-}
4754-
4755-/*!
4756- * \brief PhotoMetadata::orientationCorrection
4757- * \return
4758- */
4759-OrientationCorrection PhotoMetadata::orientationCorrection() const
4760-{
4761- return OrientationCorrection::fromOrientation(orientation());
4762-}
4763-
4764-/*!
4765- * \brief PhotoMetadata::orientationTransform
4766- * \return
4767- */
4768-QTransform PhotoMetadata::orientationTransform() const
4769-{
4770- return orientationCorrection().toTransform();
4771-}
4772-
4773-/*!
4774- * \brief PhotoMetadata::setOrientation
4775- * \param orientation
4776- */
4777-void PhotoMetadata::setOrientation(Orientation orientation)
4778-{
4779- try {
4780- if (!m_image->good()) {
4781- qDebug("Do not set Orientation, invalid image metadata.");
4782- return;
4783- }
4784-
4785- Exiv2::ExifData& exif_data = m_image->exifData();
4786-
4787- exif_data[EXIF_ORIENTATION_KEY] = orientation;
4788-
4789- if (!m_keysPresent.contains(EXIF_ORIENTATION_KEY))
4790- m_keysPresent.insert(EXIF_ORIENTATION_KEY);
4791-
4792- } catch (Exiv2::AnyError& e) {
4793- qDebug("Do not set Orientation, error reading image metadata; %s", e.what());
4794- return;
4795- }
4796-}
4797-
4798-/*!
4799- * \brief PhotoMetadata::setDateTimeDigitized
4800- * \param digitized
4801- */
4802-void PhotoMetadata::setDateTimeDigitized(const QDateTime& digitized)
4803-{
4804- try {
4805- if (!m_image->good()) {
4806- qDebug("Do not set DateTimeDigitized, invalid image metadata.");
4807- return;
4808- }
4809-
4810- Exiv2::ExifData& exif_data = m_image->exifData();
4811-
4812- exif_data[EXIF_DATETIMEDIGITIZED_KEY] = digitized.toString("yyyy:MM:dd hh:mm:ss").toStdString();
4813-
4814- if (!m_keysPresent.contains(EXIF_DATETIMEDIGITIZED_KEY))
4815- m_keysPresent.insert(EXIF_DATETIMEDIGITIZED_KEY);
4816-
4817- } catch (Exiv2::AnyError& e) {
4818- qDebug("Do not set DateTimeDigitized, error reading image metadata; %s", e.what());
4819- return;
4820- }
4821-}
4822-
4823-void PhotoMetadata::updateThumbnail(QImage image)
4824-{
4825- QImage scaled = image.scaled(image.width() / THUMBNAIL_SCALE,
4826- image.height() / THUMBNAIL_SCALE);
4827- QBuffer jpeg;
4828- jpeg.open(QIODevice::WriteOnly);
4829- scaled.save(&jpeg, "jpeg");
4830- Exiv2::ExifThumb thumb(m_image->exifData());
4831- thumb.setJpegThumbnail((Exiv2::byte*) jpeg.data().constData(), jpeg.size());
4832-}
4833-
4834-/*!
4835- * \brief PhotoMetadata::save
4836- * \return
4837- */
4838-bool PhotoMetadata::save() const
4839-{
4840- try {
4841- m_image->writeMetadata();
4842- return true;
4843- } catch (Exiv2::AnyError& e) {
4844- return false;
4845- }
4846-}
4847
4848=== removed file 'src/medialoader/photo-metadata.h'
4849--- src/medialoader/photo-metadata.h 2015-01-20 20:17:49 +0000
4850+++ src/medialoader/photo-metadata.h 1970-01-01 00:00:00 +0000
4851@@ -1,69 +0,0 @@
4852-/*
4853- * Copyright (C) 2011 Canonical Ltd
4854- *
4855- * This program is free software: you can redistribute it and/or modify
4856- * it under the terms of the GNU General Public License version 3 as
4857- * published by the Free Software Foundation.
4858- *
4859- * This program is distributed in the hope that it will be useful,
4860- * but WITHOUT ANY WARRANTY; without even the implied warranty of
4861- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4862- * GNU General Public License for more details.
4863- *
4864- * You should have received a copy of the GNU General Public License
4865- * along with this program. If not, see <http://www.gnu.org/licenses/>.
4866- *
4867- * Authors:
4868- * Lucas Beeler <lucas@yorba.org>
4869- */
4870-
4871-#ifndef GALLERY_PHOTO_METADATA_H_
4872-#define GALLERY_PHOTO_METADATA_H_
4873-
4874-// util
4875-#include <orientation.h>
4876-
4877-#include <QDateTime>
4878-#include <QFileInfo>
4879-#include <QObject>
4880-#include <QString>
4881-#include <QSet>
4882-#include <QTransform>
4883-#include <QImage>
4884-#include <QBuffer>
4885-
4886-#include <exiv2/exiv2.hpp>
4887-
4888-#define THUMBNAIL_SCALE 8.5
4889-
4890-/*!
4891- * \brief The PhotoMetadata class
4892- */
4893-class PhotoMetadata : public QObject
4894-{
4895- Q_OBJECT
4896-
4897-public:
4898- static PhotoMetadata* fromFile(const char* filepath);
4899- static PhotoMetadata* fromFile(const QFileInfo& file);
4900-
4901- QDateTime exposureTime() const;
4902- Orientation orientation() const;
4903- QTransform orientationTransform() const;
4904- OrientationCorrection orientationCorrection() const;
4905-
4906- void setOrientation(Orientation orientation);
4907- void setDateTimeDigitized(const QDateTime& digitized);
4908- void updateThumbnail(QImage image);
4909-
4910- bool save() const;
4911-
4912-private:
4913- PhotoMetadata(const char* filepath);
4914-
4915- Exiv2::Image::AutoPtr m_image;
4916- QSet<QString> m_keysPresent;
4917- QFileInfo m_fileSourceInfo;
4918-};
4919-
4920-#endif // GALLERY_PHOTO_METADATA_H_
4921
4922=== modified file 'src/photo/CMakeLists.txt'
4923--- src/photo/CMakeLists.txt 2013-08-15 06:40:15 +0000
4924+++ src/photo/CMakeLists.txt 2015-03-10 14:05:41 +0000
4925@@ -8,22 +8,17 @@
4926 ${gallery_database_src_SOURCE_DIR}
4927 ${gallery_media_src_SOURCE_DIR}
4928 ${gallery_medialoader_src_SOURCE_DIR}
4929+ ${gallery_photoeditor_src_SOURCE_DIR}
4930 ${gallery_util_src_SOURCE_DIR}
4931 ${CMAKE_BINARY_DIR}
4932 )
4933
4934 set(gallery_photo_HDRS
4935 photo.h
4936- photo-caches.h
4937- photo-edit-state.h
4938- photo-edit-thread.h
4939 )
4940
4941 set(gallery_photo_SRCS
4942 photo.cpp
4943- photo-caches.cpp
4944- photo-edit-state.cpp
4945- photo-edit-thread.cpp
4946 )
4947
4948 add_library(${GALLERY_PHOTO_LIB}
4949
4950=== removed file 'src/photo/photo-caches.cpp'
4951--- src/photo/photo-caches.cpp 2014-02-19 14:54:43 +0000
4952+++ src/photo/photo-caches.cpp 1970-01-01 00:00:00 +0000
4953@@ -1,183 +0,0 @@
4954-/*
4955- * Copyright (C) 2012 Canonical Ltd
4956- *
4957- * This program is free software: you can redistribute it and/or modify
4958- * it under the terms of the GNU General Public License version 3 as
4959- * published by the Free Software Foundation.
4960- *
4961- * This program is distributed in the hope that it will be useful,
4962- * but WITHOUT ANY WARRANTY; without even the implied warranty of
4963- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4964- * GNU General Public License for more details.
4965- *
4966- * You should have received a copy of the GNU General Public License
4967- * along with this program. If not, see <http://www.gnu.org/licenses/>.
4968- *
4969- * Authors:
4970- * Charles Lindsay <chaz@yorba.org>
4971- */
4972-
4973-#include "photo-caches.h"
4974-
4975-#include <QDir>
4976-#include <utime.h>
4977-
4978-const QString PhotoCaches::ORIGINAL_DIR = ".original";
4979-const QString PhotoCaches::ENHANCED_DIR = ".enhanced";
4980-
4981-/*!
4982- * \brief PhotoCaches::PhotoCaches
4983- * \param file
4984- */
4985-PhotoCaches::PhotoCaches(const QFileInfo& file) : m_file(file),
4986- m_originalFile(file.dir(),
4987- QString("%1/%2").arg(ORIGINAL_DIR).arg(file.fileName())),
4988- m_enhancedFile(file.dir(),
4989- QString("%1/%2").arg(ENHANCED_DIR).arg(file.fileName()))
4990-{
4991- // We always want our file checks to hit the disk.
4992- m_file.setCaching(false);
4993- m_originalFile.setCaching(false);
4994- m_enhancedFile.setCaching(false);
4995-}
4996-
4997-/*!
4998- * \brief PhotoCaches::hasCachedOriginal
4999- * \return
5000- */
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: