From 76a4202f7e41611cc1cf695712f998a00c4aa39c Mon Sep 17 00:00:00 2001 From: Julien Cabieces Date: Tue, 16 Jul 2024 14:03:13 +0200 Subject: [PATCH] feat(ICCProfile): Add an action to save an ICC profile file --- .../core/auto_generated/qgscolorutils.sip.in | 10 +++ .../core/auto_generated/qgscolorutils.sip.in | 10 +++ src/app/qgsprojectproperties.cpp | 19 +++++ src/app/qgsprojectproperties.h | 5 ++ src/core/qgscolorutils.cpp | 16 +++++ src/core/qgscolorutils.h | 9 +++ src/ui/qgsprojectpropertiesbase.ui | 72 +++++++++++-------- tests/src/app/testqgsprojectproperties.cpp | 1 + tests/src/python/test_qgscolorutils.py | 22 ++++++ 9 files changed, 135 insertions(+), 29 deletions(-) diff --git a/python/PyQt6/core/auto_generated/qgscolorutils.sip.in b/python/PyQt6/core/auto_generated/qgscolorutils.sip.in index 81063d529b11..728550ba916e 100644 --- a/python/PyQt6/core/auto_generated/qgscolorutils.sip.in +++ b/python/PyQt6/core/auto_generated/qgscolorutils.sip.in @@ -95,6 +95,16 @@ message .. versionadded:: 3.40 %End + static QString saveIccProfile( const QColorSpace &colorSpace, const QString &iccProfileFilePath ); +%Docstring +Save color space ``colorSpace`` to an ICC profile file ``iccProfileFilePath``. + +:return: error message if an error occurred else empty string. + +.. versionadded:: 3.40 +%End + + diff --git a/python/core/auto_generated/qgscolorutils.sip.in b/python/core/auto_generated/qgscolorutils.sip.in index 81063d529b11..728550ba916e 100644 --- a/python/core/auto_generated/qgscolorutils.sip.in +++ b/python/core/auto_generated/qgscolorutils.sip.in @@ -95,6 +95,16 @@ message .. versionadded:: 3.40 %End + static QString saveIccProfile( const QColorSpace &colorSpace, const QString &iccProfileFilePath ); +%Docstring +Save color space ``colorSpace`` to an ICC profile file ``iccProfileFilePath``. + +:return: error message if an error occurred else empty string. + +.. versionadded:: 3.40 +%End + + diff --git a/src/app/qgsprojectproperties.cpp b/src/app/qgsprojectproperties.cpp index dca7c6656595..fbab96ec6504 100644 --- a/src/app/qgsprojectproperties.cpp +++ b/src/app/qgsprojectproperties.cpp @@ -138,6 +138,7 @@ QgsProjectProperties::QgsProjectProperties( QgsMapCanvas *mapCanvas, QWidget *pa #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) connect( mAddIccProfile, &QToolButton::clicked, this, static_cast( &QgsProjectProperties::addIccProfile ) ); connect( mRemoveIccProfile, &QToolButton::clicked, this, &QgsProjectProperties::removeIccProfile ); + connect( mSaveIccProfile, &QToolButton::clicked, this, &QgsProjectProperties::saveIccProfile ); #endif // QgsOptionsDialogBase handles saving/restoring of geometry, splitter and current tab states, @@ -1042,6 +1043,7 @@ QgsProjectProperties::QgsProjectProperties( QgsMapCanvas *mapCanvas, QWidget *pa mColorSpaceName->setVisible( false ); mAddIccProfile->setVisible( false ); mRemoveIccProfile->setVisible( false ); + mSaveIccProfile->setVisible( false ); #endif // Default alpha transparency mDefaultOpacityWidget->setOpacity( QgsProject::instance()->styleSettings()->defaultSymbolOpacity() ); @@ -2736,10 +2738,27 @@ void QgsProjectProperties::removeIccProfile() updateColorSpaceWidgets(); } +void QgsProjectProperties::saveIccProfile() +{ + QString fileName = QFileDialog::getSaveFileName( this, tr( "Save ICC profile" ), QDir::homePath(), + tr( "ICC profile files (*.icc *.ICC)" ) ); + + if ( fileName.isEmpty() ) + return; + + const QString error = QgsColorUtils::saveIccProfile( mColorSpace, fileName ); + if ( !error.isEmpty() ) + { + QMessageBox::warning( this, tr( "Save ICC profile" ), error ); + } +} + + void QgsProjectProperties::updateColorSpaceWidgets() { mColorSpaceName->setText( mColorSpace.isValid() ? mColorSpace.description() : tr( "None" ) ); mRemoveIccProfile->setEnabled( mColorSpace.isValid() ); + mSaveIccProfile->setEnabled( mColorSpace.isValid() ); // force color model index according to color space one if ( mColorSpace.isValid() ) diff --git a/src/app/qgsprojectproperties.h b/src/app/qgsprojectproperties.h index 026a6f385214..d0ba5799a4f7 100644 --- a/src/app/qgsprojectproperties.h +++ b/src/app/qgsprojectproperties.h @@ -222,6 +222,11 @@ class APP_EXPORT QgsProjectProperties : public QgsOptionsDialogBase, private Ui: */ void removeIccProfile(); + /** + * Called whenever user select the save ICC profile button + */ + void saveIccProfile(); + /** * Update color space widget according to current project color space */ diff --git a/src/core/qgscolorutils.cpp b/src/core/qgscolorutils.cpp index 514d6173b4e9..4c37a41d2412 100644 --- a/src/core/qgscolorutils.cpp +++ b/src/core/qgscolorutils.cpp @@ -377,6 +377,22 @@ QColorSpace QgsColorUtils::iccProfile( const QString &iccProfileFilePath, QStrin return colorSpace; } + +QString QgsColorUtils::saveIccProfile( const QColorSpace &colorSpace, const QString &iccProfileFilePath ) +{ + if ( !colorSpace.isValid() ) + return QObject::tr( "Invalid ICC profile" ); + + QFile iccProfile( iccProfileFilePath ); + if ( !iccProfile.open( QIODevice::WriteOnly ) ) + return QObject::tr( "File access error '%1'" ).arg( iccProfileFilePath ); + + if ( iccProfile.write( colorSpace.iccProfile() ) < 0 ) + return QObject::tr( "Error while writing to file '%1'" ).arg( iccProfileFilePath ); + + return QString(); +} + #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) Qgis::ColorModel QgsColorUtils::toColorModel( QColorSpace::ColorModel colorModel, bool *ok ) diff --git a/src/core/qgscolorutils.h b/src/core/qgscolorutils.h index 593fc3133243..a31b8081eac8 100644 --- a/src/core/qgscolorutils.h +++ b/src/core/qgscolorutils.h @@ -108,6 +108,15 @@ class CORE_EXPORT QgsColorUtils */ static QColorSpace iccProfile( const QString &iccProfileFilePath, QString &errorMsg SIP_OUT ); + /** + * Save color space \a colorSpace to an ICC profile file \a iccProfileFilePath. + * \returns error message if an error occurred else empty string. + * + * \since QGIS 3.40 + */ + static QString saveIccProfile( const QColorSpace &colorSpace, const QString &iccProfileFilePath ); + + #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) /** diff --git a/src/ui/qgsprojectpropertiesbase.ui b/src/ui/qgsprojectpropertiesbase.ui index 41cf296f2277..153253a3dbec 100644 --- a/src/ui/qgsprojectpropertiesbase.ui +++ b/src/ui/qgsprojectpropertiesbase.ui @@ -1500,17 +1500,45 @@ projstyles - - + + - Assign random colors to symbols + ICC Profile - - + + - Color model + Opacity + + + + + + + Qt::StrongFocus + + + + + + + <html><head/><body><p>Load an ICC profile file and attach it to the project.</p><p>Color model will be updated accordingly.</p></body></html> + + + + + + + :/images/themes/default/mActionFileOpen.svg:/images/themes/default/mActionFileOpen.svg + + + + + + + <html><head/><body><p>Color model used as default when selecting a color in the whole application.</p><p>Any color defined in a different color model than the one specified here will be converted to this color model when exporting a layout.</p></body></html> @@ -1535,45 +1563,31 @@ - - + + - Opacity + Color model - - + + - ICC Profile + Assign random colors to symbols - + - <html><head/><body><p>Load an ICC profile file and attach it to the project.</p><p>Color model will be updated accordingly.</p></body></html> + <html><head/><body><p>Save ICC profile</p></body></html> - :/images/themes/default/mActionFileOpen.svg:/images/themes/default/mActionFileOpen.svg - - - - - - - <html><head/><body><p>Color model used as default when selecting a color in the whole application.</p><p>Any color defined in a different color model than the one specified here will be converted to this color model when exporting a layout.</p></body></html> - - - - - - - Qt::StrongFocus + :/images/themes/default/mActionFileSave.svg:/images/themes/default/mActionFileSave.svg diff --git a/tests/src/app/testqgsprojectproperties.cpp b/tests/src/app/testqgsprojectproperties.cpp index 4141fab6529e..624ba5cda1b5 100644 --- a/tests/src/app/testqgsprojectproperties.cpp +++ b/tests/src/app/testqgsprojectproperties.cpp @@ -272,6 +272,7 @@ void TestQgsProjectProperties::testColorSettings() #else QVERIFY( !pp->mRemoveIccProfile->isVisible() ); QVERIFY( !pp->mAddIccProfile->isVisible() ); + QVERIFY( !pp->mSaveIccProfile->isVisible() ); QVERIFY( !pp->mColorSpaceName->isVisible() ); QVERIFY( !pp->mIccProfileLabel->isVisible() ); #endif diff --git a/tests/src/python/test_qgscolorutils.py b/tests/src/python/test_qgscolorutils.py index 59716feeaaf5..ad452132ff24 100644 --- a/tests/src/python/test_qgscolorutils.py +++ b/tests/src/python/test_qgscolorutils.py @@ -9,11 +9,18 @@ __date__ = '06/07/2022' __copyright__ = 'Copyright 2022, The QGIS Project' +import os + +from qgis.PyQt.QtCore import QTemporaryDir from qgis.PyQt.QtGui import QColor from qgis.PyQt.QtXml import QDomDocument from qgis.core import QgsColorUtils, QgsReadWriteContext, QgsSymbolLayerUtils from qgis.testing import unittest +from utilities import unitTestDataPath + +TEST_DATA_DIR = unitTestDataPath() + class TestQgsColorUtils(unittest.TestCase): @@ -288,6 +295,21 @@ def test_color_string_compat(self): self.assertAlmostEqual(res.blue(), 23, delta=1) self.assertEqual(res.alpha(), 220) + def test_icc_profile(self): + """ + Test ICC profile load and save method + """ + + iccProfileFilePath = os.path.join(TEST_DATA_DIR, "sRGB2014.icc") + colorSpace, errorMsg = QgsColorUtils.iccProfile(iccProfileFilePath) + self.assertTrue(colorSpace.isValid()) + + tmpDir = QTemporaryDir() + tmpFile = f"{tmpDir.path()}/test.icc" + + error = QgsColorUtils.saveIccProfile(colorSpace, tmpFile) + self.assertTrue(not error) + if __name__ == '__main__': unittest.main()