Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rescale images used in Input #2054

Merged
merged 11 commits into from
Apr 27, 2022
Merged
10 changes: 9 additions & 1 deletion app/inpututils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
#include "qgsunittypes.h"
#include "qgsfeatureid.h"

#include "imageutils.h"

#include <Qt>
#include <QDir>
#include <QFile>
Expand All @@ -46,7 +48,6 @@
#include <algorithm>
#include <limits>
#include <math.h>

#include <iostream>

static const QString DATE_TIME_FORMAT = QStringLiteral( "yyMMdd-hhmmss" );
Expand Down Expand Up @@ -1625,3 +1626,10 @@ QString InputUtils::iconFromGeometry( const QgsWkbTypes::GeometryType &geometry
default: return QString( "qrc:/mIconTableLayer.svg" );
}
}

bool InputUtils::rescaleImage( const QString &path, QgsProject *activeProject )
{
qDebug() << "rescaleImage called";
alexbruy marked this conversation as resolved.
Show resolved Hide resolved
int quality = activeProject->readNumEntry( QStringLiteral( "Mergin" ), QStringLiteral( "PhotoQuality" ), 0 );
return ImageUtils::rescale( path, quality );
}
5 changes: 5 additions & 0 deletions app/inpututils.h
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,11 @@ class InputUtils: public QObject
*/
Q_INVOKABLE void zoomToProject( QgsProject *qgsProject, QgsQuickMapSettings *mapSettings );

/**
* Rescales image according to the project photo quality setting.
*/
Q_INVOKABLE static bool rescaleImage( const QString &path, QgsProject *activeProject );

signals:
Q_INVOKABLE void showNotificationRequested( const QString &message );

Expand Down
5 changes: 5 additions & 0 deletions app/qml/ExternalResourceBundle.qml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ Item {
*/
property var confirmImage: function confirmImage(itemWidget, prefixToRelativePath, value) {
if (value) {
console.log("TRY TO RESCALE" + value)
alexbruy marked this conversation as resolved.
Show resolved Hide resolved
if (!__inputUtils.rescaleImage(value, __activeProject.qgsProject))
{
console.log("RESCALE FAILED" + value)
}
var newCurrentValue = __inputUtils.getRelativePath(value, prefixToRelativePath)
itemWidget.editorValueChanged(newCurrentValue, newCurrentValue === "" || newCurrentValue === null)
alexbruy marked this conversation as resolved.
Show resolved Hide resolved
}
Expand Down
6 changes: 4 additions & 2 deletions app/sources.pri
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ contains(DEFINES, INPUT_TEST) {
test/testvariablesmanager.cpp \
test/testformeditors.cpp \
test/testmodels.cpp \
test/testcoreutils.cpp
test/testcoreutils.cpp \
test/testimageutils.cpp

HEADERS += \
test/inputtests.h \
Expand All @@ -153,7 +154,8 @@ contains(DEFINES, INPUT_TEST) {
test/testvariablesmanager.h \
test/testformeditors.h \
test/testmodels.h \
test/testcoreutils.h
test/testcoreutils.h \
test/testimageutils.h
}

contains(DEFINES, APPLE_PURCHASING) {
Expand Down
6 changes: 6 additions & 0 deletions app/test/inputtests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "test/testformeditors.h"
#include "test/testmodels.h"
#include "test/testcoreutils.h"
#include "test/testimageutils.h"

#if not defined APPLE_PURCHASING
#include "test/testpurchasing.h"
Expand Down Expand Up @@ -160,6 +161,11 @@ int InputTests::runTest() const
TestCoreUtils coreUtilsTest;
nFailed = QTest::qExec( &coreUtilsTest, mTestArgs );
}
else if ( mTestRequested == "--testImageUtils" )
{
TestImageUtils imageUtilsTest;
nFailed = QTest::qExec( &imageUtilsTest, mTestArgs );
}
#if not defined APPLE_PURCHASING
else if ( mTestRequested == "--testPurchasing" )
{
Expand Down
50 changes: 50 additions & 0 deletions app/test/testimageutils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "testimageutils.h"
#include "testutils.h"
#include "imageutils.h"

#include <QTemporaryDir>
#include <QImage>
#include <exiv2/exiv2.hpp>

#include <QtTest/QtTest>

void TestImageUtils::init()
{
}

void TestImageUtils::cleanup()
{
}

void TestImageUtils::testRescale()
{
QTemporaryDir dir;
QString testPhotoName = QStringLiteral( "photo.jpg" );
QFile::copy( TestUtils::testDataDir() + '/' + testPhotoName, dir.filePath( testPhotoName ) );

QVERIFY( ImageUtils::rescale( dir.filePath( testPhotoName ), 3 ) );

QImage img( dir.filePath( testPhotoName ) );
QCOMPARE( img.height(), 800 );

// check EXIF tags
std::unique_ptr< Exiv2::Image > image( Exiv2::ImageFactory::open( dir.filePath( testPhotoName ).toStdString() ) );

image->readMetadata();
Exiv2::ExifData &exifData = image->exifData();
QVERIFY( !exifData.empty() );

const Exiv2::ExifData::iterator itElevVal = exifData.findKey( Exiv2::ExifKey( "Exif.GPSInfo.GPSAltitude" ) );
const Exiv2::Rational rational = itElevVal->value().toRational( 0 );
double val = static_cast< double >( rational.first ) / rational.second;
QCOMPARE( val, 133 );
}
26 changes: 26 additions & 0 deletions app/test/testimageutils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef TESTIMAGEUTILS_H
#define TESTIMAGEUTILS_H

#include <QObject>

class TestImageUtils : public QObject
{
Q_OBJECT

private slots:
void init(); // will be called before each testfunction is executed.
void cleanup(); // will be called after every testfunction.

void testRescale();
};

#endif // TESTIMAGEUTILS_H
6 changes: 4 additions & 2 deletions core/core.pri
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ SOURCES += \
$$PWD/localprojectsmanager.cpp \
$$PWD/merginprojectmetadata.cpp \
$$PWD/project.cpp \
$$PWD/geodiffutils.cpp
$$PWD/geodiffutils.cpp \
$$PWD/imageutils.cpp

HEADERS += \
$$PWD/coreutils.h \
Expand All @@ -27,7 +28,8 @@ HEADERS += \
$$PWD/localprojectsmanager.h \
$$PWD/merginprojectmetadata.h \
$$PWD/project.h \
$$PWD/geodiffutils.h
$$PWD/geodiffutils.h \
$$PWD/imageutils.h

exists($$PWD/merginsecrets.cpp) {
message("Using production Mergin API_KEYS")
Expand Down
131 changes: 131 additions & 0 deletions core/imageutils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "imageutils.h"

#include "coreutils.h"

#include <QFile>
#include <QFileInfo>
#include <QImage>

#include <exiv2/exiv2.hpp>

bool ImageUtils::copyExifMetadata( const QString &sourceImage, const QString &targetImage )
{
if ( !QFileInfo::exists( sourceImage ) || !QFileInfo::exists( targetImage ) )
return false;

try
{
std::unique_ptr< Exiv2::Image > srcImage( Exiv2::ImageFactory::open( sourceImage.toStdString() ) );
if ( !srcImage )
return false;

std::unique_ptr< Exiv2::Image > dstImage( Exiv2::ImageFactory::open( targetImage.toStdString() ) );
if ( !dstImage )
return false;

srcImage->readMetadata();
Exiv2::ExifData &exifData = srcImage->exifData();
if ( exifData.empty() )
{
return true;
}

dstImage->setExifData( exifData );
dstImage->writeMetadata();
return true;
}
catch ( ... )
{
CoreUtils::log( "copying EXIF", QStringLiteral( "Failed to copy EXIF metadata" ) );
return false;
}
}

bool ImageUtils::rescale( const QString &path, int quality )
{

QImage sourceImage( path );
bool isPortrait = sourceImage.height() > sourceImage.width();
int size = isPortrait ? sourceImage.width() : sourceImage.height();

int newSize = size;
switch ( quality )
{
case 0: // original quality, no rescaling needed
{
break;
}
case 1: // high quality, output image size ~5Mb
{
newSize = 3000;
break;
}
case 2: // medium quality, output image size ~3Mb
{
newSize = 1500;
break;
}
case 3: // low quality, output image size ~1Mb
{
newSize = 800;
break;
}
}

// if image width or height (depending on the orientation) is smaller
// than new size we keep original image
if ( size <= newSize )
{
return true;
}

// rescale
QImage rescaledImage;
if ( isPortrait )
{
rescaledImage = sourceImage.scaledToWidth( newSize, Qt::SmoothTransformation );
}
else
{
rescaledImage = sourceImage.scaledToHeight( newSize, Qt::SmoothTransformation );
}

if ( rescaledImage.isNull() )
{
CoreUtils::log( "rescaling image", QStringLiteral( "Failed to rescale %1" ).arg( path ) );
return false;
}

QFileInfo fi( path );
QString newPath = QStringLiteral( "%1/%2_rescaled.%3" ).arg( fi.path(), fi.baseName(), fi.completeSuffix() );

if ( !rescaledImage.save( newPath ) )
{
CoreUtils::log( "rescaling image", QStringLiteral( "Failed to save rescaled image" ) );
return false;
}

// copy EXIF from source image to rescaled image
copyExifMetadata( path, newPath );
alexbruy marked this conversation as resolved.
Show resolved Hide resolved

// remove original file and rename rescaled version
if ( QFile::remove( path ) )
{
if ( QFile::rename( newPath, path ) )
{
return true;
}
}

CoreUtils::log( "rescaling image", QStringLiteral( "Can not replace original file with rescaled version" ) );
return false;
}
33 changes: 33 additions & 0 deletions core/imageutils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef IMAGEUTILS_H
#define IMAGEUTILS_H

#include <QString>

class ImageUtils
{
public:
explicit ImageUtils( ) = default;
~ImageUtils() = default;

/**
* Copies EXIF metadata from sourceImage to targetImage.
*/
static bool copyExifMetadata( const QString &sourceImage, const QString &targetImage );

/**
* Rescales image to the given quality taking into account its orientation
* and preserving EXIF metadata.
*/
static bool rescale( const QString &path, int quality );
};

#endif // IMAGEUTILS_H
3 changes: 3 additions & 0 deletions scripts/run_all_tests.bash
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ NFAILURES=$(($NFAILURES+$?))
$INPUT_EXECUTABLE --testCoreUtils
NFAILURES=$(($NFAILURES+$?))

$INPUT_EXECUTABLE --testImageUtils
NFAILURES=$(($NFAILURES+$?))

echo "Total $NFAILURES failures found in testing"

exit $NFAILURES
Binary file added test/test_data/photo.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.