diff --git a/qmlUiKirigami/ImageViewer.qml b/qmlUiKirigami/ImageViewer.qml index 2fd90d0..d53ba09 100644 --- a/qmlUiKirigami/ImageViewer.qml +++ b/qmlUiKirigami/ImageViewer.qml @@ -1,381 +1,387 @@ /* * Copyright (C) 2017 Marco Martin * Copyright (C) 2017 Atul Sharma * Copyright (C) 2015 Vishesh Handa * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ import QtQuick 2.7 import QtQuick.Window 2.2 import QtQuick.Controls 2.0 as Controls +import QtGraphicalEffects 1.0 as Effects import QtQuick.Layouts 1.3 import org.kde.kirigami 2.0 as Kirigami import org.kde.koko 0.1 as Koko import org.kde.kquickcontrolsaddons 2.0 as KQA Kirigami.Page { id: root title: i18n("Details") property alias sourceModel: imagesListModel.sourceModel property int indexValue property int imageWidth property int imageHeight leftPadding: 0 rightPadding: 0 KQA.MimeDatabase { id: mimeDB } Koko.ImageDocument { id: imageDoc path: listView.currentItem.currentImageSource onResetHandle: brightnessSlider.value = 0.5 } Kirigami.ContextDrawer { id: contextDrawer title: i18n("Edit image") handleVisible: true } actions { left: Kirigami.Action { id: backAction iconName: "view-close" tooltip: i18n("Close Image") onTriggered: root.close(); } main: Kirigami.Action { id: shareAction iconName: "document-share" tooltip: i18n("Share Image") onTriggered: { shareDialog.open(); shareDialog.inputData = { "urls": [ listView.currentItem.currentImageSource.toString() ], "mimeType": mimeDB.mimeTypeForUrl( listView.currentItem.currentImageSource).name } } } contextualActions: [ Kirigami.Action { iconName: "image-rotate-left-symbolic" text: i18n("Rotate left") tooltip: i18n("Rotate the image to the left") onTriggered: { imageDoc.rotate(270) } }, Kirigami.Action { iconName: "image-rotate-right-symbolic" text: i18n("Rotate right") tooltip: i18n("Rotate the image to the right") onTriggered: { imageDoc.rotate(90) } } ] } ColumnLayout { anchors.top: root.top width: root.width z: listView.z + 1 Controls.Slider { id: brightnessSlider property real previousValue: 0.5 Layout.fillWidth: true visible: applicationWindow().controlsVisible from: 0 to: 1.0 value: 0.5 stepSize: 0.1 z: listView.z + 1 snapMode: Controls.Slider.SnapAlways hoverEnabled: true Controls.ToolTip { parent: brightnessSlider visible: brightnessSlider.hovered text: i18n("Brightness Controller") } onValueChanged: { - if( value > previousValue) { - imageDoc.changeBrightness( true) - } else if( value < previousValue) { - imageDoc.changeBrightness( false) - } - previousValue = value - listView.forceActiveFocus() + imageDoc.edited = true } } RowLayout { Layout.fillWidth: true visible: imageDoc.edited Controls.Button { Layout.fillWidth: true text: i18n("Save") - onClicked: imageDoc.save() + onClicked: { + listView.currentItem.image.modifiedImage.grabToImage( function( result) { imageDoc.save( result.image)}) + } } Controls.Button { Layout.fillWidth: true text: i18n("Cancel") onClicked: imageDoc.cancel() } } } //FIXME: HACK property bool wasDrawerOpen Component.onCompleted: { applicationWindow().controlsVisible = false; listView.forceActiveFocus(); applicationWindow().header.visible = false; applicationWindow().footer.visible = false; wasDrawerOpen = applicationWindow().globalDrawer.visible; applicationWindow().globalDrawer.visible = false; applicationWindow().globalDrawer.enabled = false; } function close() { applicationWindow().controlsVisible = true; applicationWindow().header.visible = true; applicationWindow().footer.visible = true; applicationWindow().globalDrawer.visible = wasDrawerOpen; applicationWindow().globalDrawer.enabled = true; applicationWindow().visibility = Window.Windowed; applicationWindow().pageStack.layers.pop(); } background: Rectangle { color: "black" } Keys.onPressed: { switch(event.key) { case Qt.Key_Escape: root.close(); break; case Qt.Key_F: applicationWindow().visibility = applicationWindow().visibility == Window.FullScreen ? Window.Windowed : Window.FullScreen break; default: break; } } ListView { id: listView anchors.fill: parent orientation: Qt.Horizontal snapMode: ListView.SnapOneItem onMovementEnded: currentImage.index = model.sourceIndex(indexAt(contentX+1, 1)) model: Koko.SortModel { id: imagesListModel filterRole: Koko.Roles.MimeTypeRole filterRegExp: /image\// } currentIndex: model.proxyIndex( indexValue) onCurrentIndexChanged: { currentImage.index = model.sourceIndex( currentIndex) listView.positionViewAtIndex(currentIndex, ListView.Beginning) shareDialog.close(); } delegate: Flickable { id: flick property string currentImageSource: model.imageurl property alias image: image width: imageWidth height: imageHeight contentWidth: imageWidth contentHeight: imageHeight interactive: contentWidth > width || contentHeight > height onInteractiveChanged: listView.interactive = !interactive; clip: true z: index == listView.currentIndex ? 1000 : 0 Controls.ScrollBar.vertical: Controls.ScrollBar {} Controls.ScrollBar.horizontal: Controls.ScrollBar {} PinchArea { width: Math.max(flick.contentWidth, flick.width) height: Math.max(flick.contentHeight, flick.height) property real initialWidth property real initialHeight onPinchStarted: { initialWidth = flick.contentWidth initialHeight = flick.contentHeight } onPinchUpdated: { // adjust content pos due to drag flick.contentX += pinch.previousCenter.x - pinch.center.x flick.contentY += pinch.previousCenter.y - pinch.center.y // resize content flick.resizeContent(Math.max(imageWidth*0.7, initialWidth * pinch.scale), Math.max(imageHeight*0.7, initialHeight * pinch.scale), pinch.center) } onPinchFinished: { // Move its content within bounds. if (flick.contentWidth < root.imageWidth || flick.contentHeight < root.imageHeight) { zoomAnim.x = 0; zoomAnim.y = 0; zoomAnim.width = root.imageWidth; zoomAnim.height = root.imageHeight; zoomAnim.running = true; } else { flick.returnToBounds(); } } ParallelAnimation { id: zoomAnim property real x: 0 property real y: 0 property real width: root.imageWidth property real height: root.imageHeight NumberAnimation { target: flick property: "contentWidth" from: flick.contentWidth to: zoomAnim.width duration: Kirigami.Units.longDuration easing.type: Easing.InOutQuad } NumberAnimation { target: flick property: "contentHeight" from: flick.contentHeight to: zoomAnim.height duration: Kirigami.Units.longDuration easing.type: Easing.InOutQuad } NumberAnimation { target: flick property: "contentY" from: flick.contentY to: zoomAnim.y duration: Kirigami.Units.longDuration easing.type: Easing.InOutQuad } NumberAnimation { target: flick property: "contentX" from: flick.contentX to: zoomAnim.x duration: Kirigami.Units.longDuration easing.type: Easing.InOutQuad } } KQA.QImageItem { id: image + property alias modifiedImage: brightnessContrast width: flick.contentWidth height: flick.contentHeight fillMode: Image.PreserveAspectFit image: imageDoc.visualImage Timer { id: doubleClickTimer interval: 150 onTriggered: applicationWindow().controlsVisible = !applicationWindow().controlsVisible } MouseArea { anchors.fill: parent onClicked: { contextDrawer.drawerOpen = false doubleClickTimer.restart(); } onDoubleClicked: { doubleClickTimer.running = false; applicationWindow().controlsVisible = false; if (flick.interactive) { zoomAnim.x = 0; zoomAnim.y = 0; zoomAnim.width = root.imageWidth; zoomAnim.height = root.imageHeight; zoomAnim.running = true; } else { zoomAnim.x = mouse.x * 2; zoomAnim.y = mouse.y *2; zoomAnim.width = root.imageWidth * 3; zoomAnim.height = root.imageHeight * 3; zoomAnim.running = true; } } onWheel: { if (wheel.modifiers & Qt.ControlModifier) { if (wheel.angleDelta.y != 0) { var factor = 1 + wheel.angleDelta.y / 600; zoomAnim.running = false; zoomAnim.width = Math.min(Math.max(root.imageWidth, zoomAnim.width * factor), root.imageWidth * 4); zoomAnim.height = Math.min(Math.max(root.imageHeight, zoomAnim.height * factor), root.imageHeight * 4); //actual factors, may be less than factor var xFactor = zoomAnim.width / flick.contentWidth; var yFactor = zoomAnim.height / flick.contentHeight; zoomAnim.x = flick.contentX * xFactor + (((wheel.x - flick.contentX) * xFactor) - (wheel.x - flick.contentX)) zoomAnim.y = flick.contentY * yFactor + (((wheel.y - flick.contentY) * yFactor) - (wheel.y - flick.contentY)) zoomAnim.running = true; } else if (wheel.pixelDelta.y != 0) { flick.resizeContent(Math.min(Math.max(root.imageWidth, flick.contentWidth + wheel.pixelDelta.y), root.imageWidth * 4), Math.min(Math.max(root.imageHeight, flick.contentHeight + wheel.pixelDelta.y), root.imageHeight * 4), wheel); } } } } + + Effects.GammaAdjust { + id: brightnessContrast + source: image + anchors.fill: image + gamma: brightnessSlider.value + 0.5 + } + } } } } ShareDialog { id: shareDialog x: (root.width - width) / 2 y: root.height - height - Kirigami.Units.gridUnit * 3 inputData: { urls: [] } onFinished: { if (error==0 && output.url !== "") { console.assert(output.url !== undefined); var resultUrl = output.url; console.log("Received", resultUrl) notificationManager.showNotification( true, resultUrl); clipboard.content = resultUrl; } else { notificationManager.showNotification( false); } } } } diff --git a/src/imagedocument.cpp b/src/imagedocument.cpp index 04d076a..3281e6b 100644 --- a/src/imagedocument.cpp +++ b/src/imagedocument.cpp @@ -1,121 +1,103 @@ /* * Copyright 2017 by Atul Sharma * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "imagedocument.h" #include #include #include ImageDocument::ImageDocument() { m_image = new QImage(); connect( this, &ImageDocument::pathChanged, this, [this] (const QString &url) { emit resetHandle(); /** Since the url passed by the model in the ImageViewer.qml contains 'file://' prefix */ QString location = QUrl( url).path(); m_image->load( location); - m_originalImage = *m_image; m_edited = false; emit editedChanged(); emit visualImageChanged(); }); } ImageDocument::~ImageDocument() { } QString ImageDocument::path() { return m_path; } void ImageDocument::setPath(QString& url) { m_path = url; emit pathChanged( url); } QImage ImageDocument::visualImage() { return *m_image; } bool ImageDocument::edited() { return m_edited; } +void ImageDocument::setEdited(bool value) +{ + m_edited = value; + emit editedChanged(); +} + void ImageDocument::rotate(int angle) { QMatrix matrix; matrix.rotate( angle); *m_image = m_image->transformed( matrix); QString location = QUrl( m_path).path(); if (QFileInfo( location).isWritable()) { m_image->save( location); } emit visualImageChanged(); } -void ImageDocument::changeBrightness( bool isIncrease) -{ - if( isIncrease) { - for( int i = 0; i < m_image->rect().width() ; i++ ) - { - for( int j = 0; j < m_image->rect().height(); j++ ) - { - m_image->setPixelColor(i, j , m_image->pixelColor( i , j).lighter( 120)); - } - } - } else { - for( int i = 0; i < m_image->rect().width() ; i++ ) - { - for( int j = 0; j < m_image->rect().height(); j++ ) - { - m_image->setPixelColor(i, j , m_image->pixelColor( i , j).darker( 120)); - } - } - } - m_edited = true; - emit editedChanged(); - emit visualImageChanged(); -} - -void ImageDocument::save() +void ImageDocument::save( QImage image) { QString location = QUrl( m_path).path(); + *m_image = image; if( QFileInfo( location).isWritable()) { m_image->save( location); + emit resetHandle(); m_edited = false; emit editedChanged(); } + m_image->load( location); + emit visualImageChanged(); } void ImageDocument::cancel() { emit resetHandle(); - m_image = &m_originalImage; m_edited = false; - emit editedChanged(); - emit visualImageChanged(); } #include "moc_imagedocument.cpp" diff --git a/src/imagedocument.h b/src/imagedocument.h index c7fd71f..c5a4c44 100644 --- a/src/imagedocument.h +++ b/src/imagedocument.h @@ -1,60 +1,59 @@ /* * Copyright 2017 by Atul Sharma * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef IMAGEDOCUMENT_H #define IMAGEDOCUMENT_H #include class ImageDocument : public QObject { Q_OBJECT Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged) Q_PROPERTY(QImage visualImage READ visualImage NOTIFY visualImageChanged) - Q_PROPERTY(bool edited READ edited NOTIFY editedChanged) + Q_PROPERTY(bool edited READ edited WRITE setEdited NOTIFY editedChanged) public: ImageDocument(); ~ImageDocument(); QString path(); void setPath( QString &url); QImage visualImage(); bool edited(); + void setEdited( bool value); Q_INVOKABLE void rotate( int angle); - Q_INVOKABLE void changeBrightness( bool isIncrease); - Q_INVOKABLE void save(); + Q_INVOKABLE void save( QImage image); Q_INVOKABLE void cancel(); signals: void pathChanged(const QString &url); void visualImageChanged(); void editedChanged(); void resetHandle(); private: QString m_path; QImage *m_image; - QImage m_originalImage; bool m_edited; }; #endif