diff --git a/CMakeLists.txt b/CMakeLists.txt index 734df683e..4e991d554 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -339,6 +339,8 @@ set(${PROJECT_NAME}_SOURCES src/model/contact.h src/model/chatlogitem.cpp src/model/chatlogitem.h + src/model/exiftransform.h + src/model/exiftransform.cpp src/model/friend.cpp src/model/friend.h src/model/message.h diff --git a/cmake/Testing.cmake b/cmake/Testing.cmake index ff248070b..d92674e39 100644 --- a/cmake/Testing.cmake +++ b/cmake/Testing.cmake @@ -47,6 +47,7 @@ auto_test(model friendmessagedispatcher) auto_test(model groupmessagedispatcher) auto_test(model messageprocessor) auto_test(model sessionchatlog) +auto_test(model exiftransform) if (UNIX) auto_test(platform posixsignalnotifier) diff --git a/src/chatlog/content/filetransferwidget.cpp b/src/chatlog/content/filetransferwidget.cpp index 6f611986a..f99ecbffc 100644 --- a/src/chatlog/content/filetransferwidget.cpp +++ b/src/chatlog/content/filetransferwidget.cpp @@ -26,8 +26,7 @@ #include "src/widget/gui.h" #include "src/widget/style.h" #include "src/widget/widget.h" - -#include +#include "src/model/exiftransform.h" #include #include @@ -523,13 +522,11 @@ void FileTransferWidget::showPreview(const QString& filename) if (!imageFile.open(QIODevice::ReadOnly)) { return; } + const QByteArray imageFileData = imageFile.readAll(); QImage image = QImage::fromData(imageFileData); - const int exifOrientation = - getExifOrientation(imageFileData.constData(), imageFileData.size()); - if (exifOrientation) { - applyTransformation(exifOrientation, image); - } + auto orientation = ExifTransform::getOrientation(imageFileData); + image = ExifTransform::applyTransformation(image, orientation); const QPixmap iconPixmap = scaleCropIntoSquare(QPixmap::fromImage(image), size); @@ -599,59 +596,6 @@ QPixmap FileTransferWidget::scaleCropIntoSquare(const QPixmap& source, const int return result; } -int FileTransferWidget::getExifOrientation(const char* data, const int size) -{ - ExifData* exifData = exif_data_new_from_data(reinterpret_cast(data), size); - - if (!exifData) { - return 0; - } - - int orientation = 0; - const ExifByteOrder byteOrder = exif_data_get_byte_order(exifData); - const ExifEntry* const exifEntry = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION); - if (exifEntry) { - orientation = exif_get_short(exifEntry->data, byteOrder); - } - exif_data_free(exifData); - return orientation; -} - -void FileTransferWidget::applyTransformation(const int orientation, QImage& image) -{ - QTransform exifTransform; - switch (static_cast(orientation)) { - case ExifOrientation::TopLeft: - break; - case ExifOrientation::TopRight: - image = image.mirrored(1, 0); - break; - case ExifOrientation::BottomRight: - exifTransform.rotate(180); - break; - case ExifOrientation::BottomLeft: - image = image.mirrored(0, 1); - break; - case ExifOrientation::LeftTop: - exifTransform.rotate(90); - image = image.mirrored(0, 1); - break; - case ExifOrientation::RightTop: - exifTransform.rotate(-90); - break; - case ExifOrientation::RightBottom: - exifTransform.rotate(-90); - image = image.mirrored(0, 1); - break; - case ExifOrientation::LeftBottom: - exifTransform.rotate(90); - break; - default: - qWarning() << "Invalid exif orientation passed to applyTransformation!"; - } - image = image.transformed(exifTransform); -} - void FileTransferWidget::updateWidget(ToxFile const& file) { assert(file == fileInfo); diff --git a/src/chatlog/content/filetransferwidget.h b/src/chatlog/content/filetransferwidget.h index d3929bb9b..46ac03bdb 100644 --- a/src/chatlog/content/filetransferwidget.h +++ b/src/chatlog/content/filetransferwidget.h @@ -89,22 +89,4 @@ private: bool active; ToxFile::FileStatus lastStatus = ToxFile::INITIALIZING; - enum class ExifOrientation - { - /* do not change values, this is exif spec - * - * name corresponds to where the 0 row and 0 column is in form row-column - * i.e. entry 5 here means that the 0'th row corresponds to the left side of the scene and - * the 0'th column corresponds to the top of the captured scene. This means that the image - * needs to be mirrored and rotated to be displayed. - */ - TopLeft = 1, - TopRight = 2, - BottomRight = 3, - BottomLeft = 4, - LeftTop = 5, - RightTop = 6, - RightBottom = 7, - LeftBottom = 8 - }; }; diff --git a/src/model/exiftransform.cpp b/src/model/exiftransform.cpp new file mode 100644 index 000000000..8ea3acf60 --- /dev/null +++ b/src/model/exiftransform.cpp @@ -0,0 +1,103 @@ +/* + Copyright © 2020 by The qTox Project Contributors + + This file is part of qTox, a Qt-based graphical interface for Tox. + + qTox is libre 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 3 of the License, or + (at your option) any later version. + + qTox 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with qTox. If not, see . +*/ + +#include "exiftransform.h" + +#include + +#include + +namespace ExifTransform +{ + Orientation getOrientation(QByteArray imageData) + { + auto data = imageData.constData(); + auto size = imageData.size(); + + ExifData* exifData = exif_data_new_from_data(reinterpret_cast(data), size); + + if (!exifData) { + return Orientation::TopLeft; + } + + int orientation = 0; + const ExifByteOrder byteOrder = exif_data_get_byte_order(exifData); + const ExifEntry* const exifEntry = exif_data_get_entry(exifData, EXIF_TAG_ORIENTATION); + if (exifEntry) { + orientation = exif_get_short(exifEntry->data, byteOrder); + } + exif_data_free(exifData); + + switch (orientation){ + case 1: + return Orientation::TopLeft; + case 2: + return Orientation::TopRight; + case 3: + return Orientation::BottomRight; + case 4: + return Orientation::BottomLeft; + case 5: + return Orientation::LeftTop; + case 6: + return Orientation::RightTop; + case 7: + return Orientation::RightBottom; + case 8: + return Orientation::LeftBottom; + default: + qWarning() << "Invalid exif orientation"; + return Orientation::TopLeft; + } + } + + QImage applyTransformation(QImage image, Orientation orientation) + { + QTransform exifTransform; + switch (orientation) { + case Orientation::TopLeft: + break; + case Orientation::TopRight: + image = image.mirrored(1, 0); + break; + case Orientation::BottomRight: + exifTransform.rotate(180); + break; + case Orientation::BottomLeft: + image = image.mirrored(0, 1); + break; + case Orientation::LeftTop: + exifTransform.rotate(-90); + image = image.mirrored(1, 0); + break; + case Orientation::RightTop: + exifTransform.rotate(90); + break; + case Orientation::RightBottom: + exifTransform.rotate(90); + image = image.mirrored(1, 0); + break; + case Orientation::LeftBottom: + exifTransform.rotate(-90); + break; + } + image = image.transformed(exifTransform); + return image; + } +}; diff --git a/src/model/exiftransform.h b/src/model/exiftransform.h new file mode 100644 index 000000000..8fd0bacf0 --- /dev/null +++ b/src/model/exiftransform.h @@ -0,0 +1,45 @@ +/* + Copyright © 2020 by The qTox Project Contributors + + This file is part of qTox, a Qt-based graphical interface for Tox. + + qTox is libre 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 3 of the License, or + (at your option) any later version. + + qTox 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with qTox. If not, see . +*/ + +#pragma once + +#include + +namespace ExifTransform +{ + enum class Orientation + { + /* name corresponds to where the 0 row and 0 column is in form row-column + * i.e. entry 5 here means that the 0'th row corresponds to the left side of the scene and + * the 0'th column corresponds to the top of the captured scene. This means that the image + * needs to be mirrored and rotated to be displayed. + */ + TopLeft, + TopRight, + BottomRight, + BottomLeft, + LeftTop, + RightTop, + RightBottom, + LeftBottom + }; + + Orientation getOrientation(QByteArray imageData); + QImage applyTransformation(QImage image, Orientation orientation); +}; diff --git a/test/model/exiftransform_test.cpp b/test/model/exiftransform_test.cpp new file mode 100644 index 000000000..76310dcef --- /dev/null +++ b/test/model/exiftransform_test.cpp @@ -0,0 +1,165 @@ +/* + Copyright © 2020 by The qTox Project Contributors + + This file is part of qTox, a Qt-based graphical interface for Tox. + + qTox is libre 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 3 of the License, or + (at your option) any later version. + + qTox 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with qTox. If not, see . +*/ + +#include "src/model/exiftransform.h" + +#include +#include + +static const auto rowColor = QColor(Qt::green).rgb(); +static const auto colColor = QColor(Qt::blue).rgb(); + +enum class Side +{ + top, + bottom, + left, + right +}; + +static QPoint getPosition(Side side) +{ + int x, y; + switch (side) + { + case Side::top: + { + x = 1; + y = 0; + break; + } + case Side::bottom: + { + x = 1; + y = 2; + break; + } + case Side::left: + { + x = 0; + y = 1; + break; + } + case Side::right: + { + x = 2; + y = 1; + break; + } + } + + return {x, y}; +} + +static QRgb getColor(const QImage& image, Side side) +{ + return image.pixel(getPosition(side)); +} + + +class TestExifTransform : public QObject +{ + Q_OBJECT + +private slots: + void init(); + void testTopLeft(); + void testTopRight(); + void testBottomRight(); + void testBottomLeft(); + void testLeftTop(); + void testRightTop(); + void testRightBottom(); + void testLeftBottom(); + +private: + QImage inputImage; +}; + +void TestExifTransform::init() +{ + inputImage = QImage(QSize(3, 3), QImage::Format_RGB32); + QPainter painter(&inputImage); + painter.fillRect(QRect(0, 0, 3, 3), Qt::white); + // First row has a green dot in the middle + painter.setPen(rowColor); + painter.drawPoint(getPosition(Side::top)); + // First column has a blue dot in the middle + painter.setPen(colColor); + painter.drawPoint(getPosition(Side::left)); +} + +void TestExifTransform::testTopLeft() +{ + auto image = ExifTransform::applyTransformation(inputImage, ExifTransform::Orientation::TopLeft); + QVERIFY(getColor(image, Side::top) == rowColor); + QVERIFY(getColor(image, Side::left) == colColor); +} + +void TestExifTransform::testTopRight() +{ + auto image = ExifTransform::applyTransformation(inputImage, ExifTransform::Orientation::TopRight); + QVERIFY(getColor(image, Side::top) == rowColor); + QVERIFY(getColor(image, Side::right) == colColor); +} + +void TestExifTransform::testBottomRight() +{ + auto image = ExifTransform::applyTransformation(inputImage, ExifTransform::Orientation::BottomRight); + QVERIFY(getColor(image, Side::bottom) == rowColor); + QVERIFY(getColor(image, Side::right) == colColor); +} + +void TestExifTransform::testBottomLeft() +{ + auto image = ExifTransform::applyTransformation(inputImage, ExifTransform::Orientation::BottomLeft); + QVERIFY(getColor(image, Side::bottom) == rowColor); + QVERIFY(getColor(image, Side::left) == colColor); +} + +void TestExifTransform::testLeftTop() +{ + auto image = ExifTransform::applyTransformation(inputImage, ExifTransform::Orientation::LeftTop); + QVERIFY(getColor(image, Side::left) == rowColor); + QVERIFY(getColor(image, Side::top) == colColor); +} + +void TestExifTransform::testRightTop() +{ + auto image = ExifTransform::applyTransformation(inputImage, ExifTransform::Orientation::RightTop); + QVERIFY(getColor(image, Side::right) == rowColor); + QVERIFY(getColor(image, Side::top) == colColor); +} + +void TestExifTransform::testRightBottom() +{ + auto image = ExifTransform::applyTransformation(inputImage, ExifTransform::Orientation::RightBottom); + QVERIFY(getColor(image, Side::right) == rowColor); + QVERIFY(getColor(image, Side::bottom) == colColor); +} + +void TestExifTransform::testLeftBottom() +{ + auto image = ExifTransform::applyTransformation(inputImage, ExifTransform::Orientation::LeftBottom); + QVERIFY(getColor(image, Side::left) == rowColor); + QVERIFY(getColor(image, Side::bottom) == colColor); +} + +QTEST_APPLESS_MAIN(TestExifTransform) +#include "exiftransform_test.moc"