summaryrefslogtreecommitdiffstats
path: root/xbmc/pictures
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 18:07:22 +0000
commitc04dcc2e7d834218ef2d4194331e383402495ae1 (patch)
tree7333e38d10d75386e60f336b80c2443c1166031d /xbmc/pictures
parentInitial commit. (diff)
downloadkodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz
kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/pictures')
-rw-r--r--xbmc/pictures/CMakeLists.txt27
-rw-r--r--xbmc/pictures/ExifParse.cpp958
-rw-r--r--xbmc/pictures/ExifParse.h37
-rw-r--r--xbmc/pictures/GUIDialogPictureInfo.cpp114
-rw-r--r--xbmc/pictures/GUIDialogPictureInfo.h32
-rw-r--r--xbmc/pictures/GUIViewStatePictures.cpp86
-rw-r--r--xbmc/pictures/GUIViewStatePictures.h25
-rw-r--r--xbmc/pictures/GUIWindowPictures.cpp625
-rw-r--r--xbmc/pictures/GUIWindowPictures.h51
-rw-r--r--xbmc/pictures/GUIWindowSlideShow.cpp1382
-rw-r--r--xbmc/pictures/GUIWindowSlideShow.h155
-rw-r--r--xbmc/pictures/IptcParse.cpp212
-rw-r--r--xbmc/pictures/IptcParse.h10
-rw-r--r--xbmc/pictures/JpegParse.cpp316
-rw-r--r--xbmc/pictures/JpegParse.h63
-rw-r--r--xbmc/pictures/Picture.cpp588
-rw-r--r--xbmc/pictures/Picture.h134
-rw-r--r--xbmc/pictures/PictureInfoLoader.cpp99
-rw-r--r--xbmc/pictures/PictureInfoLoader.h34
-rw-r--r--xbmc/pictures/PictureInfoTag.cpp764
-rw-r--r--xbmc/pictures/PictureInfoTag.h161
-rw-r--r--xbmc/pictures/PictureScalingAlgorithm.cpp65
-rw-r--r--xbmc/pictures/PictureScalingAlgorithm.h51
-rw-r--r--xbmc/pictures/PictureThumbLoader.cpp249
-rw-r--r--xbmc/pictures/PictureThumbLoader.h40
-rw-r--r--xbmc/pictures/SlideShowPicture.cpp1016
-rw-r--r--xbmc/pictures/SlideShowPicture.h139
-rw-r--r--xbmc/pictures/libexif.cpp35
-rw-r--r--xbmc/pictures/libexif.h138
29 files changed, 7606 insertions, 0 deletions
diff --git a/xbmc/pictures/CMakeLists.txt b/xbmc/pictures/CMakeLists.txt
new file mode 100644
index 0000000..9bbe6cd
--- /dev/null
+++ b/xbmc/pictures/CMakeLists.txt
@@ -0,0 +1,27 @@
+set(SOURCES ExifParse.cpp
+ GUIDialogPictureInfo.cpp
+ GUIViewStatePictures.cpp
+ GUIWindowPictures.cpp
+ GUIWindowSlideShow.cpp
+ IptcParse.cpp
+ JpegParse.cpp
+ libexif.cpp
+ Picture.cpp
+ PictureInfoLoader.cpp
+ PictureInfoTag.cpp
+ PictureScalingAlgorithm.cpp
+ PictureThumbLoader.cpp
+ SlideShowPicture.cpp)
+
+set(HEADERS GUIDialogPictureInfo.h
+ GUIViewStatePictures.h
+ GUIWindowPictures.h
+ GUIWindowSlideShow.h
+ Picture.h
+ PictureInfoLoader.h
+ PictureInfoTag.h
+ PictureScalingAlgorithm.h
+ PictureThumbLoader.h
+ SlideShowPicture.h)
+
+core_add_library(pictures)
diff --git a/xbmc/pictures/ExifParse.cpp b/xbmc/pictures/ExifParse.cpp
new file mode 100644
index 0000000..21ebb60
--- /dev/null
+++ b/xbmc/pictures/ExifParse.cpp
@@ -0,0 +1,958 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+//--------------------------------------------------------------------------
+// Program to pull the EXIF information out of various types of digital
+// images and present it in a reasonably consistent way
+//
+// Original code pulled from 'jhead' by Matthias Wandel (http://www.sentex.net/~mwandel/) - jhead
+// Adapted for XBMC by DD.
+//--------------------------------------------------------------------------
+
+// Note: Jhead supports TAG_MAKER_NOTE exif field,
+// but that is omitted for now - to make porting easier and addition smaller
+
+#include "ExifParse.h"
+
+#ifdef TARGET_WINDOWS
+#include <windows.h>
+#else
+#include <memory.h>
+#include <cstring>
+#endif
+
+#include <math.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#ifndef min
+#define min(a,b) (a)>(b)?(b):(a)
+#endif
+#ifndef max
+#define max(a,b) (a)<(b)?(b):(a)
+#endif
+
+
+// Prototypes for exif utility functions.
+static void ErrNonfatal(const char* const msg, int a1, int a2);
+
+#define DIR_ENTRY_ADDR(Start, Entry) ((Start)+2+12*(Entry))
+
+
+//--------------------------------------------------------------------------
+// Describes tag values
+#define TAG_DESCRIPTION 0x010E
+#define TAG_MAKE 0x010F
+#define TAG_MODEL 0x0110
+#define TAG_ORIENTATION 0x0112
+#define TAG_X_RESOLUTION 0x011A // Not processed. Format rational64u (see http://search.cpan.org/src/EXIFTOOL/Image-ExifTool-6.76/html/TagNames/EXIF.html)
+#define TAG_Y_RESOLUTION 0x011B // Not processed. Format rational64u
+#define TAG_RESOLUTION_UNIT 0x0128 // Not processed. Format int16u. Values: 1-none; 2-inches; 3-cm
+#define TAG_SOFTWARE 0x0131
+#define TAG_DATETIME 0x0132
+#define TAG_THUMBNAIL_OFFSET 0x0201
+#define TAG_THUMBNAIL_LENGTH 0x0202
+#define TAG_Y_CB_CR_POS 0x0213 // Not processed. Format int16u. Values: 1-Centered; 2-Co-sited
+#define TAG_EXPOSURETIME 0x829A
+#define TAG_FNUMBER 0x829D
+#define TAG_EXIF_OFFSET 0x8769
+#define TAG_EXPOSURE_PROGRAM 0x8822
+#define TAG_GPSINFO 0x8825
+#define TAG_ISO_EQUIVALENT 0x8827
+#define TAG_EXIF_VERSION 0x9000 // Not processed.
+#define TAG_COMPONENT_CFG 0x9101 // Not processed.
+#define TAG_DATETIME_ORIGINAL 0x9003
+#define TAG_DATETIME_DIGITIZED 0x9004
+#define TAG_SHUTTERSPEED 0x9201
+#define TAG_APERTURE 0x9202
+#define TAG_EXPOSURE_BIAS 0x9204
+#define TAG_MAXAPERTURE 0x9205
+#define TAG_SUBJECT_DISTANCE 0x9206
+#define TAG_METERING_MODE 0x9207
+#define TAG_LIGHT_SOURCE 0x9208
+#define TAG_FLASH 0x9209
+#define TAG_FOCALLENGTH 0x920A
+#define TAG_MAKER_NOTE 0x927C // Not processed yet. Maybe in the future.
+#define TAG_USERCOMMENT 0x9286
+#define TAG_XP_COMMENT 0x9c9c
+#define TAG_FLASHPIX_VERSION 0xA000 // Not processed.
+#define TAG_COLOUR_SPACE 0xA001 // Not processed. Format int16u. Values: 1-RGB; 2-Adobe RGB 65535-Uncalibrated
+#define TAG_EXIF_IMAGEWIDTH 0xa002
+#define TAG_EXIF_IMAGELENGTH 0xa003
+#define TAG_INTEROP_OFFSET 0xa005
+#define TAG_FOCALPLANEXRES 0xa20E
+#define TAG_FOCALPLANEUNITS 0xa210
+#define TAG_EXPOSURE_INDEX 0xa215
+#define TAG_EXPOSURE_MODE 0xa402
+#define TAG_WHITEBALANCE 0xa403
+#define TAG_DIGITALZOOMRATIO 0xA404
+#define TAG_FOCALLENGTH_35MM 0xa405
+
+#define TAG_GPS_LAT_REF 1
+#define TAG_GPS_LAT 2
+#define TAG_GPS_LONG_REF 3
+#define TAG_GPS_LONG 4
+#define TAG_GPS_ALT_REF 5
+#define TAG_GPS_ALT 6
+
+//--------------------------------------------------------------------------
+// Exif format descriptor stuff
+namespace
+{
+constexpr auto FMT_BYTE = 1;
+[[maybe_unused]] constexpr auto FMT_STRING = 2;
+constexpr auto FMT_USHORT = 3;
+constexpr auto FMT_ULONG = 4;
+constexpr auto FMT_URATIONAL = 5;
+constexpr auto FMT_SBYTE = 6;
+[[maybe_unused]] constexpr auto FMT_UNDEFINED = 7;
+constexpr auto FMT_SSHORT = 8;
+constexpr auto FMT_SLONG = 9;
+constexpr auto FMT_SRATIONAL = 10;
+constexpr auto FMT_SINGLE = 11;
+constexpr auto FMT_DOUBLE = 12;
+// NOTE: Remember to change NUM_FORMATS if you define a new format
+constexpr auto NUM_FORMATS = 12;
+
+const unsigned int BytesPerFormat[NUM_FORMATS] = {1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8};
+} // namespace
+
+//--------------------------------------------------------------------------
+// Internationalisation string IDs. The enum order must match that in the
+// language file (e.g. 'language/resource.language.en_gb/strings.po', and EXIF_PARSE_STRING_ID_BASE
+// must match the ID of the first Exif string in that file.
+#define EXIF_PARSE_STRING_ID_BASE 21800
+enum {
+// Distance
+ ExifStrDistanceInfinite = EXIF_PARSE_STRING_ID_BASE,
+// Whitebalance et.al.
+ ExifStrManual,
+ ExifStrAuto,
+// Flash modes
+ ExifStrYes,
+ ExifStrNo,
+ ExifStrFlashNoStrobe,
+ ExifStrFlashStrobe,
+ ExifStrFlashManual,
+ ExifStrFlashManualNoReturn,
+ ExifStrFlashManualReturn,
+ ExifStrFlashAuto,
+ ExifStrFlashAutoNoReturn,
+ ExifStrFlashAutoReturn,
+ ExifStrFlashRedEye,
+ ExifStrFlashRedEyeNoReturn,
+ ExifStrFlashRedEyeReturn,
+ ExifStrFlashManualRedEye,
+ ExifStrFlashManualRedEyeNoReturn,
+ ExifStrFlashManualRedEyeReturn,
+ ExifStrFlashAutoRedEye,
+ ExifStrFlashAutoRedEyeNoReturn,
+ ExifStrFlashAutoRedEyeReturn,
+// Light sources
+ ExifStrDaylight,
+ ExifStrFluorescent,
+ ExifStrIncandescent,
+ ExifStrFlash,
+ ExifStrFineWeather,
+ ExifStrShade,
+// Metering Mode
+ ExifStrMeteringCenter,
+ ExifStrMeteringSpot,
+ ExifStrMeteringMatrix,
+// Exposure Program
+ ExifStrExposureProgram,
+ ExifStrExposureAperture,
+ ExifStrExposureShutter,
+ ExifStrExposureCreative,
+ ExifStrExposureAction,
+ ExifStrExposurePortrait,
+ ExifStrExposureLandscape,
+// Exposure mode
+ ExifStrExposureModeAuto,
+// ISO equivalent
+ ExifStrIsoEquivalent,
+// GPS latitude, longitude, altitude
+ ExifStrGpsLatitude,
+ ExifStrGpsLongitude,
+ ExifStrGpsAltitude,
+};
+
+
+
+
+//--------------------------------------------------------------------------
+// Report non fatal errors. Now that microsoft.net modifies exif headers,
+// there's corrupted ones, and there could be more in the future.
+//--------------------------------------------------------------------------
+static void ErrNonfatal(const char* const msg, int a1, int a2)
+{
+ printf("ExifParse - Nonfatal Error : %s %d %d", msg, a1, a2);
+}
+
+//--------------------------------------------------------------------------
+// Convert a 16 bit unsigned value from file's native byte order
+//--------------------------------------------------------------------------
+int CExifParse::Get16(const void* const Short, const bool motorolaOrder)
+{
+ if (motorolaOrder) {
+ return (((const unsigned char *)Short)[0] << 8) | ((const unsigned char *)Short)[1];
+ } else {
+ return (((const unsigned char *)Short)[1] << 8) | ((const unsigned char *)Short)[0];
+ }
+}
+
+//--------------------------------------------------------------------------
+// Convert a 32 bit signed value from file's native byte order
+//--------------------------------------------------------------------------
+int CExifParse::Get32(const void* const Long, const bool motorolaOrder)
+{
+ if (motorolaOrder) {
+ return (((const char *)Long)[0] << 24) | (((const unsigned char *)Long)[1] << 16)
+ | (((const unsigned char *)Long)[2] << 8 ) | (((const unsigned char *)Long)[3] << 0 );
+ } else {
+ return (((const char *)Long)[3] << 24) | (((const unsigned char *)Long)[2] << 16)
+ | (((const unsigned char *)Long)[1] << 8 ) | (((const unsigned char *)Long)[0] << 0 );
+ }
+}
+
+//--------------------------------------------------------------------------
+// It appears that CStdString constructor replaces "\n" with "\r\n" which results
+// in "\r\r\n" if there already is "\r\n", which in turn results in corrupted
+// display. So this is an attempt to undo effects of a smart constructor. Also,
+// replaces all nonprintable characters with "."
+//--------------------------------------------------------------------------
+/*void CExifParse::FixComment(CStdString& comment)
+{
+ comment.Replace("\r\r\n", "\r\n");
+ for (unsigned int i=0; i<comment.length(); i++)
+ {
+ if ((comment[i] < 32) && (comment[i] != '\n') && (comment[i] != '\t') && (comment[i] != '\r'))
+ {
+ comment[i] = '.';
+ }
+ }
+}*/
+
+//--------------------------------------------------------------------------
+// Evaluate number, be it int, rational, or float from directory.
+//--------------------------------------------------------------------------
+double CExifParse::ConvertAnyFormat(const void* const ValuePtr, int Format)
+{
+ double Value;
+ Value = 0;
+
+ switch(Format)
+ {
+ case FMT_SBYTE: Value = *(const signed char*)ValuePtr; break;
+ case FMT_BYTE: Value = *(const unsigned char*)ValuePtr; break;
+
+ case FMT_USHORT: Value = Get16(ValuePtr, m_MotorolaOrder); break;
+ case FMT_ULONG: Value = (unsigned)Get32(ValuePtr, m_MotorolaOrder); break;
+
+ case FMT_URATIONAL:
+ case FMT_SRATIONAL:
+ {
+ int Num,Den;
+ Num = Get32(ValuePtr, m_MotorolaOrder);
+ Den = Get32(4+(const char *)ValuePtr, m_MotorolaOrder);
+
+ if (Den == 0) Value = 0;
+ else Value = (double)Num/Den;
+ }
+ break;
+
+ case FMT_SSHORT: Value = (signed short)Get16(ValuePtr, m_MotorolaOrder); break;
+ case FMT_SLONG: Value = Get32(ValuePtr, m_MotorolaOrder); break;
+
+ // Not sure if this is correct (never seen float used in Exif format)
+ case FMT_SINGLE: Value = (double)*(const float*)ValuePtr; break;
+ case FMT_DOUBLE: Value = *(const double*)ValuePtr; break;
+
+ default:
+ ErrNonfatal("Illegal format code %d",Format,0);
+ }
+ return Value;
+}
+
+//--------------------------------------------------------------------------
+// Exif date tag is stored as a fixed format string "YYYY:MM:DD HH:MM:SS".
+// If date is not set, then the string is filled with blanks and colons:
+// " : : : : ". We want this string localised.
+//--------------------------------------------------------------------------
+/*void CExifParse::LocaliseDate (void)
+{
+ if (m_ExifInfo[SLIDESHOW_EXIF_DATE_TIME][0] != ' ')
+ {
+ int year = atoi(m_ExifInfo[SLIDESHOW_EXIF_DATE_TIME].substr(0, 4).c_str());
+ int month = atoi(m_ExifInfo[SLIDESHOW_EXIF_DATE_TIME].substr(5, 2).c_str());
+ int day = atoi(m_ExifInfo[SLIDESHOW_EXIF_DATE_TIME].substr(8, 2).c_str());
+ int hour = atoi(m_ExifInfo[SLIDESHOW_EXIF_DATE_TIME].substr(11,2).c_str());
+ int min = atoi(m_ExifInfo[SLIDESHOW_EXIF_DATE_TIME].substr(14,2).c_str());
+ int sec = atoi(m_ExifInfo[SLIDESHOW_EXIF_DATE_TIME].substr(17,2).c_str());
+ CDateTime date(year, month, day, hour, min, sec);
+ m_ExifInfo[SLIDESHOW_EXIF_DATE_TIME] = date.GetAsLocalizedDateTime();
+ }
+}*/
+
+
+//--------------------------------------------------------------------------
+// Convert exposure time into a human readable format
+//--------------------------------------------------------------------------
+/*void CExifParse::GetExposureTime(const float exposureTime, CStdString& outStr)
+{
+ if (exposureTime)
+ {
+ if (exposureTime < 0.010) outStr.Format("%6.4fs ", exposureTime);
+ else outStr.Format("%5.3fs ", exposureTime);
+ if (exposureTime <= 0.5) outStr.Format("%s (1/%d)", outStr, (int)(0.5 + 1/exposureTime));
+ }
+}*/
+
+//--------------------------------------------------------------------------
+// Process one of the nested EXIF directories.
+//--------------------------------------------------------------------------
+void CExifParse::ProcessDir(const unsigned char* const DirStart,
+ const unsigned char* const OffsetBase,
+ const unsigned ExifLength,
+ int NestingLevel)
+{
+ if (NestingLevel > 4)
+ {
+ ErrNonfatal("Maximum directory nesting exceeded (corrupt exif header)", 0,0);
+ return;
+ }
+
+ char IndentString[25];
+ memset(IndentString, ' ', 25);
+ IndentString[NestingLevel * 4] = '\0';
+
+
+ int NumDirEntries = Get16((const void*)DirStart, m_MotorolaOrder);
+
+ const unsigned char* const DirEnd = DIR_ENTRY_ADDR(DirStart, NumDirEntries);
+ if (DirEnd+4 > (OffsetBase+ExifLength))
+ {
+ if (DirEnd+2 == OffsetBase+ExifLength || DirEnd == OffsetBase+ExifLength)
+ {
+ // Version 1.3 of jhead would truncate a bit too much.
+ // This also caught later on as well.
+ }
+ else
+ {
+ ErrNonfatal("Illegally sized directory", 0,0);
+ return;
+ }
+ }
+
+ for (int de=0;de<NumDirEntries;de++)
+ {
+ int Tag, Format, Components;
+ unsigned char* ValuePtr;
+ int ByteCount;
+ const unsigned char* const DirEntry = DIR_ENTRY_ADDR(DirStart, de);
+
+ Tag = Get16(DirEntry, m_MotorolaOrder);
+ Format = Get16(DirEntry+2, m_MotorolaOrder);
+ Components = Get32(DirEntry+4, m_MotorolaOrder);
+
+ if (Format <= 0 || Format > NUM_FORMATS)
+ {
+ ErrNonfatal("Illegal number format %d for tag %04x", Format, Tag);
+ continue;
+ }
+
+ if ((unsigned)Components > 0x10000)
+ {
+ ErrNonfatal("Illegal number of components %d for tag %04x", Components, Tag);
+ continue;
+ }
+
+ ByteCount = Components * BytesPerFormat[Format - 1];
+
+ if (ByteCount > 4)
+ {
+ unsigned OffsetVal;
+ OffsetVal = (unsigned)Get32(DirEntry+8, m_MotorolaOrder);
+ // If its bigger than 4 bytes, the dir entry contains an offset.
+ if (OffsetVal > UINT32_MAX - ByteCount || OffsetVal + ByteCount > ExifLength)
+ {
+ // Bogus pointer offset and / or bytecount value
+ ErrNonfatal("Illegal value pointer for tag %04x", Tag,0);
+ continue;
+ }
+ ValuePtr = (unsigned char*)(const_cast<unsigned char*>(OffsetBase)+OffsetVal);
+
+ if (OffsetVal > m_LargestExifOffset)
+ {
+ m_LargestExifOffset = OffsetVal;
+ }
+
+ }
+ else {
+ // 4 bytes or less and value is in the dir entry itself
+ ValuePtr = (unsigned char*)(const_cast<unsigned char*>(DirEntry)+8);
+ }
+
+
+ // Extract useful components of tag
+ switch(Tag)
+ {
+ case TAG_DESCRIPTION:
+ {
+ int length = max(ByteCount, 0);
+ length = min(length, MAX_COMMENT);
+ strncpy(m_ExifInfo->Description, (char *)ValuePtr, length);
+ m_ExifInfo->Description[length] = '\0';
+ break;
+ }
+ case TAG_MAKE:
+ {
+ int space = sizeof(m_ExifInfo->CameraMake);
+ if (space > 0)
+ {
+ strncpy(m_ExifInfo->CameraMake, (char *)ValuePtr, space - 1);
+ m_ExifInfo->CameraMake[space - 1] = '\0';
+ }
+ break;
+ }
+ case TAG_MODEL:
+ {
+ int space = sizeof(m_ExifInfo->CameraModel);
+ if (space > 0)
+ {
+ strncpy(m_ExifInfo->CameraModel, (char *)ValuePtr, space - 1);
+ m_ExifInfo->CameraModel[space - 1] = '\0';
+ }
+ break;
+ }
+// case TAG_SOFTWARE: strncpy(m_ExifInfo->Software, ValuePtr, 5); break;
+ case TAG_FOCALPLANEXRES: m_FocalPlaneXRes = ConvertAnyFormat(ValuePtr, Format); break;
+ case TAG_THUMBNAIL_OFFSET: m_ExifInfo->ThumbnailOffset = (unsigned)ConvertAnyFormat(ValuePtr, Format); break;
+ case TAG_THUMBNAIL_LENGTH: m_ExifInfo->ThumbnailSize = (unsigned)ConvertAnyFormat(ValuePtr, Format); break;
+
+ case TAG_MAKER_NOTE:
+ continue;
+ break;
+
+ case TAG_DATETIME_ORIGINAL:
+ {
+
+ int space = sizeof(m_ExifInfo->DateTime);
+ if (space > 0)
+ {
+ strncpy(m_ExifInfo->DateTime, (char *)ValuePtr, space - 1);
+ m_ExifInfo->DateTime[space - 1] = '\0';
+ // If we get a DATETIME_ORIGINAL, we use that one.
+ m_DateFound = true;
+ }
+ break;
+ }
+ case TAG_DATETIME_DIGITIZED:
+ case TAG_DATETIME:
+ {
+ if (!m_DateFound)
+ {
+ // If we don't already have a DATETIME_ORIGINAL, use whatever
+ // time fields we may have.
+ int space = sizeof(m_ExifInfo->DateTime);
+ if (space > 0)
+ {
+ strncpy(m_ExifInfo->DateTime, (char *)ValuePtr, space - 1);
+ m_ExifInfo->DateTime[space - 1] = '\0';
+ }
+ }
+ break;
+ }
+ case TAG_USERCOMMENT:
+ {
+ // The UserComment allows comments without the charset limitations of ImageDescription.
+ // Therefore the UserComment field is prefixed by a CharacterCode field (8 Byte):
+ // - ASCII: 'ASCII\0\0\0'
+ // - Unicode: 'UNICODE\0'
+ // - JIS X208-1990: 'JIS\0\0\0\0\0'
+ // - Unknown: '\0\0\0\0\0\0\0\0' (application specific)
+
+ m_ExifInfo->CommentsCharset = EXIF_COMMENT_CHARSET_UNKNOWN;
+
+ const int EXIF_COMMENT_CHARSET_LENGTH = 8;
+ if (ByteCount >= EXIF_COMMENT_CHARSET_LENGTH)
+ {
+ // As some implementations use spaces instead of \0 for the padding,
+ // we're not so strict and check only the prefix.
+ if (memcmp(ValuePtr, "ASCII", 5) == 0)
+ m_ExifInfo->CommentsCharset = EXIF_COMMENT_CHARSET_ASCII;
+ else if (memcmp(ValuePtr, "UNICODE", 7) == 0)
+ m_ExifInfo->CommentsCharset = EXIF_COMMENT_CHARSET_UNICODE;
+ else if (memcmp(ValuePtr, "JIS", 3) == 0)
+ m_ExifInfo->CommentsCharset = EXIF_COMMENT_CHARSET_JIS;
+
+ int length = ByteCount - EXIF_COMMENT_CHARSET_LENGTH;
+ length = min(length, MAX_COMMENT);
+ memcpy(m_ExifInfo->Comments, ValuePtr + EXIF_COMMENT_CHARSET_LENGTH, length);
+ m_ExifInfo->Comments[length] = '\0';
+// FixComment(comment); // Ensure comment is printable
+ }
+ }
+ break;
+
+ case TAG_XP_COMMENT:
+ {
+ // The XP user comment field is always unicode (UCS-2) encoded
+ m_ExifInfo->XPCommentsCharset = EXIF_COMMENT_CHARSET_UNICODE;
+ size_t length = min(ByteCount, MAX_COMMENT);
+ memcpy(m_ExifInfo->XPComment, ValuePtr, length);
+ m_ExifInfo->XPComment[length] = '\0';
+ }
+ break;
+
+ case TAG_FNUMBER:
+ // Simplest way of expressing aperture, so I trust it the most.
+ // (overwrite previously computd value if there is one)
+ m_ExifInfo->ApertureFNumber = (float)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_APERTURE:
+ case TAG_MAXAPERTURE:
+ // More relevant info always comes earlier, so only use this field if we don't
+ // have appropriate aperture information yet.
+ if (m_ExifInfo->ApertureFNumber == 0)
+ {
+ m_ExifInfo->ApertureFNumber = (float)exp(ConvertAnyFormat(ValuePtr, Format)*log(2.0)*0.5);
+ }
+ break;
+
+ case TAG_FOCALLENGTH:
+ // Nice digital cameras actually save the focal length as a function
+ // of how far they are zoomed in.
+ m_ExifInfo->FocalLength = (float)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_SUBJECT_DISTANCE:
+ // Inidcates the distacne the autofocus camera is focused to.
+ // Tends to be less accurate as distance increases.
+ {
+ float distance = (float)ConvertAnyFormat(ValuePtr, Format);
+ m_ExifInfo->Distance = distance;
+ }
+ break;
+
+ case TAG_EXPOSURETIME:
+ {
+ // Simplest way of expressing exposure time, so I trust it most.
+ // (overwrite previously computd value if there is one)
+ float expTime = (float)ConvertAnyFormat(ValuePtr, Format);
+ if (expTime)
+ m_ExifInfo->ExposureTime = expTime;
+ }
+ break;
+
+ case TAG_SHUTTERSPEED:
+ // More complicated way of expressing exposure time, so only use
+ // this value if we don't already have it from somewhere else.
+ if (m_ExifInfo->ExposureTime == 0)
+ {
+ m_ExifInfo->ExposureTime = (float)(1/exp(ConvertAnyFormat(ValuePtr, Format)*log(2.0)));
+ }
+ break;
+
+ case TAG_FLASH:
+ m_ExifInfo->FlashUsed = (int)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_ORIENTATION:
+ m_ExifInfo->Orientation = (int)ConvertAnyFormat(ValuePtr, Format);
+ if (m_ExifInfo->Orientation < 0 || m_ExifInfo->Orientation > 8)
+ {
+ ErrNonfatal("Undefined rotation value %d", m_ExifInfo->Orientation, 0);
+ m_ExifInfo->Orientation = 0;
+ }
+ break;
+
+ case TAG_EXIF_IMAGELENGTH:
+ case TAG_EXIF_IMAGEWIDTH:
+ // Use largest of height and width to deal with images that have been
+ // rotated to portrait format.
+ {
+ int a = (int)ConvertAnyFormat(ValuePtr, Format);
+ if (m_ExifImageWidth < a) m_ExifImageWidth = a;
+ }
+ break;
+
+ case TAG_FOCALPLANEUNITS:
+ switch((int)ConvertAnyFormat(ValuePtr, Format))
+ {
+ // According to the information I was using, 2 means meters.
+ // But looking at the Cannon powershot's files, inches is the only
+ // sensible value.
+ case 1: m_FocalPlaneUnits = 25.4; break; // inch
+ case 2: m_FocalPlaneUnits = 25.4; break;
+ case 3: m_FocalPlaneUnits = 10; break; // centimeter
+ case 4: m_FocalPlaneUnits = 1; break; // millimeter
+ case 5: m_FocalPlaneUnits = .001; break; // micrometer
+ }
+ break;
+
+ case TAG_EXPOSURE_BIAS:
+ m_ExifInfo->ExposureBias = (float)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_WHITEBALANCE:
+ m_ExifInfo->Whitebalance = (int)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_LIGHT_SOURCE:
+ //Quercus: 17-1-2004 Added LightSource, some cams return this, whitebalance or both
+ m_ExifInfo->LightSource = (int)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_METERING_MODE:
+ m_ExifInfo->MeteringMode = (int)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_EXPOSURE_PROGRAM:
+ m_ExifInfo->ExposureProgram = (int)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_EXPOSURE_INDEX:
+ if (m_ExifInfo->ISOequivalent == 0)
+ {
+ // Exposure index and ISO equivalent are often used interchangeably,
+ // so we will do the same.
+ // http://photography.about.com/library/glossary/bldef_ei.htm
+ m_ExifInfo->ISOequivalent = (int)ConvertAnyFormat(ValuePtr, Format);
+ }
+ break;
+
+ case TAG_ISO_EQUIVALENT:
+ m_ExifInfo->ISOequivalent = (int)ConvertAnyFormat(ValuePtr, Format);
+ if (m_ExifInfo->ISOequivalent < 50)
+ m_ExifInfo->ISOequivalent *= 200; // Fixes strange encoding on some older digicams.
+ break;
+
+ case TAG_EXPOSURE_MODE:
+ m_ExifInfo->ExposureMode = (int)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_DIGITALZOOMRATIO:
+ m_ExifInfo->DigitalZoomRatio = (float)ConvertAnyFormat(ValuePtr, Format);
+ break;
+
+ case TAG_EXIF_OFFSET:
+ case TAG_INTEROP_OFFSET:
+ {
+ const unsigned char* const SubdirStart = OffsetBase + (unsigned)Get32(ValuePtr, m_MotorolaOrder);
+ if (SubdirStart < OffsetBase || SubdirStart > OffsetBase+ExifLength)
+ {
+ ErrNonfatal("Illegal exif or interop ofset directory link",0,0);
+ }
+ else
+ {
+ ProcessDir(SubdirStart, OffsetBase, ExifLength, NestingLevel+1);
+ }
+ continue;
+ }
+ break;
+
+ case TAG_GPSINFO:
+ {
+ const unsigned char* const SubdirStart = OffsetBase + (unsigned)Get32(ValuePtr, m_MotorolaOrder);
+ if (SubdirStart < OffsetBase || SubdirStart > OffsetBase+ExifLength)
+ {
+ ErrNonfatal("Illegal GPS directory link",0,0);
+ }
+ else
+ {
+ ProcessGpsInfo(SubdirStart, ByteCount, OffsetBase, ExifLength);
+ }
+ continue;
+ }
+ break;
+
+ case TAG_FOCALLENGTH_35MM:
+ // The focal length equivalent 35 mm is a 2.2 tag (defined as of April 2002)
+ // if its present, use it to compute equivalent focal length instead of
+ // computing it from sensor geometry and actual focal length.
+ m_ExifInfo->FocalLength35mmEquiv = (unsigned)ConvertAnyFormat(ValuePtr, Format);
+ break;
+ }
+ }
+
+
+ // In addition to linking to subdirectories via exif tags,
+ // there's also a potential link to another directory at the end of each
+ // directory. this has got to be the result of a committee!
+ unsigned Offset;
+
+ if (DIR_ENTRY_ADDR(DirStart, NumDirEntries) + 4 <= OffsetBase+ExifLength)
+ {
+ Offset = (unsigned)Get32(DirStart+2+12*NumDirEntries, m_MotorolaOrder);
+ if (Offset)
+ {
+ const unsigned char* const SubdirStart = OffsetBase + Offset;
+ if (SubdirStart > OffsetBase+ExifLength || SubdirStart < OffsetBase)
+ {
+ if (SubdirStart > OffsetBase && SubdirStart < OffsetBase+ExifLength+20)
+ {
+ // Jhead 1.3 or earlier would crop the whole directory!
+ // As Jhead produces this form of format incorrectness,
+ // I'll just let it pass silently
+ }
+ else
+ {
+ ErrNonfatal("Illegal subdirectory link",0,0);
+ }
+ }
+ else
+ {
+ if (SubdirStart <= OffsetBase+ExifLength)
+ {
+ ProcessDir(SubdirStart, OffsetBase, ExifLength, NestingLevel+1);
+ }
+ }
+ if (Offset > m_LargestExifOffset)
+ {
+ m_LargestExifOffset = Offset;
+ }
+ }
+ }
+ else
+ {
+ // The exif header ends before the last next directory pointer.
+ }
+
+ if (m_ExifInfo->ThumbnailOffset)
+ {
+ m_ExifInfo->ThumbnailAtEnd = false;
+
+ if (m_ExifInfo->ThumbnailOffset <= ExifLength)
+ {
+ if (m_ExifInfo->ThumbnailSize > ExifLength - m_ExifInfo->ThumbnailOffset)
+ {
+ // If thumbnail extends past exif header, only save the part that
+ // actually exists. Canon's EOS viewer utility will do this - the
+ // thumbnail extracts ok with this hack.
+ m_ExifInfo->ThumbnailSize = ExifLength - m_ExifInfo->ThumbnailOffset;
+ }
+ }
+ }
+}
+
+
+//--------------------------------------------------------------------------
+// Process a EXIF marker
+// Describes all the drivel that most digital cameras include...
+//--------------------------------------------------------------------------
+bool CExifParse::Process (const unsigned char* const ExifSection, const unsigned short length, ExifInfo_t *info)
+{
+ m_ExifInfo = info;
+ // EXIF signature: "Exif\0\0"
+ // Check EXIF signatures
+ const char ExifHeader[] = "Exif\0\0";
+ const char ExifAlignment0[] = "II";
+ const char ExifAlignment1[] = "MM";
+ const char ExifExtra = 0x2a;
+
+ const char* pos = (const char*)(ExifSection + sizeof(short)); // position data pointer after length field
+
+ if (memcmp(pos, ExifHeader,6))
+ {
+ printf("ExifParse: incorrect Exif header");
+ return false;
+ }
+ pos += 6;
+
+ if (memcmp(pos, ExifAlignment0, strlen(ExifAlignment0)) == 0)
+ {
+ m_MotorolaOrder = false;
+ }
+ else if (memcmp(pos, ExifAlignment1, strlen(ExifAlignment1)) == 0)
+ {
+ m_MotorolaOrder = true;
+ }
+ else
+ {
+ printf("ExifParse: invalid Exif alignment marker");
+ return false;
+ }
+ pos += strlen(ExifAlignment0);
+
+ // Check the next value for correctness.
+ if (Get16((const void*)(pos), m_MotorolaOrder) != ExifExtra)
+ {
+ printf("ExifParse: invalid Exif start (1)");
+ return false;
+ }
+ pos += sizeof(short);
+
+ unsigned long FirstOffset = (unsigned)Get32((const void*)pos, m_MotorolaOrder);
+ if (FirstOffset < 8 || FirstOffset + 8 >= length)
+ {
+ ErrNonfatal("Invalid offset of first IFD value: %u", FirstOffset, 0);
+ return false;
+ }
+
+
+
+ // First directory starts 16 bytes in. All offset are relative to 8 bytes in.
+ ProcessDir(ExifSection+8+FirstOffset, ExifSection+8, length-8, 0);
+
+ m_ExifInfo->ThumbnailAtEnd = m_ExifInfo->ThumbnailOffset >= m_LargestExifOffset;
+
+ // Compute the CCD width, in millimeters.
+ if (m_FocalPlaneXRes != 0)
+ {
+ // Note: With some cameras, its not possible to compute this correctly because
+ // they don't adjust the indicated focal plane resolution units when using less
+ // than maximum resolution, so the CCDWidth value comes out too small. Nothing
+ // that Jhead can do about it - its a camera problem.
+ m_ExifInfo->CCDWidth = (float)(m_ExifImageWidth * m_FocalPlaneUnits / m_FocalPlaneXRes);
+ }
+
+ if (m_ExifInfo->FocalLength)
+ {
+ if (m_ExifInfo->FocalLength35mmEquiv == 0)
+ {
+ // Compute 35 mm equivalent focal length based on sensor geometry if we haven't
+ // already got it explicitly from a tag.
+ if (m_ExifInfo->CCDWidth != 0.0f)
+ {
+ m_ExifInfo->FocalLength35mmEquiv =
+ (int)(m_ExifInfo->FocalLength / m_ExifInfo->CCDWidth * 36 + 0.5f);
+ }
+ }
+ }
+ return true;
+}
+
+
+
+//--------------------------------------------------------------------------
+// GPS Lat/Long extraction helper function
+//--------------------------------------------------------------------------
+void CExifParse::GetLatLong(
+ const unsigned int Format,
+ const unsigned char* ValuePtr,
+ const int ComponentSize,
+ char *latLongString)
+{
+ if (Format != FMT_URATIONAL)
+ {
+ ErrNonfatal("Illegal number format %d for GPS Lat/Long", Format, 0);
+ }
+ else
+ {
+ double Values[3];
+ for (unsigned a=0; a<3 ;a++)
+ {
+ Values[a] = ConvertAnyFormat(ValuePtr+a*ComponentSize, Format);
+ }
+ if (Values[0] < 0 || Values[0] > 180 || Values[1] < 0 || Values[1] >= 60 || Values[2] < 0 || Values[2] >= 60)
+ {
+ // Ignore invalid values (DMS format expected)
+ ErrNonfatal("Invalid Lat/Long value", 0, 0);
+ latLongString[0] = 0;
+ }
+ else
+ {
+ char latLong[30];
+ sprintf(latLong, "%3.0fd %2.0f' %5.2f\"", Values[0], Values[1], Values[2]);
+ strcat(latLongString, latLong);
+ }
+ }
+}
+
+//--------------------------------------------------------------------------
+// Process GPS info directory
+//--------------------------------------------------------------------------
+void CExifParse::ProcessGpsInfo(
+ const unsigned char* const DirStart,
+ int ByteCountUnused,
+ const unsigned char* const OffsetBase,
+ unsigned ExifLength)
+{
+ int NumDirEntries = Get16(DirStart, m_MotorolaOrder);
+
+ for (int de=0;de<NumDirEntries;de++)
+ {
+ const unsigned char* DirEntry = DIR_ENTRY_ADDR(DirStart, de);
+
+ // Fix from aosp 34a2564d3268a5ca1472c5076675782fbaf724d6
+ if (DirEntry + 12 > OffsetBase + ExifLength)
+ {
+ ErrNonfatal("GPS info directory goes past end of exif", 0, 0);
+ return;
+ }
+
+ unsigned Tag = Get16(DirEntry, m_MotorolaOrder);
+ unsigned Format = Get16(DirEntry+2, m_MotorolaOrder);
+ unsigned Components = (unsigned)Get32(DirEntry+4, m_MotorolaOrder);
+ if (Format == 0 || Format > NUM_FORMATS)
+ {
+ ErrNonfatal("Illegal number format %d for tag %04x", Format, Tag);
+ continue;
+ }
+
+ unsigned ComponentSize = BytesPerFormat[Format - 1];
+ unsigned ByteCount = Components * ComponentSize;
+
+ const unsigned char* ValuePtr;
+
+ if (ByteCount > 4)
+ {
+ unsigned OffsetVal = (unsigned)Get32(DirEntry+8, m_MotorolaOrder);
+ // If its bigger than 4 bytes, the dir entry contains an offset.
+ if (OffsetVal > UINT32_MAX - ByteCount || OffsetVal + ByteCount > ExifLength)
+ {
+ // Bogus pointer offset and / or bytecount value
+ ErrNonfatal("Illegal value pointer for tag %04x", Tag,0);
+ continue;
+ }
+ ValuePtr = OffsetBase+OffsetVal;
+ }
+ else
+ {
+ // 4 bytes or less and value is in the dir entry itself
+ ValuePtr = DirEntry+8;
+ }
+
+ switch(Tag)
+ {
+ case TAG_GPS_LAT_REF:
+ m_ExifInfo->GpsLat[0] = ValuePtr[0];
+ m_ExifInfo->GpsLat[1] = 0;
+ break;
+
+ case TAG_GPS_LONG_REF:
+ m_ExifInfo->GpsLong[0] = ValuePtr[0];
+ m_ExifInfo->GpsLong[1] = 0;
+ break;
+
+ case TAG_GPS_LAT:
+ GetLatLong(Format, ValuePtr, ComponentSize, m_ExifInfo->GpsLat);
+ break;
+ case TAG_GPS_LONG:
+ GetLatLong(Format, ValuePtr, ComponentSize, m_ExifInfo->GpsLong);
+ break;
+
+ case TAG_GPS_ALT_REF:
+ if (ValuePtr[0] != 0)
+ m_ExifInfo->GpsAlt[0] = '-';
+ m_ExifInfo->GpsAlt[1] = 0;
+ break;
+
+ case TAG_GPS_ALT:
+ {
+ char temp[18];
+ sprintf(temp, "%.2fm", static_cast<double>(ConvertAnyFormat(ValuePtr, Format)));
+ strcat(m_ExifInfo->GpsAlt, temp);
+ }
+ break;
+ }
+ }
+}
+
diff --git a/xbmc/pictures/ExifParse.h b/xbmc/pictures/ExifParse.h
new file mode 100644
index 0000000..6da3bf2
--- /dev/null
+++ b/xbmc/pictures/ExifParse.h
@@ -0,0 +1,37 @@
+#pragma once
+
+#include "libexif.h"
+
+class CExifParse
+{
+ public:
+ ~CExifParse(void) = default;
+ bool Process(const unsigned char* const Data, const unsigned short length, ExifInfo_t *info);
+ static int Get16(const void* const Short, const bool motorolaOrder=true);
+ static int Get32(const void* const Long, const bool motorolaOrder=true);
+
+ private:
+ ExifInfo_t *m_ExifInfo = nullptr;
+ double m_FocalPlaneXRes = 0.0;
+ double m_FocalPlaneUnits = 0.0;
+ unsigned m_LargestExifOffset = 0; // Last exif data referenced (to check if thumbnail is at end)
+ int m_ExifImageWidth = 0;
+ bool m_MotorolaOrder = false;
+ bool m_DateFound = false;
+
+// void LocaliseDate (void);
+// void GetExposureTime (const float exposureTime);
+ double ConvertAnyFormat(const void* const ValuePtr, int Format);
+ void ProcessDir(const unsigned char* const DirStart,
+ const unsigned char* const OffsetBase,
+ const unsigned ExifLength, int NestingLevel);
+ void ProcessGpsInfo(const unsigned char* const DirStart,
+ int ByteCountUnused,
+ const unsigned char* const OffsetBase,
+ unsigned ExifLength);
+ void GetLatLong(const unsigned int Format,
+ const unsigned char* ValuePtr,
+ const int ComponentSize,
+ char *latlongString);
+};
+
diff --git a/xbmc/pictures/GUIDialogPictureInfo.cpp b/xbmc/pictures/GUIDialogPictureInfo.cpp
new file mode 100644
index 0000000..90764e0
--- /dev/null
+++ b/xbmc/pictures/GUIDialogPictureInfo.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIDialogPictureInfo.h"
+
+#include "FileItem.h"
+#include "GUIInfoManager.h"
+#include "ServiceBroker.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "input/Key.h"
+
+#define CONTROL_PICTURE_INFO 5
+
+#define SLIDESHOW_STRING_BASE 21800 - SLIDESHOW_LABELS_START
+
+CGUIDialogPictureInfo::CGUIDialogPictureInfo(void)
+ : CGUIDialog(WINDOW_DIALOG_PICTURE_INFO, "DialogPictureInfo.xml")
+{
+ m_pictureInfo = new CFileItemList;
+ m_loadType = KEEP_IN_MEMORY;
+}
+
+CGUIDialogPictureInfo::~CGUIDialogPictureInfo(void)
+{
+ delete m_pictureInfo;
+}
+
+void CGUIDialogPictureInfo::SetPicture(CFileItem *item)
+{
+ CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetPicturesInfoProvider().SetCurrentSlide(item);
+}
+
+void CGUIDialogPictureInfo::OnInitWindow()
+{
+ UpdatePictureInfo();
+ CGUIDialog::OnInitWindow();
+}
+
+bool CGUIDialogPictureInfo::OnAction(const CAction& action)
+{
+ switch (action.GetID())
+ {
+ // if we're running from slideshow mode, drop the "next picture" and "previous picture" actions through.
+ case ACTION_NEXT_PICTURE:
+ case ACTION_PREV_PICTURE:
+ case ACTION_PLAYER_PLAY:
+ case ACTION_PAUSE:
+ if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_SLIDESHOW)
+ {
+ CGUIWindow* pWindow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_SLIDESHOW);
+ return pWindow->OnAction(action);
+ }
+ break;
+
+ case ACTION_SHOW_INFO:
+ Close();
+ return true;
+ };
+ return CGUIDialog::OnAction(action);
+}
+
+void CGUIDialogPictureInfo::FrameMove()
+{
+ const CFileItem* item = CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetPicturesInfoProvider().GetCurrentSlide();
+ if (item && item->GetPath() != m_currentPicture)
+ {
+ UpdatePictureInfo();
+ m_currentPicture = item->GetPath();
+ }
+ CGUIDialog::FrameMove();
+}
+
+void CGUIDialogPictureInfo::UpdatePictureInfo()
+{
+ // add stuff from the current slide to the list
+ CGUIMessage msgReset(GUI_MSG_LABEL_RESET, GetID(), CONTROL_PICTURE_INFO);
+ OnMessage(msgReset);
+ m_pictureInfo->Clear();
+ for (int info = SLIDESHOW_LABELS_START; info <= SLIDESHOW_LABELS_END; ++info)
+ {
+ // we only want to add SLIDESHOW_EXIF_DATE_TIME
+ // so we skip the other date formats
+ if (info == SLIDESHOW_EXIF_DATE || info == SLIDESHOW_EXIF_LONG_DATE || info == SLIDESHOW_EXIF_LONG_DATE_TIME )
+ continue;
+
+ std::string picInfo =
+ CServiceBroker::GetGUI()->GetInfoManager().GetLabel(info, INFO::DEFAULT_CONTEXT);
+ if (!picInfo.empty())
+ {
+ CFileItemPtr item(new CFileItem(g_localizeStrings.Get(SLIDESHOW_STRING_BASE + info)));
+ item->SetLabel2(picInfo);
+ m_pictureInfo->Add(item);
+ }
+ }
+ CGUIMessage msg(GUI_MSG_LABEL_BIND, GetID(), CONTROL_PICTURE_INFO, 0, 0, m_pictureInfo);
+ OnMessage(msg);
+}
+
+void CGUIDialogPictureInfo::OnDeinitWindow(int nextWindowID)
+{
+ CGUIDialog::OnDeinitWindow(nextWindowID);
+ CGUIMessage msgReset(GUI_MSG_LABEL_RESET, GetID(), CONTROL_PICTURE_INFO);
+ OnMessage(msgReset);
+ m_pictureInfo->Clear();
+ m_currentPicture.clear();
+}
diff --git a/xbmc/pictures/GUIDialogPictureInfo.h b/xbmc/pictures/GUIDialogPictureInfo.h
new file mode 100644
index 0000000..20e5b3b
--- /dev/null
+++ b/xbmc/pictures/GUIDialogPictureInfo.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "guilib/GUIDialog.h"
+
+class CFileItemList;
+
+class CGUIDialogPictureInfo :
+ public CGUIDialog
+{
+public:
+ CGUIDialogPictureInfo(void);
+ ~CGUIDialogPictureInfo(void) override;
+ void SetPicture(CFileItem *item);
+ void FrameMove() override;
+
+protected:
+ void OnInitWindow() override;
+ void OnDeinitWindow(int nextWindowID) override;
+ bool OnAction(const CAction& action) override;
+ void UpdatePictureInfo();
+
+ CFileItemList* m_pictureInfo;
+ std::string m_currentPicture;
+};
diff --git a/xbmc/pictures/GUIViewStatePictures.cpp b/xbmc/pictures/GUIViewStatePictures.cpp
new file mode 100644
index 0000000..47d0cdf
--- /dev/null
+++ b/xbmc/pictures/GUIViewStatePictures.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIViewStatePictures.h"
+
+#include "FileItem.h"
+#include "ServiceBroker.h"
+#include "filesystem/Directory.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/WindowIDs.h"
+#include "settings/MediaSourceSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/FileExtensionProvider.h"
+#include "view/ViewState.h"
+#include "view/ViewStateSettings.h"
+
+using namespace XFILE;
+using namespace ADDON;
+
+CGUIViewStateWindowPictures::CGUIViewStateWindowPictures(const CFileItemList& items) : CGUIViewState(items)
+{
+ if (items.IsVirtualDirectoryRoot())
+ {
+ AddSortMethod(SortByLabel, 551, LABEL_MASKS());
+ AddSortMethod(SortByDriveType, 564, LABEL_MASKS());
+ SetSortMethod(SortByLabel);
+
+ SetViewAsControl(DEFAULT_VIEW_LIST);
+
+ SetSortOrder(SortOrderAscending);
+ }
+ else
+ {
+ AddSortMethod(SortByLabel, 551, LABEL_MASKS("%L", "%I", "%L", "")); // Filename, Size | Foldername, empty
+ AddSortMethod(SortBySize, 553, LABEL_MASKS("%L", "%I", "%L", "%I")); // Filename, Size | Foldername, Size
+ AddSortMethod(SortByDate, 552, LABEL_MASKS("%L", "%J", "%L", "%J")); // Filename, Date | Foldername, Date
+ AddSortMethod(SortByDateTaken, 577, LABEL_MASKS("%L", "%t", "%L", "%J")); // Filename, DateTaken | Foldername, Date
+ AddSortMethod(SortByFile, 561, LABEL_MASKS("%L", "%I", "%L", "")); // Filename, Size | FolderName, empty
+
+ const CViewState *viewState = CViewStateSettings::GetInstance().Get("pictures");
+ SetSortMethod(viewState->m_sortDescription);
+ SetViewAsControl(viewState->m_viewMode);
+ SetSortOrder(viewState->m_sortDescription.sortOrder);
+ }
+ LoadViewState(items.GetPath(), WINDOW_PICTURES);
+}
+
+void CGUIViewStateWindowPictures::SaveViewState()
+{
+ SaveViewToDb(m_items.GetPath(), WINDOW_PICTURES, CViewStateSettings::GetInstance().Get("pictures"));
+}
+
+std::string CGUIViewStateWindowPictures::GetLockType()
+{
+ return "pictures";
+}
+
+std::string CGUIViewStateWindowPictures::GetExtensions()
+{
+ std::string extensions = CServiceBroker::GetFileExtensionProvider().GetPictureExtensions();
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_PICTURES_SHOWVIDEOS))
+ extensions += "|" + CServiceBroker::GetFileExtensionProvider().GetVideoExtensions();
+
+ return extensions;
+}
+
+VECSOURCES& CGUIViewStateWindowPictures::GetSources()
+{
+ VECSOURCES *pictureSources = CMediaSourceSettings::GetInstance().GetSources("pictures");
+
+ // Guard against source type not existing
+ if (pictureSources == nullptr)
+ {
+ static VECSOURCES empty;
+ return empty;
+ }
+
+ return *pictureSources;
+}
+
diff --git a/xbmc/pictures/GUIViewStatePictures.h b/xbmc/pictures/GUIViewStatePictures.h
new file mode 100644
index 0000000..343089a
--- /dev/null
+++ b/xbmc/pictures/GUIViewStatePictures.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "view/GUIViewState.h"
+
+class CGUIViewStateWindowPictures : public CGUIViewState
+{
+public:
+ explicit CGUIViewStateWindowPictures(const CFileItemList& items);
+
+ std::string GetLockType() override;
+ std::string GetExtensions() override;
+ VECSOURCES& GetSources() override;
+
+protected:
+ void SaveViewState() override;
+};
+
diff --git a/xbmc/pictures/GUIWindowPictures.cpp b/xbmc/pictures/GUIWindowPictures.cpp
new file mode 100644
index 0000000..ebc8fe9
--- /dev/null
+++ b/xbmc/pictures/GUIWindowPictures.cpp
@@ -0,0 +1,625 @@
+/*
+ * Copyright (C) 2005-2020 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIWindowPictures.h"
+
+#include "Autorun.h"
+#include "FileItem.h"
+#include "GUIDialogPictureInfo.h"
+#include "GUIPassword.h"
+#include "GUIWindowSlideShow.h"
+#include "PictureInfoLoader.h"
+#include "ServiceBroker.h"
+#include "URL.h"
+#include "Util.h"
+#include "addons/gui/GUIDialogAddonInfo.h"
+#include "application/Application.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPlayer.h"
+#include "dialogs/GUIDialogMediaSource.h"
+#include "dialogs/GUIDialogProgress.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "input/actions/ActionIDs.h"
+#include "interfaces/AnnouncementManager.h"
+#include "media/MediaLockState.h"
+#include "messaging/helpers/DialogOKHelper.h"
+#include "playlists/PlayList.h"
+#include "playlists/PlayListFactory.h"
+#include "settings/MediaSourceSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/SortUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/Variant.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+#include "view/GUIViewState.h"
+
+#define CONTROL_BTNSORTASC 4
+#define CONTROL_LABELFILES 12
+
+using namespace XFILE;
+using namespace KODI::MESSAGING;
+
+using namespace std::chrono_literals;
+
+#define CONTROL_BTNSLIDESHOW 6
+#define CONTROL_BTNSLIDESHOW_RECURSIVE 7
+#define CONTROL_SHUFFLE 9
+
+CGUIWindowPictures::CGUIWindowPictures(void)
+ : CGUIMediaWindow(WINDOW_PICTURES, "MyPics.xml")
+{
+ m_thumbLoader.SetObserver(this);
+ m_slideShowStarted = false;
+ m_dlgProgress = NULL;
+}
+
+void CGUIWindowPictures::OnInitWindow()
+{
+ CGUIMediaWindow::OnInitWindow();
+ if (m_slideShowStarted)
+ {
+ CGUIWindowSlideShow* wndw = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW);
+ std::string path;
+ if (wndw && wndw->GetCurrentSlide())
+ path = URIUtils::GetDirectory(wndw->GetCurrentSlide()->GetPath());
+ if (m_vecItems->IsPath(path))
+ {
+ if (wndw && wndw->GetCurrentSlide())
+ m_viewControl.SetSelectedItem(wndw->GetCurrentSlide()->GetPath());
+ SaveSelectedItemInHistory();
+ }
+ m_slideShowStarted = false;
+ }
+}
+
+CGUIWindowPictures::~CGUIWindowPictures(void) = default;
+
+bool CGUIWindowPictures::OnMessage(CGUIMessage& message)
+{
+ switch ( message.GetMessage() )
+ {
+ case GUI_MSG_WINDOW_DEINIT:
+ {
+ if (m_thumbLoader.IsLoading())
+ m_thumbLoader.StopThread();
+
+ }
+ break;
+
+ case GUI_MSG_WINDOW_INIT:
+ {
+ // is this the first time accessing this window?
+ if (m_vecItems->GetPath() == "?" && message.GetStringParam().empty())
+ message.SetStringParam(CMediaSourceSettings::GetInstance().GetDefaultSource("pictures"));
+
+ m_dlgProgress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS);
+ }
+ break;
+
+ case GUI_MSG_CLICKED:
+ {
+ int iControl = message.GetSenderId();
+ if (iControl == CONTROL_BTNSLIDESHOW) // Slide Show
+ {
+ OnSlideShow();
+ }
+ else if (iControl == CONTROL_BTNSLIDESHOW_RECURSIVE) // Recursive Slide Show
+ {
+ OnSlideShowRecursive();
+ }
+ else if (iControl == CONTROL_SHUFFLE)
+ {
+ const std::shared_ptr<CSettings> settings = CServiceBroker::GetSettingsComponent()->GetSettings();
+ settings->ToggleBool(CSettings::SETTING_SLIDESHOW_SHUFFLE);
+ settings->Save();
+ }
+ else if (m_viewControl.HasControl(iControl)) // list/thumb control
+ {
+ int iItem = m_viewControl.GetSelectedItem();
+ int iAction = message.GetParam1();
+
+ // iItem is checked for validity inside these routines
+ if (iAction == ACTION_DELETE_ITEM)
+ {
+ // is delete allowed?
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_ALLOWFILEDELETION))
+ OnDeleteItem(iItem);
+ else
+ return false;
+ }
+ else if (iAction == ACTION_PLAYER_PLAY)
+ {
+ ShowPicture(iItem, true);
+ return true;
+ }
+ else if (iAction == ACTION_SHOW_INFO)
+ {
+ OnItemInfo(iItem);
+ return true;
+ }
+ }
+ }
+ break;
+ }
+ return CGUIMediaWindow::OnMessage(message);
+}
+
+void CGUIWindowPictures::UpdateButtons()
+{
+ CGUIMediaWindow::UpdateButtons();
+
+ // Update the shuffle button
+ SET_CONTROL_SELECTED(GetID(), CONTROL_SHUFFLE, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_SLIDESHOW_SHUFFLE));
+
+ // check we can slideshow or recursive slideshow
+ int nFolders = m_vecItems->GetFolderCount();
+ if (nFolders == m_vecItems->Size() ||
+ m_vecItems->GetPath() == "addons://sources/image/")
+ {
+ CONTROL_DISABLE(CONTROL_BTNSLIDESHOW);
+ }
+ else
+ {
+ CONTROL_ENABLE(CONTROL_BTNSLIDESHOW);
+ }
+ if (m_guiState.get() && !m_guiState->HideParentDirItems())
+ nFolders--;
+ if (m_vecItems->Size() == 0 || nFolders == 0 ||
+ m_vecItems->GetPath() == "addons://sources/image/")
+ {
+ CONTROL_DISABLE(CONTROL_BTNSLIDESHOW_RECURSIVE);
+ }
+ else
+ {
+ CONTROL_ENABLE(CONTROL_BTNSLIDESHOW_RECURSIVE);
+ }
+}
+
+void CGUIWindowPictures::OnPrepareFileItems(CFileItemList& items)
+{
+ CGUIMediaWindow::OnPrepareFileItems(items);
+
+ for (int i=0;i<items.Size();++i )
+ if (StringUtils::EqualsNoCase(items[i]->GetLabel(), "folder.jpg"))
+ items.Remove(i);
+
+ if (items.GetFolderCount() == items.Size() || !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_PICTURES_USETAGS))
+ return;
+
+ // Start the music info loader thread
+ CPictureInfoLoader loader;
+ loader.SetProgressCallback(m_dlgProgress);
+ loader.Load(items);
+
+ bool bShowProgress = !CServiceBroker::GetGUI()->GetWindowManager().HasModalDialog(true);
+ bool bProgressVisible = false;
+
+ auto start = std::chrono::steady_clock::now();
+
+ while (loader.IsLoading() && m_dlgProgress && !m_dlgProgress->IsCanceled())
+ {
+ if (bShowProgress)
+ { // Do we have to init a progress dialog?
+ auto end = std::chrono::steady_clock::now();
+ auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
+
+ if (!bProgressVisible && duration.count() > 1500 && m_dlgProgress)
+ { // tag loading takes more then 1.5 secs, show a progress dialog
+ CURL url(items.GetPath());
+
+ m_dlgProgress->SetHeading(CVariant{189});
+ m_dlgProgress->SetLine(0, CVariant{505});
+ m_dlgProgress->SetLine(1, CVariant{""});
+ m_dlgProgress->SetLine(2, CVariant{url.GetWithoutUserDetails()});
+ m_dlgProgress->Open();
+ m_dlgProgress->ShowProgressBar(true);
+ bProgressVisible = true;
+ }
+
+ if (bProgressVisible && m_dlgProgress)
+ { // keep GUI alive
+ m_dlgProgress->Progress();
+ }
+ } // if (bShowProgress)
+ KODI::TIME::Sleep(1ms);
+ } // while (loader.IsLoading())
+
+ if (bProgressVisible && m_dlgProgress)
+ m_dlgProgress->Close();
+}
+
+bool CGUIWindowPictures::Update(const std::string &strDirectory, bool updateFilterPath /* = true */)
+{
+ if (m_thumbLoader.IsLoading())
+ m_thumbLoader.StopThread();
+
+ if (!CGUIMediaWindow::Update(strDirectory, updateFilterPath))
+ return false;
+
+ m_vecItems->SetArt("thumb", "");
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_PICTURES_GENERATETHUMBS))
+ m_thumbLoader.Load(*m_vecItems);
+
+ CPictureThumbLoader thumbLoader;
+ std::string thumb = thumbLoader.GetCachedImage(*m_vecItems, "thumb");
+ m_vecItems->SetArt("thumb", thumb);
+
+ return true;
+}
+
+bool CGUIWindowPictures::OnClick(int iItem, const std::string &player)
+{
+ if ( iItem < 0 || iItem >= m_vecItems->Size() ) return true;
+ CFileItemPtr pItem = m_vecItems->Get(iItem);
+
+ if (pItem->IsCBZ() || pItem->IsCBR())
+ {
+ CURL pathToUrl;
+ if (pItem->IsCBZ())
+ pathToUrl = URIUtils::CreateArchivePath("zip", pItem->GetURL(), "");
+ else
+ pathToUrl = URIUtils::CreateArchivePath("rar", pItem->GetURL(), "");
+
+ OnShowPictureRecursive(pathToUrl.Get());
+ return true;
+ }
+ else if (CGUIMediaWindow::OnClick(iItem, player))
+ return true;
+
+ return false;
+}
+
+bool CGUIWindowPictures::GetDirectory(const std::string &strDirectory, CFileItemList& items)
+{
+ if (!CGUIMediaWindow::GetDirectory(strDirectory, items))
+ return false;
+
+ std::string label;
+ if (items.GetLabel().empty() && m_rootDir.IsSource(items.GetPath(), CMediaSourceSettings::GetInstance().GetSources("pictures"), &label))
+ items.SetLabel(label);
+
+ if (items.GetContent().empty() && !items.IsVirtualDirectoryRoot() && !items.IsPlugin())
+ items.SetContent("images");
+ return true;
+}
+
+bool CGUIWindowPictures::OnPlayMedia(int iItem, const std::string &player)
+{
+ if (m_vecItems->Get(iItem)->IsVideo())
+ return CGUIMediaWindow::OnPlayMedia(iItem);
+
+ return ShowPicture(iItem, false);
+}
+
+bool CGUIWindowPictures::ShowPicture(int iItem, bool startSlideShow)
+{
+ if ( iItem < 0 || iItem >= m_vecItems->Size() ) return false;
+ CFileItemPtr pItem = m_vecItems->Get(iItem);
+ std::string strPicture = pItem->GetPath();
+
+#ifdef HAS_DVD_DRIVE
+ if (pItem->IsDVD())
+ return MEDIA_DETECT::CAutorun::PlayDiscAskResume(m_vecItems->Get(iItem)->GetPath());
+#endif
+
+ if (pItem->m_bIsShareOrDrive)
+ return false;
+
+ CGUIWindowSlideShow *pSlideShow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW);
+ if (!pSlideShow)
+ return false;
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (appPlayer->IsPlayingVideo())
+ g_application.StopPlaying();
+
+ pSlideShow->Reset();
+ bool bShowVideos = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_PICTURES_SHOWVIDEOS);
+ for (const auto& pItem : *m_vecItems)
+ {
+ if (!pItem->m_bIsFolder &&
+ !(URIUtils::IsRAR(pItem->GetPath()) || URIUtils::IsZIP(pItem->GetPath())) &&
+ (pItem->IsPicture() || (bShowVideos && pItem->IsVideo())))
+ {
+ pSlideShow->Add(pItem.get());
+ }
+ }
+
+ if (pSlideShow->NumSlides() == 0)
+ return false;
+
+ pSlideShow->Select(strPicture);
+
+ if (startSlideShow)
+ pSlideShow->StartSlideShow();
+ else
+ {
+ CVariant param;
+ param["player"]["speed"] = 1;
+ param["player"]["playerid"] = PLAYLIST::TYPE_PICTURE;
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnPlay",
+ pSlideShow->GetCurrentSlide(), param);
+ }
+
+ m_slideShowStarted = true;
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SLIDESHOW);
+
+ return true;
+}
+
+void CGUIWindowPictures::OnShowPictureRecursive(const std::string& strPath)
+{
+ CGUIWindowSlideShow *pSlideShow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW);
+ if (pSlideShow)
+ {
+ // stop any video
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (appPlayer->IsPlayingVideo())
+ g_application.StopPlaying();
+
+ SortDescription sorting = m_guiState->GetSortMethod();
+ pSlideShow->AddFromPath(strPath, true,
+ sorting.sortBy, sorting.sortOrder, sorting.sortAttributes);
+ if (pSlideShow->NumSlides())
+ {
+ m_slideShowStarted = true;
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SLIDESHOW);
+ }
+ }
+}
+
+void CGUIWindowPictures::OnSlideShowRecursive(const std::string &strPicture)
+{
+ CGUIWindowSlideShow *pSlideShow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW);
+ if (pSlideShow)
+ {
+ std::string strExtensions;
+ CFileItemList items;
+ CGUIViewState* viewState=CGUIViewState::GetViewState(GetID(), items);
+ if (viewState)
+ {
+ strExtensions = viewState->GetExtensions();
+ delete viewState;
+ }
+ m_slideShowStarted = true;
+
+ SortDescription sorting = m_guiState->GetSortMethod();
+ pSlideShow->RunSlideShow(strPicture, true,
+ CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_SLIDESHOW_SHUFFLE),false,
+ "", true,
+ sorting.sortBy, sorting.sortOrder, sorting.sortAttributes,
+ strExtensions);
+ }
+}
+
+void CGUIWindowPictures::OnSlideShowRecursive()
+{
+ OnSlideShowRecursive(m_vecItems->GetPath());
+}
+
+void CGUIWindowPictures::OnSlideShow()
+{
+ OnSlideShow(m_vecItems->GetPath());
+}
+
+void CGUIWindowPictures::OnSlideShow(const std::string &strPicture)
+{
+ CGUIWindowSlideShow *pSlideShow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW);
+ if (pSlideShow)
+ {
+ std::string strExtensions;
+ CFileItemList items;
+ CGUIViewState* viewState=CGUIViewState::GetViewState(GetID(), items);
+ if (viewState)
+ {
+ strExtensions = viewState->GetExtensions();
+ delete viewState;
+ }
+ m_slideShowStarted = true;
+
+ SortDescription sorting = m_guiState->GetSortMethod();
+ pSlideShow->RunSlideShow(strPicture, false ,false, false,
+ "", true,
+ sorting.sortBy, sorting.sortOrder, sorting.sortAttributes,
+ strExtensions);
+ }
+}
+
+void CGUIWindowPictures::OnRegenerateThumbs()
+{
+ if (m_thumbLoader.IsLoading()) return;
+ m_thumbLoader.SetRegenerateThumbs(true);
+ m_thumbLoader.Load(*m_vecItems);
+}
+
+void CGUIWindowPictures::GetContextButtons(int itemNumber, CContextButtons &buttons)
+{
+ CFileItemPtr item;
+ if (itemNumber >= 0 && itemNumber < m_vecItems->Size())
+ item = m_vecItems->Get(itemNumber);
+
+ if (item)
+ {
+ if ( m_vecItems->IsVirtualDirectoryRoot() || m_vecItems->GetPath() == "sources://pictures/" )
+ {
+ CGUIDialogContextMenu::GetContextButtons("pictures", item, buttons);
+ }
+ else
+ {
+ if (item)
+ {
+ if (!(item->m_bIsFolder || item->IsZIP() || item->IsRAR() || item->IsCBZ() || item->IsCBR() || item->IsScript()))
+ {
+ if (item->IsPicture())
+ buttons.Add(CONTEXT_BUTTON_INFO, 13406); // picture info
+ buttons.Add(CONTEXT_BUTTON_VIEW_SLIDESHOW, item->m_bIsFolder ? 13317 : 13422); // View Slideshow
+ }
+ if (item->m_bIsFolder)
+ buttons.Add(CONTEXT_BUTTON_RECURSIVE_SLIDESHOW, 13318); // Recursive Slideshow
+
+ if (!m_thumbLoader.IsLoading())
+ buttons.Add(CONTEXT_BUTTON_REFRESH_THUMBS, 13315); // Create Thumbnails
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_ALLOWFILEDELETION) && !item->IsReadOnly())
+ {
+ buttons.Add(CONTEXT_BUTTON_DELETE, 117);
+ buttons.Add(CONTEXT_BUTTON_RENAME, 118);
+ }
+ }
+
+ if (!item->IsPlugin() && !item->IsScript() && !m_vecItems->IsPlugin())
+ buttons.Add(CONTEXT_BUTTON_SWITCH_MEDIA, 523);
+ }
+ }
+ CGUIMediaWindow::GetContextButtons(itemNumber, buttons);
+}
+
+bool CGUIWindowPictures::OnContextButton(int itemNumber, CONTEXT_BUTTON button)
+{
+ CFileItemPtr item = (itemNumber >= 0 && itemNumber < m_vecItems->Size()) ? m_vecItems->Get(itemNumber) : CFileItemPtr();
+ if (CGUIDialogContextMenu::OnContextButton("pictures", item, button))
+ {
+ Update("");
+ return true;
+ }
+ switch (button)
+ {
+ case CONTEXT_BUTTON_VIEW_SLIDESHOW:
+ if (item && item->m_bIsFolder)
+ OnSlideShow(item->GetPath());
+ else
+ ShowPicture(itemNumber, true);
+ return true;
+ case CONTEXT_BUTTON_RECURSIVE_SLIDESHOW:
+ if (item)
+ OnSlideShowRecursive(item->GetPath());
+ return true;
+ case CONTEXT_BUTTON_INFO:
+ OnItemInfo(itemNumber);
+ return true;
+ case CONTEXT_BUTTON_REFRESH_THUMBS:
+ OnRegenerateThumbs();
+ return true;
+ case CONTEXT_BUTTON_DELETE:
+ OnDeleteItem(itemNumber);
+ return true;
+ case CONTEXT_BUTTON_RENAME:
+ OnRenameItem(itemNumber);
+ return true;
+ case CONTEXT_BUTTON_SWITCH_MEDIA:
+ CGUIDialogContextMenu::SwitchMedia("pictures", m_vecItems->GetPath());
+ return true;
+ default:
+ break;
+ }
+ return CGUIMediaWindow::OnContextButton(itemNumber, button);
+}
+
+bool CGUIWindowPictures::OnAddMediaSource()
+{
+ return CGUIDialogMediaSource::ShowAndAddMediaSource("pictures");
+}
+
+void CGUIWindowPictures::OnItemLoaded(CFileItem *pItem)
+{
+ CPictureThumbLoader::ProcessFoldersAndArchives(pItem);
+}
+
+void CGUIWindowPictures::LoadPlayList(const std::string& strPlayList)
+{
+ CLog::Log(LOGDEBUG,
+ "CGUIWindowPictures::LoadPlayList()... converting playlist into slideshow: {}",
+ strPlayList);
+ std::unique_ptr<PLAYLIST::CPlayList> pPlayList(PLAYLIST::CPlayListFactory::Create(strPlayList));
+ if (nullptr != pPlayList)
+ {
+ if (!pPlayList->Load(strPlayList))
+ {
+ HELPERS::ShowOKDialogText(CVariant{6}, CVariant{477});
+ return ; //hmmm unable to load playlist?
+ }
+ }
+
+ PLAYLIST::CPlayList playlist = *pPlayList;
+ if (playlist.size() > 0)
+ {
+ // set up slideshow
+ CGUIWindowSlideShow *pSlideShow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW);
+ if (!pSlideShow)
+ return;
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (appPlayer->IsPlayingVideo())
+ g_application.StopPlaying();
+
+ // convert playlist items into slideshow items
+ pSlideShow->Reset();
+ for (int i = 0; i < playlist.size(); ++i)
+ {
+ CFileItemPtr pItem = playlist[i];
+ //CLog::Log(LOGDEBUG,"-- playlist item: {}", pItem->GetPath());
+ if (pItem->IsPicture() && !(pItem->IsZIP() || pItem->IsRAR() || pItem->IsCBZ() || pItem->IsCBR()))
+ pSlideShow->Add(pItem.get());
+ }
+
+ // start slideshow if there are items
+ pSlideShow->StartSlideShow();
+ if (pSlideShow->NumSlides())
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SLIDESHOW);
+ }
+}
+
+void CGUIWindowPictures::OnItemInfo(int itemNumber)
+{
+ CFileItemPtr item = m_vecItems->Get(itemNumber);
+ if (!item)
+ return;
+ if (!m_vecItems->IsPlugin() && (item->IsPlugin() || item->IsScript()))
+ {
+ CGUIDialogAddonInfo::ShowForItem(item);
+ return;
+ }
+ if (item->m_bIsFolder || item->IsZIP() || item->IsRAR() || item->IsCBZ() || item->IsCBR() || !item->IsPicture())
+ return;
+ CGUIDialogPictureInfo *pictureInfo = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogPictureInfo>(WINDOW_DIALOG_PICTURE_INFO);
+ if (pictureInfo)
+ {
+ pictureInfo->SetPicture(item.get());
+ pictureInfo->Open();
+ }
+}
+
+std::string CGUIWindowPictures::GetStartFolder(const std::string &dir)
+{
+ if (StringUtils::EqualsNoCase(dir, "plugins") ||
+ StringUtils::EqualsNoCase(dir, "addons"))
+ return "addons://sources/image/";
+
+ SetupShares();
+ VECSOURCES shares;
+ m_rootDir.GetSources(shares);
+ bool bIsSourceName = false;
+ int iIndex = CUtil::GetMatchingSource(dir, shares, bIsSourceName);
+ if (iIndex > -1)
+ {
+ if (iIndex < static_cast<int>(shares.size()) && shares[iIndex].m_iHasLock == LOCK_STATE_LOCKED)
+ {
+ CFileItem item(shares[iIndex]);
+ if (!g_passwordManager.IsItemUnlocked(&item,"pictures"))
+ return "";
+ }
+ if (bIsSourceName)
+ return shares[iIndex].strPath;
+ return dir;
+ }
+ return CGUIMediaWindow::GetStartFolder(dir);
+}
diff --git a/xbmc/pictures/GUIWindowPictures.h b/xbmc/pictures/GUIWindowPictures.h
new file mode 100644
index 0000000..115e562
--- /dev/null
+++ b/xbmc/pictures/GUIWindowPictures.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "PictureThumbLoader.h"
+#include "windows/GUIMediaWindow.h"
+
+class CGUIDialogProgress;
+
+class CGUIWindowPictures : public CGUIMediaWindow, public IBackgroundLoaderObserver
+{
+public:
+ CGUIWindowPictures(void);
+ ~CGUIWindowPictures(void) override;
+ bool OnMessage(CGUIMessage& message) override;
+ void OnInitWindow() override;
+
+protected:
+ bool GetDirectory(const std::string &strDirectory, CFileItemList& items) override;
+ void OnItemInfo(int item);
+ bool OnClick(int iItem, const std::string &player = "") override;
+ void UpdateButtons() override;
+ void OnPrepareFileItems(CFileItemList& items) override;
+ bool Update(const std::string &strDirectory, bool updateFilterPath = true) override;
+ void GetContextButtons(int itemNumber, CContextButtons &buttons) override;
+ bool OnContextButton(int itemNumber, CONTEXT_BUTTON button) override;
+ bool OnAddMediaSource() override;
+ std::string GetStartFolder(const std::string &dir) override;
+
+ void OnRegenerateThumbs();
+ bool OnPlayMedia(int iItem, const std::string &player = "") override;
+ bool ShowPicture(int iItem, bool startSlideShow);
+ void OnShowPictureRecursive(const std::string& strPath);
+ void OnSlideShow(const std::string& strPicture);
+ void OnSlideShow();
+ void OnSlideShowRecursive(const std::string& strPicture);
+ void OnSlideShowRecursive();
+ void OnItemLoaded(CFileItem* pItem) override;
+ void LoadPlayList(const std::string& strPlayList) override;
+
+ CGUIDialogProgress* m_dlgProgress;
+
+ CPictureThumbLoader m_thumbLoader;
+ bool m_slideShowStarted;
+};
diff --git a/xbmc/pictures/GUIWindowSlideShow.cpp b/xbmc/pictures/GUIWindowSlideShow.cpp
new file mode 100644
index 0000000..a1a898b
--- /dev/null
+++ b/xbmc/pictures/GUIWindowSlideShow.cpp
@@ -0,0 +1,1382 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "GUIWindowSlideShow.h"
+
+#include "FileItem.h"
+#include "GUIDialogPictureInfo.h"
+#include "GUIInfoManager.h"
+#include "GUIUserMessages.h"
+#include "ServiceBroker.h"
+#include "TextureDatabase.h"
+#include "URL.h"
+#include "application/Application.h"
+#include "application/ApplicationComponents.h"
+#include "application/ApplicationPlayer.h"
+#include "application/ApplicationPowerHandling.h"
+#include "filesystem/Directory.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUILabelControl.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "guilib/Texture.h"
+#include "input/Key.h"
+#include "interfaces/AnnouncementManager.h"
+#include "pictures/GUIViewStatePictures.h"
+#include "pictures/PictureThumbLoader.h"
+#include "playlists/PlayListTypes.h"
+#include "rendering/RenderSystem.h"
+#include "settings/DisplaySettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/Random.h"
+#include "utils/URIUtils.h"
+#include "utils/Variant.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+
+#include <random>
+
+using namespace XFILE;
+using namespace KODI::MESSAGING;
+using namespace std::chrono_literals;
+
+#define MAX_ZOOM_FACTOR 10
+#define MAX_PICTURE_SIZE 2048*2048
+
+#define IMMEDIATE_TRANSITION_TIME 1
+
+#define PICTURE_MOVE_AMOUNT 0.02f
+#define PICTURE_MOVE_AMOUNT_ANALOG 0.01f
+#define PICTURE_MOVE_AMOUNT_TOUCH 0.002f
+#define PICTURE_VIEW_BOX_COLOR 0xffffff00 // YELLOW
+#define PICTURE_VIEW_BOX_BACKGROUND 0xff000000 // BLACK
+
+#define ROTATION_SNAP_RANGE 10.0f
+
+#define LABEL_ROW1 10
+#define CONTROL_PAUSE 13
+
+static float zoomamount[10] = { 1.0f, 1.2f, 1.5f, 2.0f, 2.8f, 4.0f, 6.0f, 9.0f, 13.5f, 20.0f };
+
+CBackgroundPicLoader::CBackgroundPicLoader()
+ : CThread("BgPicLoader")
+ , m_iPic{0}
+ , m_iSlideNumber{0}
+ , m_maxWidth{0}
+ , m_maxHeight{0}
+ , m_isLoading{false}
+ , m_pCallback{nullptr}
+{
+}
+
+CBackgroundPicLoader::~CBackgroundPicLoader()
+{
+ StopThread();
+}
+
+void CBackgroundPicLoader::Create(CGUIWindowSlideShow *pCallback)
+{
+ m_pCallback = pCallback;
+ m_isLoading = false;
+ CThread::Create(false);
+}
+
+void CBackgroundPicLoader::Process()
+{
+ auto totalTime = std::chrono::milliseconds(0);
+ unsigned int count = 0;
+ while (!m_bStop)
+ { // loop around forever, waiting for the app to call LoadPic
+ if (AbortableWait(m_loadPic, 10ms) == WAIT_SIGNALED)
+ {
+ if (m_pCallback)
+ {
+ auto start = std::chrono::steady_clock::now();
+ std::unique_ptr<CTexture> texture =
+ CTexture::LoadFromFile(m_strFileName, m_maxWidth, m_maxHeight);
+
+ auto end = std::chrono::steady_clock::now();
+ auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
+
+ totalTime += duration;
+ count++;
+ // tell our parent
+ bool bFullSize = false;
+ if (texture)
+ {
+ bFullSize = ((int)texture->GetWidth() < m_maxWidth) && ((int)texture->GetHeight() < m_maxHeight);
+ if (!bFullSize)
+ {
+ int iSize = texture->GetWidth() * texture->GetHeight() - MAX_PICTURE_SIZE;
+ if ((iSize + (int)texture->GetWidth() > 0) || (iSize + (int)texture->GetHeight() > 0))
+ bFullSize = true;
+ if (!bFullSize && texture->GetWidth() == CServiceBroker::GetRenderSystem()->GetMaxTextureSize())
+ bFullSize = true;
+ if (!bFullSize && texture->GetHeight() == CServiceBroker::GetRenderSystem()->GetMaxTextureSize())
+ bFullSize = true;
+ }
+ }
+ m_pCallback->OnLoadPic(m_iPic, m_iSlideNumber, m_strFileName, std::move(texture),
+ bFullSize);
+ m_isLoading = false;
+ }
+ }
+ }
+ if (count > 0)
+ CLog::Log(LOGDEBUG, "Time for loading {} images: {} ms, average {} ms", count,
+ totalTime.count(), totalTime.count() / count);
+}
+
+void CBackgroundPicLoader::LoadPic(int iPic, int iSlideNumber, const std::string &strFileName, const int maxWidth, const int maxHeight)
+{
+ m_iPic = iPic;
+ m_iSlideNumber = iSlideNumber;
+ m_strFileName = strFileName;
+ m_maxWidth = maxWidth;
+ m_maxHeight = maxHeight;
+ m_isLoading = true;
+ m_loadPic.Set();
+}
+
+CGUIWindowSlideShow::CGUIWindowSlideShow(void)
+ : CGUIDialog(WINDOW_SLIDESHOW, "SlideShow.xml")
+{
+ m_Resolution = RES_INVALID;
+ m_loadType = KEEP_IN_MEMORY;
+ m_bLoadNextPic = false;
+ Reset();
+}
+
+void CGUIWindowSlideShow::AnnouncePlayerPlay(const CFileItemPtr& item)
+{
+ CVariant param;
+ param["player"]["speed"] = m_bSlideShow && !m_bPause ? 1 : 0;
+ param["player"]["playerid"] = PLAYLIST::TYPE_PICTURE;
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnPlay", item, param);
+}
+
+void CGUIWindowSlideShow::AnnouncePlayerPause(const CFileItemPtr& item)
+{
+ CVariant param;
+ param["player"]["speed"] = 0;
+ param["player"]["playerid"] = PLAYLIST::TYPE_PICTURE;
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnPause", item, param);
+}
+
+void CGUIWindowSlideShow::AnnouncePlayerStop(const CFileItemPtr& item)
+{
+ CVariant param;
+ param["player"]["playerid"] = PLAYLIST::TYPE_PICTURE;
+ param["end"] = true;
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnStop", item, param);
+}
+
+void CGUIWindowSlideShow::AnnouncePlaylistClear()
+{
+ CVariant data;
+ data["playlistid"] = PLAYLIST::TYPE_PICTURE;
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Playlist, "OnClear", data);
+}
+
+void CGUIWindowSlideShow::AnnouncePlaylistAdd(const CFileItemPtr& item, int pos)
+{
+ CVariant data;
+ data["playlistid"] = PLAYLIST::TYPE_PICTURE;
+ data["position"] = pos;
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Playlist, "OnAdd", item, data);
+}
+
+void CGUIWindowSlideShow::AnnouncePropertyChanged(const std::string &strProperty, const CVariant &value)
+{
+ if (strProperty.empty() || value.isNull())
+ return;
+
+ CVariant data;
+ data["player"]["playerid"] = PLAYLIST::TYPE_PICTURE;
+ data["property"][strProperty] = value;
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnPropertyChanged",
+ data);
+}
+
+bool CGUIWindowSlideShow::IsPlaying() const
+{
+ return m_Image[m_iCurrentPic].IsLoaded();
+}
+
+void CGUIWindowSlideShow::Reset()
+{
+ m_bSlideShow = false;
+ m_bShuffled = false;
+ m_bPause = false;
+ m_bPlayingVideo = false;
+ m_bErrorMessage = false;
+ m_Image[0].UnLoad();
+ m_Image[0].Close();
+ m_Image[1].UnLoad();
+ m_Image[1].Close();
+
+ m_fRotate = 0.0f;
+ m_fInitialRotate = 0.0f;
+ m_iZoomFactor = 1;
+ m_fZoom = 1.0f;
+ m_fInitialZoom = 0.0f;
+ m_iCurrentSlide = 0;
+ m_iNextSlide = 1;
+ m_iCurrentPic = 0;
+ m_iDirection = 1;
+ m_iLastFailedNextSlide = -1;
+ m_slides.clear();
+ AnnouncePlaylistClear();
+ m_Resolution = CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution();
+}
+
+void CGUIWindowSlideShow::OnDeinitWindow(int nextWindowID)
+{
+ if (m_Resolution != CDisplaySettings::GetInstance().GetCurrentResolution())
+ {
+ //FIXME: Use GUI resolution for now
+ //CServiceBroker::GetWinSystem()->GetGfxContext().SetVideoResolution(CDisplaySettings::GetInstance().GetCurrentResolution(), true);
+ }
+
+ if (nextWindowID != WINDOW_FULLSCREEN_VIDEO &&
+ nextWindowID != WINDOW_FULLSCREEN_GAME)
+ {
+ // wait for any outstanding picture loads
+ if (m_pBackgroundLoader)
+ {
+ // sleep until the loader finishes loading the current pic
+ CLog::Log(LOGDEBUG,"Waiting for BackgroundLoader thread to close");
+ while (m_pBackgroundLoader->IsLoading())
+ KODI::TIME::Sleep(10ms);
+ // stop the thread
+ CLog::Log(LOGDEBUG,"Stopping BackgroundLoader thread");
+ m_pBackgroundLoader->StopThread();
+ m_pBackgroundLoader.reset();
+ }
+ // and close the images.
+ m_Image[0].Close();
+ m_Image[1].Close();
+ }
+ CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetPicturesInfoProvider().SetCurrentSlide(nullptr);
+ m_bSlideShow = false;
+
+ CGUIDialog::OnDeinitWindow(nextWindowID);
+}
+
+void CGUIWindowSlideShow::Add(const CFileItem *picture)
+{
+ CFileItemPtr item(new CFileItem(*picture));
+ if (!item->HasVideoInfoTag() && !item->HasPictureInfoTag())
+ {
+ // item without tag; get mimetype then we can tell whether it's video item
+ item->FillInMimeType();
+
+ if (!item->IsVideo())
+ // then it is a picture and force tag generation
+ item->GetPictureInfoTag();
+ }
+ AnnouncePlaylistAdd(item, m_slides.size());
+
+ m_slides.emplace_back(std::move(item));
+}
+
+void CGUIWindowSlideShow::ShowNext()
+{
+ if (m_slides.size() == 1)
+ return;
+
+ m_iDirection = 1;
+ m_iNextSlide = GetNextSlide();
+ m_iZoomFactor = 1;
+ m_fZoom = 1.0f;
+ m_fRotate = 0.0f;
+ m_bLoadNextPic = true;
+}
+
+void CGUIWindowSlideShow::ShowPrevious()
+{
+ if (m_slides.size() == 1)
+ return;
+
+ m_iDirection = -1;
+ m_iNextSlide = GetNextSlide();
+ m_iZoomFactor = 1;
+ m_fZoom = 1.0f;
+ m_fRotate = 0.0f;
+ m_bLoadNextPic = true;
+}
+
+void CGUIWindowSlideShow::Select(const std::string& strPicture)
+{
+ for (size_t i = 0; i < m_slides.size(); ++i)
+ {
+ const CFileItemPtr item = m_slides.at(i);
+ if (item->GetPath() == strPicture)
+ {
+ m_iDirection = 1;
+ if (!m_Image[m_iCurrentPic].IsLoaded() && (!m_pBackgroundLoader || !m_pBackgroundLoader->IsLoading()))
+ {
+ // will trigger loading current slide when next Process call.
+ m_iCurrentSlide = i;
+ m_iNextSlide = GetNextSlide();
+ }
+ else
+ {
+ m_iNextSlide = i;
+ m_bLoadNextPic = true;
+ }
+ return ;
+ }
+ }
+}
+
+void CGUIWindowSlideShow::GetSlideShowContents(CFileItemList &list)
+{
+ for (size_t index = 0; index < m_slides.size(); index++)
+ list.Add(CFileItemPtr(new CFileItem(*m_slides.at(index))));
+}
+
+std::shared_ptr<const CFileItem> CGUIWindowSlideShow::GetCurrentSlide()
+{
+ if (m_iCurrentSlide >= 0 && m_iCurrentSlide < static_cast<int>(m_slides.size()))
+ return m_slides.at(m_iCurrentSlide);
+ return CFileItemPtr();
+}
+
+bool CGUIWindowSlideShow::InSlideShow() const
+{
+ return m_bSlideShow;
+}
+
+void CGUIWindowSlideShow::StartSlideShow()
+{
+ m_bSlideShow = true;
+ m_iDirection = 1;
+ if (m_slides.size())
+ AnnouncePlayerPlay(m_slides.at(m_iCurrentSlide));
+}
+
+void CGUIWindowSlideShow::SetDirection(int direction)
+{
+ direction = direction >= 0 ? 1 : -1;
+ if (m_iDirection != direction)
+ {
+ m_iDirection = direction;
+ m_iNextSlide = GetNextSlide();
+ }
+}
+
+void CGUIWindowSlideShow::Process(unsigned int currentTime, CDirtyRegionList &regions)
+{
+ const RESOLUTION_INFO res = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo();
+
+ // reset the screensaver if we're in a slideshow
+ // (unless we are the screensaver!)
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appPower = components.GetComponent<CApplicationPowerHandling>();
+ if (m_bSlideShow && !m_bPause && !appPower->IsInScreenSaver())
+ appPower->ResetScreenSaver();
+ int iSlides = m_slides.size();
+ if (!iSlides)
+ return;
+
+ // if we haven't processed yet, we should mark the whole screen
+ if (!HasProcessed())
+ regions.push_back(CDirtyRegion(CRect(0.0f, 0.0f, (float)CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(), (float)CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight())));
+
+ if (m_iCurrentSlide < 0 || m_iCurrentSlide >= static_cast<int>(m_slides.size()))
+ m_iCurrentSlide = 0;
+ if (m_iNextSlide < 0 || m_iNextSlide >= static_cast<int>(m_slides.size()))
+ m_iNextSlide = GetNextSlide();
+
+ // Create our background loader if necessary
+ if (!m_pBackgroundLoader)
+ {
+ m_pBackgroundLoader.reset(new CBackgroundPicLoader());
+ m_pBackgroundLoader->Create(this);
+ }
+
+ bool bSlideShow = m_bSlideShow && !m_bPause && !m_bPlayingVideo;
+ if (bSlideShow && m_slides.at(m_iCurrentSlide)->HasProperty("unplayable"))
+ {
+ m_iNextSlide = GetNextSlide();
+ if (m_iCurrentSlide == m_iNextSlide)
+ return;
+ m_iCurrentSlide = m_iNextSlide;
+ m_iNextSlide = GetNextSlide();
+ }
+
+ if (m_bErrorMessage)
+ { // we have an error when loading either the current or next picture
+ // check to see if we have a picture loaded
+ CLog::Log(LOGDEBUG, "We have an error loading picture {}!", m_pBackgroundLoader->SlideNumber());
+ if (m_iCurrentSlide == m_pBackgroundLoader->SlideNumber())
+ {
+ if (m_Image[m_iCurrentPic].IsLoaded())
+ {
+ // current image was already loaded, so we can ignore this error.
+ m_bErrorMessage = false;
+ }
+ else
+ {
+ CLog::Log(LOGERROR, "Error loading the current image {}: {}", m_iCurrentSlide,
+ m_slides.at(m_iCurrentSlide)->GetPath());
+ if (!m_slides.at(m_iCurrentPic)->IsVideo())
+ {
+ // try next if we are in slideshow
+ CLog::Log(LOGINFO, "set image {} unplayable", m_slides.at(m_iCurrentSlide)->GetPath());
+ m_slides.at(m_iCurrentSlide)->SetProperty("unplayable", true);
+ }
+ if (m_bLoadNextPic || (bSlideShow && !m_bPause && !m_slides.at(m_iCurrentPic)->IsVideo()))
+ {
+ // change to next item, wait loading.
+ m_iCurrentSlide = m_iNextSlide;
+ m_iNextSlide = GetNextSlide();
+ m_bErrorMessage = false;
+ }
+ // else just drop through - there's nothing we can do (error message will be displayed)
+ }
+ }
+ else if (m_iNextSlide == m_pBackgroundLoader->SlideNumber())
+ {
+ CLog::Log(LOGERROR, "Error loading the next image {}: {}", m_iNextSlide,
+ m_slides.at(m_iNextSlide)->GetPath());
+ // load next image failed, then skip to load next of next if next is not video.
+ if (!m_slides.at(m_iNextSlide)->IsVideo())
+ {
+ CLog::Log(LOGINFO, "set image {} unplayable", m_slides.at(m_iNextSlide)->GetPath());
+ m_slides.at(m_iNextSlide)->SetProperty("unplayable", true);
+ // change to next item, wait loading.
+ m_iNextSlide = GetNextSlide();
+ }
+ else
+ { // prevent reload the next pic and repeat fail.
+ m_iLastFailedNextSlide = m_iNextSlide;
+ }
+ m_bErrorMessage = false;
+ }
+ else
+ { // Non-current and non-next slide, just ignore error.
+ CLog::Log(LOGERROR, "Error loading the non-current non-next image {}/{}: {}", m_iNextSlide,
+ m_pBackgroundLoader->SlideNumber(), m_slides.at(m_iNextSlide)->GetPath());
+ m_bErrorMessage = false;
+ }
+ }
+
+ if (m_bErrorMessage)
+ { // hack, just mark it all
+ regions.push_back(CDirtyRegion(CRect(0.0f, 0.0f, (float)CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(), (float)CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight())));
+ return;
+ }
+
+ if (!m_Image[m_iCurrentPic].IsLoaded() && !m_pBackgroundLoader->IsLoading())
+ { // load first image
+ CFileItemPtr item = m_slides.at(m_iCurrentSlide);
+ std::string picturePath = GetPicturePath(item.get());
+ if (!picturePath.empty())
+ {
+ if (item->IsVideo())
+ CLog::Log(LOGDEBUG, "Loading the thumb {} for current video {}: {}", picturePath,
+ m_iCurrentSlide, item->GetPath());
+ else
+ CLog::Log(LOGDEBUG, "Loading the current image {}: {}", m_iCurrentSlide, item->GetPath());
+
+ // load using the background loader
+ int maxWidth, maxHeight;
+
+ GetCheckedSize((float)res.iWidth * m_fZoom,
+ (float)res.iHeight * m_fZoom,
+ maxWidth, maxHeight);
+ m_pBackgroundLoader->LoadPic(m_iCurrentPic, m_iCurrentSlide, picturePath, maxWidth, maxHeight);
+ m_iLastFailedNextSlide = -1;
+ m_bLoadNextPic = false;
+ }
+ }
+
+ // check if we should discard an already loaded next slide
+ if (m_Image[1 - m_iCurrentPic].IsLoaded() && m_Image[1 - m_iCurrentPic].SlideNumber() != m_iNextSlide)
+ m_Image[1 - m_iCurrentPic].Close();
+
+ if (m_iNextSlide != m_iCurrentSlide && m_Image[m_iCurrentPic].IsLoaded() && !m_Image[1 - m_iCurrentPic].IsLoaded() && !m_pBackgroundLoader->IsLoading() && m_iLastFailedNextSlide != m_iNextSlide)
+ { // load the next image
+ m_iLastFailedNextSlide = -1;
+ CFileItemPtr item = m_slides.at(m_iNextSlide);
+ std::string picturePath = GetPicturePath(item.get());
+ if (!picturePath.empty() && (!item->IsVideo() || !m_bSlideShow || m_bPause))
+ {
+ if (item->IsVideo())
+ CLog::Log(LOGDEBUG, "Loading the thumb {} for next video {}: {}", picturePath, m_iNextSlide,
+ item->GetPath());
+ else
+ CLog::Log(LOGDEBUG, "Loading the next image {}: {}", m_iNextSlide, item->GetPath());
+
+ int maxWidth, maxHeight;
+ GetCheckedSize((float)res.iWidth * m_fZoom,
+ (float)res.iHeight * m_fZoom,
+ maxWidth, maxHeight);
+ m_pBackgroundLoader->LoadPic(1 - m_iCurrentPic, m_iNextSlide, picturePath, maxWidth, maxHeight);
+ }
+ }
+
+ bool bPlayVideo = m_slides.at(m_iCurrentSlide)->IsVideo() && m_iVideoSlide != m_iCurrentSlide;
+ if (bPlayVideo)
+ bSlideShow = false;
+
+ // render the current image
+ if (m_Image[m_iCurrentPic].IsLoaded())
+ {
+ m_Image[m_iCurrentPic].SetInSlideshow(bSlideShow);
+ m_Image[m_iCurrentPic].Pause(!bSlideShow);
+ m_Image[m_iCurrentPic].Process(currentTime, regions);
+ }
+
+ // Check if we should be transitioning immediately
+ if (m_bLoadNextPic && m_Image[m_iCurrentPic].IsLoaded())
+ {
+ CLog::Log(LOGDEBUG, "Starting immediate transition due to user wanting slide {}",
+ m_slides.at(m_iNextSlide)->GetPath());
+ if (m_Image[m_iCurrentPic].StartTransition())
+ {
+ m_Image[m_iCurrentPic].SetTransitionTime(1, IMMEDIATE_TRANSITION_TIME);
+ m_bLoadNextPic = false;
+ }
+ }
+
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+
+ // render the next image
+ if (m_Image[m_iCurrentPic].DrawNextImage())
+ {
+ if (m_bSlideShow && !m_bPause && m_slides.at(m_iNextSlide)->IsVideo())
+ {
+ // do not show thumb of video when playing slideshow
+ }
+ else if (m_Image[1 - m_iCurrentPic].IsLoaded())
+ {
+ if (appPlayer->IsPlayingVideo())
+ appPlayer->ClosePlayer();
+ m_bPlayingVideo = false;
+ m_iVideoSlide = -1;
+
+ // first time render the next image, make sure using current display effect.
+ if (!m_Image[1 - m_iCurrentPic].IsStarted())
+ {
+ CSlideShowPic::DISPLAY_EFFECT effect = GetDisplayEffect(m_iNextSlide);
+ if (m_Image[1 - m_iCurrentPic].DisplayEffectNeedChange(effect))
+ m_Image[1 - m_iCurrentPic].Reset(effect);
+ }
+ // set the appropriate transition time
+ m_Image[1 - m_iCurrentPic].SetTransitionTime(0, m_Image[m_iCurrentPic].GetTransitionTime(1));
+ m_Image[1 - m_iCurrentPic].Pause(!m_bSlideShow || m_bPause || m_slides.at(m_iNextSlide)->IsVideo());
+ m_Image[1 - m_iCurrentPic].Process(currentTime, regions);
+ }
+ else // next pic isn't loaded. We should hang around if it is in progress
+ {
+ if (m_pBackgroundLoader->IsLoading())
+ {
+ // CLog::Log(LOGDEBUG, "Having to hold the current image ({}) while we load {}", m_vecSlides[m_iCurrentSlide], m_vecSlides[m_iNextSlide]);
+ m_Image[m_iCurrentPic].Keep();
+ }
+ }
+ }
+
+ // check if we should swap images now
+ if (m_Image[m_iCurrentPic].IsFinished() || (m_bLoadNextPic && !m_Image[m_iCurrentPic].IsLoaded()))
+ {
+ m_bLoadNextPic = false;
+ if (m_Image[m_iCurrentPic].IsFinished())
+ CLog::Log(LOGDEBUG, "Image {} is finished rendering, switching to {}",
+ m_slides.at(m_iCurrentSlide)->GetPath(), m_slides.at(m_iNextSlide)->GetPath());
+ else
+ // what if it's bg loading?
+ CLog::Log(LOGDEBUG, "Image {} is not loaded, switching to {}",
+ m_slides.at(m_iCurrentSlide)->GetPath(), m_slides.at(m_iNextSlide)->GetPath());
+
+ if (m_Image[m_iCurrentPic].IsFinished() && m_iCurrentSlide == m_iNextSlide && m_Image[m_iCurrentPic].SlideNumber() == m_iNextSlide)
+ m_Image[m_iCurrentPic].Reset(GetDisplayEffect(m_iCurrentSlide));
+ else
+ {
+ if (m_Image[m_iCurrentPic].IsLoaded())
+ m_Image[m_iCurrentPic].Reset(GetDisplayEffect(m_iCurrentSlide));
+ else
+ m_Image[m_iCurrentPic].Close();
+
+ if ((m_Image[1 - m_iCurrentPic].IsLoaded() && m_Image[1 - m_iCurrentPic].SlideNumber() == m_iNextSlide) ||
+ (m_pBackgroundLoader->IsLoading() && m_pBackgroundLoader->SlideNumber() == m_iNextSlide && m_pBackgroundLoader->Pic() == 1 - m_iCurrentPic))
+ {
+ m_iCurrentPic = 1 - m_iCurrentPic;
+ }
+ else
+ {
+ m_Image[1 - m_iCurrentPic].Close();
+ m_iCurrentPic = 1 - m_iCurrentPic;
+ }
+ m_iCurrentSlide = m_iNextSlide;
+ m_iNextSlide = GetNextSlide();
+
+ bPlayVideo = m_slides.at(m_iCurrentSlide)->IsVideo() && m_iVideoSlide != m_iCurrentSlide;
+ }
+ AnnouncePlayerPlay(m_slides.at(m_iCurrentSlide));
+
+ m_iZoomFactor = 1;
+ m_fZoom = 1.0f;
+ m_fRotate = 0.0f;
+ }
+
+ if (bPlayVideo && !PlayVideo())
+ return;
+
+ if (m_Image[m_iCurrentPic].IsLoaded())
+ CServiceBroker::GetGUI()->GetInfoManager().GetInfoProviders().GetPicturesInfoProvider().SetCurrentSlide(m_slides.at(m_iCurrentSlide).get());
+
+ RenderPause();
+ if (m_slides.at(m_iCurrentSlide)->IsVideo() && appPlayer && appPlayer->IsRenderingGuiLayer())
+ {
+ MarkDirtyRegion();
+ }
+ CGUIWindow::Process(currentTime, regions);
+ m_renderRegion.SetRect(0, 0, (float)CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(), (float)CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight());
+}
+
+void CGUIWindowSlideShow::Render()
+{
+ if (m_slides.empty())
+ return;
+
+ CGraphicContext& gfxCtx = CServiceBroker::GetWinSystem()->GetGfxContext();
+ gfxCtx.Clear(0xff000000);
+
+ if (m_slides.at(m_iCurrentSlide)->IsVideo())
+ {
+ gfxCtx.SetViewWindow(0, 0, m_coordsRes.iWidth, m_coordsRes.iHeight);
+ gfxCtx.SetRenderingResolution(gfxCtx.GetVideoResolution(), false);
+
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+
+ if (appPlayer->IsRenderingVideoLayer())
+ {
+ const CRect old = gfxCtx.GetScissors();
+ CRect region = GetRenderRegion();
+ region.Intersect(old);
+ gfxCtx.SetScissors(region);
+ gfxCtx.Clear(0);
+ gfxCtx.SetScissors(old);
+ }
+ else if (appPlayer)
+ {
+ const UTILS::COLOR::Color alpha = gfxCtx.MergeAlpha(0xff000000) >> 24;
+ appPlayer->Render(false, alpha);
+ }
+
+ gfxCtx.SetRenderingResolution(m_coordsRes, m_needsScaling);
+ }
+ else
+ {
+ if (m_Image[m_iCurrentPic].IsLoaded())
+ m_Image[m_iCurrentPic].Render();
+
+ if (m_Image[m_iCurrentPic].DrawNextImage() && m_Image[1 - m_iCurrentPic].IsLoaded())
+ m_Image[1 - m_iCurrentPic].Render();
+ }
+
+ RenderErrorMessage();
+ CGUIWindow::Render();
+}
+
+void CGUIWindowSlideShow::RenderEx()
+{
+ if (m_slides.at(m_iCurrentSlide)->IsVideo())
+ {
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ appPlayer->Render(false, 255, false);
+ }
+
+ CGUIWindow::RenderEx();
+}
+
+int CGUIWindowSlideShow::GetNextSlide()
+{
+ if (m_slides.size() <= 1)
+ return m_iCurrentSlide;
+ int step = m_iDirection >= 0 ? 1 : -1;
+ int nextSlide = (m_iCurrentSlide + step + m_slides.size()) % m_slides.size();
+ while (nextSlide != m_iCurrentSlide)
+ {
+ if (!m_slides.at(nextSlide)->HasProperty("unplayable"))
+ return nextSlide;
+ nextSlide = (nextSlide + step + m_slides.size()) % m_slides.size();
+ }
+ return m_iCurrentSlide;
+}
+
+EVENT_RESULT CGUIWindowSlideShow::OnMouseEvent(const CPoint &point, const CMouseEvent &event)
+{
+ if (event.m_id == ACTION_GESTURE_NOTIFY)
+ {
+ int result = EVENT_RESULT_ROTATE | EVENT_RESULT_ZOOM;
+ if (m_iZoomFactor == 1 || !m_Image[m_iCurrentPic].m_bCanMoveHorizontally)
+ result |= EVENT_RESULT_SWIPE;
+ else
+ result |= EVENT_RESULT_PAN_HORIZONTAL;
+
+ if (m_Image[m_iCurrentPic].m_bCanMoveVertically)
+ result |= EVENT_RESULT_PAN_VERTICAL;
+
+ return (EVENT_RESULT)result;
+ }
+ else if (event.m_id == ACTION_GESTURE_BEGIN)
+ {
+ m_firstGesturePoint = point;
+ m_fInitialZoom = m_fZoom;
+ m_fInitialRotate = m_fRotate;
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_GESTURE_PAN)
+ {
+ // zoomed in - free move mode
+ if (m_iZoomFactor != 1 &&
+ (m_Image[m_iCurrentPic].m_bCanMoveHorizontally || m_Image[m_iCurrentPic].m_bCanMoveVertically))
+ {
+ Move(PICTURE_MOVE_AMOUNT_TOUCH / m_iZoomFactor * (m_firstGesturePoint.x - point.x), PICTURE_MOVE_AMOUNT_TOUCH / m_iZoomFactor * (m_firstGesturePoint.y - point.y));
+ m_firstGesturePoint = point;
+ }
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_GESTURE_SWIPE_LEFT || event.m_id == ACTION_GESTURE_SWIPE_RIGHT)
+ {
+ if (m_iZoomFactor == 1 || !m_Image[m_iCurrentPic].m_bCanMoveHorizontally)
+ {
+ // on zoomlevel 1 just detect swipe left and right
+ if (event.m_id == ACTION_GESTURE_SWIPE_LEFT)
+ OnAction(CAction(ACTION_NEXT_PICTURE));
+ else
+ OnAction(CAction(ACTION_PREV_PICTURE));
+ }
+ }
+ else if (event.m_id == ACTION_GESTURE_END || event.m_id == ACTION_GESTURE_ABORT)
+ {
+ if (m_fRotate != 0.0f)
+ {
+ // "snap" to nearest of 0, 90, 180 and 270 if the
+ // difference in angle is +/-10 degrees
+ float reminder = fmodf(m_fRotate, 90.0f);
+ if (fabs(reminder) < ROTATION_SNAP_RANGE)
+ Rotate(-reminder);
+ else if (reminder > 90.0f - ROTATION_SNAP_RANGE)
+ Rotate(90.0f - reminder);
+ else if (-reminder > 90.0f - ROTATION_SNAP_RANGE)
+ Rotate(-90.0f - reminder);
+ }
+
+ m_fInitialZoom = 0.0f;
+ m_fInitialRotate = 0.0f;
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_GESTURE_ZOOM)
+ {
+ ZoomRelative(m_fInitialZoom * event.m_offsetX, true);
+ return EVENT_RESULT_HANDLED;
+ }
+ else if (event.m_id == ACTION_GESTURE_ROTATE)
+ {
+ Rotate(m_fInitialRotate + event.m_offsetX - m_fRotate, true);
+ return EVENT_RESULT_HANDLED;
+ }
+ return EVENT_RESULT_UNHANDLED;
+}
+
+bool CGUIWindowSlideShow::OnAction(const CAction &action)
+{
+ switch (action.GetID())
+ {
+ case ACTION_SHOW_INFO:
+ {
+ CGUIDialogPictureInfo *pictureInfo = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogPictureInfo>(WINDOW_DIALOG_PICTURE_INFO);
+ if (pictureInfo)
+ {
+ // no need to set the picture here, it's done in Render()
+ pictureInfo->Open();
+ }
+ }
+ break;
+ case ACTION_STOP:
+ {
+ if (m_slides.size())
+ AnnouncePlayerStop(m_slides.at(m_iCurrentSlide));
+ auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (appPlayer->IsPlayingVideo())
+ appPlayer->ClosePlayer();
+ Close();
+ break;
+ }
+
+ case ACTION_NEXT_PICTURE:
+ ShowNext();
+ break;
+
+ case ACTION_PREV_PICTURE:
+ ShowPrevious();
+ break;
+
+ case ACTION_MOVE_RIGHT:
+ if (m_iZoomFactor == 1 || !m_Image[m_iCurrentPic].m_bCanMoveHorizontally)
+ ShowNext();
+ else
+ Move(PICTURE_MOVE_AMOUNT, 0);
+ break;
+
+ case ACTION_MOVE_LEFT:
+ if (m_iZoomFactor == 1 || !m_Image[m_iCurrentPic].m_bCanMoveHorizontally)
+ ShowPrevious();
+ else
+ Move( -PICTURE_MOVE_AMOUNT, 0);
+ break;
+
+ case ACTION_MOVE_DOWN:
+ Move(0, PICTURE_MOVE_AMOUNT);
+ break;
+
+ case ACTION_MOVE_UP:
+ Move(0, -PICTURE_MOVE_AMOUNT);
+ break;
+
+ case ACTION_PAUSE:
+ case ACTION_PLAYER_PLAY:
+ if (m_slides.size() == 0)
+ break;
+ if (m_slides.at(m_iCurrentSlide)->IsVideo())
+ {
+ if (!m_bPlayingVideo)
+ {
+ if (m_bSlideShow)
+ {
+ SetDirection(1);
+ m_bPause = false;
+ }
+ PlayVideo();
+ }
+ }
+ else if (!m_bSlideShow || m_bPause)
+ {
+ m_bSlideShow = true;
+ m_bPause = false;
+ SetDirection(1);
+ if (m_Image[m_iCurrentPic].IsLoaded())
+ {
+ CSlideShowPic::DISPLAY_EFFECT effect = GetDisplayEffect(m_iCurrentSlide);
+ if (m_Image[m_iCurrentPic].DisplayEffectNeedChange(effect))
+ m_Image[m_iCurrentPic].Reset(effect);
+ }
+ AnnouncePlayerPlay(m_slides.at(m_iCurrentSlide));
+ }
+ else if (action.GetID() == ACTION_PAUSE)
+ {
+ m_bPause = true;
+ AnnouncePlayerPause(m_slides.at(m_iCurrentSlide));
+ }
+ break;
+
+ case ACTION_ZOOM_OUT:
+ Zoom(m_iZoomFactor - 1);
+ break;
+
+ case ACTION_ZOOM_IN:
+ Zoom(m_iZoomFactor + 1);
+ break;
+
+ case ACTION_GESTURE_SWIPE_UP:
+ case ACTION_GESTURE_SWIPE_DOWN:
+ if (m_iZoomFactor == 1 || !m_Image[m_iCurrentPic].m_bCanMoveVertically)
+ {
+ bool swipeOnLeft = action.GetAmount() < CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth() / 2.0f;
+ bool swipeUp = action.GetID() == ACTION_GESTURE_SWIPE_UP;
+ if (swipeUp == swipeOnLeft)
+ Rotate(90.0f);
+ else
+ Rotate(-90.0f);
+ }
+ break;
+
+ case ACTION_ROTATE_PICTURE_CW:
+ Rotate(90.0f);
+ break;
+
+ case ACTION_ROTATE_PICTURE_CCW:
+ Rotate(-90.0f);
+ break;
+
+ case ACTION_ZOOM_LEVEL_NORMAL:
+ case ACTION_ZOOM_LEVEL_1:
+ case ACTION_ZOOM_LEVEL_2:
+ case ACTION_ZOOM_LEVEL_3:
+ case ACTION_ZOOM_LEVEL_4:
+ case ACTION_ZOOM_LEVEL_5:
+ case ACTION_ZOOM_LEVEL_6:
+ case ACTION_ZOOM_LEVEL_7:
+ case ACTION_ZOOM_LEVEL_8:
+ case ACTION_ZOOM_LEVEL_9:
+ Zoom((action.GetID() - ACTION_ZOOM_LEVEL_NORMAL) + 1);
+ break;
+
+ case ACTION_ANALOG_MOVE:
+ // this action is used and works, when CAction object provides both x and y coordinates
+ Move(action.GetAmount()*PICTURE_MOVE_AMOUNT_ANALOG, -action.GetAmount(1)*PICTURE_MOVE_AMOUNT_ANALOG);
+ break;
+ case ACTION_ANALOG_MOVE_X_LEFT:
+ Move(-action.GetAmount()*PICTURE_MOVE_AMOUNT_ANALOG, 0.0f);
+ break;
+ case ACTION_ANALOG_MOVE_X_RIGHT:
+ Move(action.GetAmount()*PICTURE_MOVE_AMOUNT_ANALOG, 0.0f);
+ break;
+ case ACTION_ANALOG_MOVE_Y_UP:
+ Move(0.0f, -action.GetAmount()*PICTURE_MOVE_AMOUNT_ANALOG);
+ break;
+ case ACTION_ANALOG_MOVE_Y_DOWN:
+ Move(0.0f, action.GetAmount()*PICTURE_MOVE_AMOUNT_ANALOG);
+ break;
+
+ default:
+ return CGUIDialog::OnAction(action);
+ }
+ return true;
+}
+
+void CGUIWindowSlideShow::RenderErrorMessage()
+{
+ if (!m_bErrorMessage)
+ return ;
+
+ const CGUIControl *control = GetControl(LABEL_ROW1);
+ if (NULL == control || control->GetControlType() != CGUIControl::GUICONTROL_LABEL)
+ {
+ return;
+ }
+
+ CGUIFont *pFont = static_cast<const CGUILabelControl*>(control)->GetLabelInfo().font;
+ CGUITextLayout::DrawText(pFont, 0.5f*CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(), 0.5f*CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight(), 0xffffffff, 0, g_localizeStrings.Get(747), XBFONT_CENTER_X | XBFONT_CENTER_Y);
+}
+
+bool CGUIWindowSlideShow::OnMessage(CGUIMessage& message)
+{
+ switch ( message.GetMessage() )
+ {
+ case GUI_MSG_WINDOW_INIT:
+ {
+ m_Resolution = (RESOLUTION) CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_PICTURES_DISPLAYRESOLUTION);
+
+ //FIXME: Use GUI resolution for now
+ if (false /*m_Resolution != CDisplaySettings::GetInstance().GetCurrentResolution() && m_Resolution != INVALID && m_Resolution!=AUTORES*/)
+ CServiceBroker::GetWinSystem()->GetGfxContext().SetVideoResolution(m_Resolution, false);
+ else
+ m_Resolution = CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution();
+
+ CGUIDialog::OnMessage(message);
+
+ // turn off slideshow if we only have 1 image
+ if (m_slides.size() <= 1)
+ m_bSlideShow = false;
+
+ return true;
+ }
+ break;
+
+ case GUI_MSG_SHOW_PICTURE:
+ {
+ const std::string& strFile = message.GetStringParam();
+ Reset();
+ CFileItem item(strFile, false);
+ Add(&item);
+ RunSlideShow("", false, false, true, "", false);
+ }
+ break;
+
+ case GUI_MSG_START_SLIDESHOW:
+ {
+ const std::string& strFolder = message.GetStringParam();
+ unsigned int iParams = message.GetParam1();
+ const std::string& beginSlidePath = message.GetStringParam(1);
+ //decode params
+ bool bRecursive = false;
+ bool bRandom = false;
+ bool bNotRandom = false;
+ bool bPause = false;
+ if (iParams > 0)
+ {
+ if ((iParams & 1) == 1)
+ bRecursive = true;
+ if ((iParams & 2) == 2)
+ bRandom = true;
+ if ((iParams & 4) == 4)
+ bNotRandom = true;
+ if ((iParams & 8) == 8)
+ bPause = true;
+ }
+ RunSlideShow(strFolder, bRecursive, bRandom, bNotRandom, beginSlidePath, !bPause);
+ }
+ break;
+
+ case GUI_MSG_PLAYLISTPLAYER_STOPPED:
+ {
+ }
+ break;
+
+ case GUI_MSG_PLAYBACK_STOPPED:
+ {
+ if (m_bPlayingVideo)
+ {
+ m_bPlayingVideo = false;
+ m_iVideoSlide = -1;
+ if (m_bSlideShow)
+ m_bPause = true;
+ }
+ }
+ break;
+
+ case GUI_MSG_PLAYBACK_ENDED:
+ {
+ if (m_bPlayingVideo)
+ {
+ m_bPlayingVideo = false;
+ m_iVideoSlide = -1;
+ if (m_bSlideShow)
+ {
+ m_bPause = false;
+ if (m_iCurrentSlide == m_iNextSlide)
+ break;
+ m_Image[m_iCurrentPic].Close();
+ m_iCurrentPic = 1 - m_iCurrentPic;
+ m_iCurrentSlide = m_iNextSlide;
+ m_iNextSlide = GetNextSlide();
+ AnnouncePlayerPlay(m_slides.at(m_iCurrentSlide));
+ m_iZoomFactor = 1;
+ m_fZoom = 1.0f;
+ m_fRotate = 0.0f;
+ }
+ }
+ }
+ break;
+ }
+ return CGUIDialog::OnMessage(message);
+}
+
+void CGUIWindowSlideShow::RenderPause()
+{ // display the pause icon
+ if (m_bPause)
+ {
+ SET_CONTROL_VISIBLE(CONTROL_PAUSE);
+ }
+ else
+ {
+ SET_CONTROL_HIDDEN(CONTROL_PAUSE);
+ }
+}
+
+void CGUIWindowSlideShow::Rotate(float fAngle, bool immediate /* = false */)
+{
+ if (m_Image[m_iCurrentPic].DrawNextImage())
+ return;
+
+ m_fRotate += fAngle;
+
+ m_Image[m_iCurrentPic].Rotate(fAngle, immediate);
+}
+
+void CGUIWindowSlideShow::Zoom(int iZoom)
+{
+ if (iZoom > MAX_ZOOM_FACTOR || iZoom < 1)
+ return;
+
+ ZoomRelative(zoomamount[iZoom - 1]);
+}
+
+void CGUIWindowSlideShow::ZoomRelative(float fZoom, bool immediate /* = false */)
+{
+ if (fZoom < zoomamount[0])
+ fZoom = zoomamount[0];
+ else if (fZoom > zoomamount[MAX_ZOOM_FACTOR - 1])
+ fZoom = zoomamount[MAX_ZOOM_FACTOR - 1];
+
+ if (m_Image[m_iCurrentPic].DrawNextImage())
+ return;
+
+ m_fZoom = fZoom;
+
+ // find the nearest zoom factor
+ for (unsigned int i = 1; i < MAX_ZOOM_FACTOR; i++)
+ {
+ if (m_fZoom > zoomamount[i])
+ continue;
+
+ if (fabs(m_fZoom - zoomamount[i - 1]) < fabs(m_fZoom - zoomamount[i]))
+ m_iZoomFactor = i;
+ else
+ m_iZoomFactor = i + 1;
+
+ break;
+ }
+
+ m_Image[m_iCurrentPic].Zoom(m_fZoom, immediate);
+}
+
+void CGUIWindowSlideShow::Move(float fX, float fY)
+{
+ if (m_Image[m_iCurrentPic].IsLoaded() && m_Image[m_iCurrentPic].GetZoom() > 1)
+ { // we move in the opposite direction, due to the fact we are moving
+ // the viewing window, not the picture.
+ m_Image[m_iCurrentPic].Move( -fX, -fY);
+ }
+}
+
+bool CGUIWindowSlideShow::PlayVideo()
+{
+ CFileItemPtr item = m_slides.at(m_iCurrentSlide);
+ if (!item || !item->IsVideo())
+ return false;
+ CLog::Log(LOGDEBUG, "Playing current video slide {}", item->GetPath());
+ m_bPlayingVideo = true;
+ m_iVideoSlide = m_iCurrentSlide;
+ bool ret = g_application.PlayFile(*item, "");
+ if (ret == true)
+ return true;
+ else
+ {
+ CLog::Log(LOGINFO, "set video {} unplayable", item->GetPath());
+ item->SetProperty("unplayable", true);
+ }
+ m_bPlayingVideo = false;
+ m_iVideoSlide = -1;
+ return false;
+}
+
+CSlideShowPic::DISPLAY_EFFECT CGUIWindowSlideShow::GetDisplayEffect(int iSlideNumber) const
+{
+ if (m_bSlideShow && !m_bPause && !m_slides.at(iSlideNumber)->IsVideo())
+ return CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_SLIDESHOW_DISPLAYEFFECTS) ? CSlideShowPic::EFFECT_RANDOM : CSlideShowPic::EFFECT_NONE;
+ else
+ return CSlideShowPic::EFFECT_NO_TIMEOUT;
+}
+
+void CGUIWindowSlideShow::OnLoadPic(int iPic,
+ int iSlideNumber,
+ const std::string& strFileName,
+ std::unique_ptr<CTexture> pTexture,
+ bool bFullSize)
+{
+ if (pTexture)
+ {
+ // set the pic's texture + size etc.
+ if (iSlideNumber >= static_cast<int>(m_slides.size()) || GetPicturePath(m_slides.at(iSlideNumber).get()) != strFileName)
+ { // throw this away - we must have cleared the slideshow while we were still loading
+ return;
+ }
+ CLog::Log(LOGDEBUG, "Finished background loading slot {}, {}: {}", iPic, iSlideNumber,
+ m_slides.at(iSlideNumber)->GetPath());
+ m_Image[iPic].SetOriginalSize(pTexture->GetOriginalWidth(), pTexture->GetOriginalHeight(), bFullSize);
+ m_Image[iPic].SetTexture(iSlideNumber, std::move(pTexture), GetDisplayEffect(iSlideNumber));
+
+ m_Image[iPic].m_bIsComic = false;
+ if (URIUtils::IsInRAR(m_slides.at(m_iCurrentSlide)->GetPath()) || URIUtils::IsInZIP(m_slides.at(m_iCurrentSlide)->GetPath())) // move to top for cbr/cbz
+ {
+ CURL url(m_slides.at(m_iCurrentSlide)->GetPath());
+ const std::string& strHostName = url.GetHostName();
+ if (URIUtils::HasExtension(strHostName, ".cbr|.cbz"))
+ {
+ m_Image[iPic].m_bIsComic = true;
+ m_Image[iPic].Move((float)m_Image[iPic].GetOriginalWidth(),(float)m_Image[iPic].GetOriginalHeight());
+ }
+ }
+ }
+ else if (iSlideNumber >= static_cast<int>(m_slides.size()) || GetPicturePath(m_slides.at(iSlideNumber).get()) != strFileName)
+ { // Failed to load image. and not match values calling LoadPic, then something is changed, ignore.
+ CLog::Log(LOGDEBUG,
+ "CGUIWindowSlideShow::OnLoadPic({}, {}, {}) on failure not match current state (cur "
+ "{}, next {}, curpic {}, pic[0, 1].slidenumber={}, {}, {})",
+ iPic, iSlideNumber, strFileName, m_iCurrentSlide, m_iNextSlide, m_iCurrentPic,
+ m_Image[0].SlideNumber(), m_Image[1].SlideNumber(),
+ iSlideNumber >= static_cast<int>(m_slides.size())
+ ? ""
+ : m_slides.at(iSlideNumber)->GetPath());
+ }
+ else
+ { // Failed to load image. What should be done??
+ // We should wait for the current pic to finish rendering, then transition it out,
+ // release the texture, and try and reload this pic from scratch
+ m_bErrorMessage = true;
+ }
+ MarkDirtyRegion();
+}
+
+void CGUIWindowSlideShow::Shuffle()
+{
+ KODI::UTILS::RandomShuffle(m_slides.begin(), m_slides.end());
+ m_iCurrentSlide = 0;
+ m_iNextSlide = GetNextSlide();
+ m_bShuffled = true;
+
+ AnnouncePropertyChanged("shuffled", true);
+}
+
+int CGUIWindowSlideShow::NumSlides() const
+{
+ return m_slides.size();
+}
+
+int CGUIWindowSlideShow::CurrentSlide() const
+{
+ return m_iCurrentSlide + 1;
+}
+
+void CGUIWindowSlideShow::AddFromPath(const std::string &strPath,
+ bool bRecursive,
+ SortBy method, SortOrder order, SortAttribute sortAttributes,
+ const std::string &strExtensions)
+{
+ if (strPath!="")
+ {
+ // reset the slideshow
+ Reset();
+ if (bRecursive)
+ {
+ path_set recursivePaths;
+ AddItems(strPath, &recursivePaths, method, order, sortAttributes);
+ }
+ else
+ AddItems(strPath, NULL, method, order, sortAttributes);
+ }
+}
+
+void CGUIWindowSlideShow::RunSlideShow(const std::string &strPath,
+ bool bRecursive /* = false */, bool bRandom /* = false */,
+ bool bNotRandom /* = false */, const std::string &beginSlidePath /* = "" */,
+ bool startSlideShow /* = true */, SortBy method /* = SortByLabel */,
+ SortOrder order /* = SortOrderAscending */, SortAttribute sortAttributes /* = SortAttributeNone */,
+ const std::string &strExtensions)
+{
+ // stop any video
+ const auto& components = CServiceBroker::GetAppComponents();
+ const auto appPlayer = components.GetComponent<CApplicationPlayer>();
+ if (appPlayer->IsPlayingVideo())
+ g_application.StopPlaying();
+
+ AddFromPath(strPath, bRecursive, method, order, sortAttributes, strExtensions);
+
+ if (!NumSlides())
+ return;
+
+ // mutually exclusive options
+ // if both are set, clear both and use the gui setting
+ if (bRandom && bNotRandom)
+ bRandom = bNotRandom = false;
+
+ // NotRandom overrides the window setting
+ if ((!bNotRandom && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_SLIDESHOW_SHUFFLE)) || bRandom)
+ Shuffle();
+
+ if (!beginSlidePath.empty())
+ Select(beginSlidePath);
+
+ if (startSlideShow)
+ StartSlideShow();
+ else
+ {
+ CVariant param;
+ param["player"]["speed"] = 0;
+ param["player"]["playerid"] = PLAYLIST::TYPE_PICTURE;
+ CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnPlay",
+ GetCurrentSlide(), param);
+ }
+
+ CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_SLIDESHOW);
+}
+
+void CGUIWindowSlideShow::AddItems(const std::string &strPath, path_set *recursivePaths, SortBy method, SortOrder order, SortAttribute sortAttributes)
+{
+ // check whether we've already added this path
+ if (recursivePaths)
+ {
+ std::string path(strPath);
+ URIUtils::RemoveSlashAtEnd(path);
+ if (recursivePaths->find(path) != recursivePaths->end())
+ return;
+ recursivePaths->insert(path);
+ }
+
+ CFileItemList items;
+ CGUIViewStateWindowPictures viewState(items);
+
+ // fetch directory and sort accordingly
+ if (!CDirectory::GetDirectory(strPath, items, viewState.GetExtensions(), DIR_FLAG_NO_FILE_DIRS))
+ return;
+
+ items.Sort(method, order, sortAttributes);
+
+ // need to go into all subdirs
+ for (int i = 0; i < items.Size(); i++)
+ {
+ CFileItemPtr item = items[i];
+ if (item->m_bIsFolder && recursivePaths)
+ {
+ AddItems(item->GetPath(), recursivePaths);
+ }
+ else if (!item->m_bIsFolder && !URIUtils::IsRAR(item->GetPath()) && !URIUtils::IsZIP(item->GetPath()))
+ { // add to the slideshow
+ Add(item.get());
+ }
+ }
+}
+
+void CGUIWindowSlideShow::GetCheckedSize(float width, float height, int &maxWidth, int &maxHeight)
+{
+ maxWidth = CServiceBroker::GetRenderSystem()->GetMaxTextureSize();
+ maxHeight = CServiceBroker::GetRenderSystem()->GetMaxTextureSize();
+}
+
+std::string CGUIWindowSlideShow::GetPicturePath(CFileItem *item)
+{
+ bool isVideo = item->IsVideo();
+ std::string picturePath = item->GetDynPath();
+ if (isVideo)
+ {
+ picturePath = item->GetArt("thumb");
+ if (picturePath.empty() && !item->HasProperty("nothumb"))
+ {
+ CPictureThumbLoader thumbLoader;
+ thumbLoader.LoadItem(item);
+ picturePath = item->GetArt("thumb");
+ if (picturePath.empty())
+ item->SetProperty("nothumb", true);
+ }
+ }
+ return picturePath;
+}
+
+
+void CGUIWindowSlideShow::RunSlideShow(const std::vector<std::string>& paths, int start /* = 0*/)
+{
+ auto dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW);
+ if (dialog)
+ {
+ std::vector<CFileItemPtr> items;
+ items.reserve(paths.size());
+ for (const auto& path : paths)
+ items.push_back(std::make_shared<CFileItem>(CTextureUtils::GetWrappedImageURL(path), false));
+
+ dialog->Reset();
+ dialog->m_bPause = true;
+ dialog->m_bSlideShow = false;
+ dialog->m_iDirection = 1;
+ dialog->m_iCurrentSlide = start;
+ dialog->m_iNextSlide = (start + 1) % items.size();
+ dialog->m_slides = std::move(items);
+ dialog->Open();
+ }
+}
diff --git a/xbmc/pictures/GUIWindowSlideShow.h b/xbmc/pictures/GUIWindowSlideShow.h
new file mode 100644
index 0000000..6955fb4
--- /dev/null
+++ b/xbmc/pictures/GUIWindowSlideShow.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "SlideShowPicture.h"
+#include "guilib/GUIDialog.h"
+#include "threads/Event.h"
+#include "threads/Thread.h"
+#include "utils/SortUtils.h"
+
+#include <memory>
+#include <set>
+
+class CFileItemList;
+class CVariant;
+
+class CGUIWindowSlideShow;
+
+class CBackgroundPicLoader : public CThread
+{
+public:
+ CBackgroundPicLoader();
+ ~CBackgroundPicLoader() override;
+
+ void Create(CGUIWindowSlideShow *pCallback);
+ void LoadPic(int iPic, int iSlideNumber, const std::string &strFileName, const int maxWidth, const int maxHeight);
+ bool IsLoading() { return m_isLoading; }
+ int SlideNumber() const { return m_iSlideNumber; }
+ int Pic() const { return m_iPic; }
+
+private:
+ void Process() override;
+ int m_iPic;
+ int m_iSlideNumber;
+ std::string m_strFileName;
+ int m_maxWidth;
+ int m_maxHeight;
+
+ CEvent m_loadPic;
+ bool m_isLoading;
+
+ CGUIWindowSlideShow *m_pCallback;
+};
+
+class CGUIWindowSlideShow : public CGUIDialog
+{
+public:
+ CGUIWindowSlideShow(void);
+ ~CGUIWindowSlideShow() override = default;
+
+ bool OnMessage(CGUIMessage& message) override;
+ EVENT_RESULT OnMouseEvent(const CPoint &point, const CMouseEvent &event) override;
+ bool OnAction(const CAction &action) override;
+ void Render() override;
+ void RenderEx() override;
+ void Process(unsigned int currentTime, CDirtyRegionList &regions) override;
+ void OnDeinitWindow(int nextWindowID) override;
+
+ void Reset();
+ void Add(const CFileItem *picture);
+ bool IsPlaying() const;
+ void Select(const std::string& strPicture);
+ void GetSlideShowContents(CFileItemList &list);
+ std::shared_ptr<const CFileItem> GetCurrentSlide();
+ void RunSlideShow(const std::string &strPath, bool bRecursive = false,
+ bool bRandom = false, bool bNotRandom = false,
+ const std::string &beginSlidePath="", bool startSlideShow = true,
+ SortBy method = SortByLabel,
+ SortOrder order = SortOrderAscending,
+ SortAttribute sortAttributes = SortAttributeNone,
+ const std::string &strExtensions="");
+ void AddFromPath(const std::string &strPath, bool bRecursive,
+ SortBy method = SortByLabel,
+ SortOrder order = SortOrderAscending,
+ SortAttribute sortAttributes = SortAttributeNone,
+ const std::string &strExtensions="");
+ void StartSlideShow();
+ bool InSlideShow() const;
+ void OnLoadPic(int iPic,
+ int iSlideNumber,
+ const std::string& strFileName,
+ std::unique_ptr<CTexture> pTexture,
+ bool bFullSize);
+ int NumSlides() const;
+ int CurrentSlide() const;
+ void Shuffle();
+ bool IsPaused() const { return m_bPause; }
+ bool IsShuffled() const { return m_bShuffled; }
+ int GetDirection() const { return m_iDirection; }
+
+ static void RunSlideShow(const std::vector<std::string>& paths, int start = 0);
+
+private:
+ void ShowNext();
+ void ShowPrevious();
+ void SetDirection(int direction); // -1: rewind, 1: forward
+
+ typedef std::set<std::string> path_set; // set to track which paths we're adding
+ void AddItems(const std::string &strPath, path_set *recursivePaths,
+ SortBy method = SortByLabel,
+ SortOrder order = SortOrderAscending,
+ SortAttribute sortAttributes = SortAttributeNone);
+ bool PlayVideo();
+ CSlideShowPic::DISPLAY_EFFECT GetDisplayEffect(int iSlideNumber) const;
+ void RenderPause();
+ void RenderErrorMessage();
+ void Rotate(float fAngle, bool immediate = false);
+ void Zoom(int iZoom);
+ void ZoomRelative(float fZoom, bool immediate = false);
+ void Move(float fX, float fY);
+ void GetCheckedSize(float width, float height, int &maxWidth, int &maxHeight);
+ std::string GetPicturePath(CFileItem *item);
+ int GetNextSlide();
+
+ void AnnouncePlayerPlay(const CFileItemPtr& item);
+ void AnnouncePlayerPause(const CFileItemPtr& item);
+ void AnnouncePlayerStop(const CFileItemPtr& item);
+ void AnnouncePlaylistClear();
+ void AnnouncePlaylistAdd(const CFileItemPtr& item, int pos);
+ void AnnouncePropertyChanged(const std::string &strProperty, const CVariant &value);
+
+ int m_iCurrentSlide;
+ int m_iNextSlide;
+ int m_iDirection;
+ float m_fRotate;
+ float m_fInitialRotate;
+ int m_iZoomFactor;
+ float m_fZoom;
+ float m_fInitialZoom;
+
+ bool m_bShuffled;
+ bool m_bSlideShow;
+ bool m_bPause;
+ bool m_bPlayingVideo;
+ int m_iVideoSlide = -1;
+ bool m_bErrorMessage;
+
+ std::vector<CFileItemPtr> m_slides;
+
+ CSlideShowPic m_Image[2];
+
+ int m_iCurrentPic;
+ // background loader
+ std::unique_ptr<CBackgroundPicLoader> m_pBackgroundLoader;
+ int m_iLastFailedNextSlide;
+ bool m_bLoadNextPic;
+ RESOLUTION m_Resolution;
+ CPoint m_firstGesturePoint;
+};
diff --git a/xbmc/pictures/IptcParse.cpp b/xbmc/pictures/IptcParse.cpp
new file mode 100644
index 0000000..dc66f27
--- /dev/null
+++ b/xbmc/pictures/IptcParse.cpp
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+//--------------------------------------------------------------------------
+// Module to pull IPTC information out of various types of digital images.
+//--------------------------------------------------------------------------
+
+//--------------------------------------------------------------------------
+// Process IPTC data.
+//--------------------------------------------------------------------------
+
+#include "IptcParse.h"
+
+#include "ExifParse.h"
+
+#ifdef TARGET_WINDOWS
+#include <windows.h>
+#else
+#include <string.h>
+#endif
+
+#include <stdio.h>
+
+#ifndef min
+#define min(a,b) (a)>(b)?(b):(a)
+#endif
+
+// Supported IPTC entry types
+#define IPTC_RECORD_VERSION 0x00
+#define IPTC_SUPLEMENTAL_CATEGORIES 0x14
+#define IPTC_KEYWORDS 0x19
+#define IPTC_CAPTION 0x78
+#define IPTC_AUTHOR 0x7A
+#define IPTC_HEADLINE 0x69
+#define IPTC_SPECIAL_INSTRUCTIONS 0x28
+#define IPTC_CATEGORY 0x0F
+#define IPTC_BYLINE 0x50
+#define IPTC_BYLINE_TITLE 0x55
+#define IPTC_CREDIT 0x6E
+#define IPTC_SOURCE 0x73
+#define IPTC_COPYRIGHT_NOTICE 0x74
+#define IPTC_OBJECT_NAME 0x05
+#define IPTC_CITY 0x5A
+#define IPTC_STATE 0x5F
+#define IPTC_COUNTRY 0x65
+#define IPTC_TRANSMISSION_REFERENCE 0x67
+#define IPTC_DATE 0x37
+#define IPTC_URGENCY 0x0A
+#define IPTC_COUNTRY_CODE 0x64
+#define IPTC_REFERENCE_SERVICE 0x2D
+#define IPTC_TIME_CREATED 0x3C
+#define IPTC_SUB_LOCATION 0x5C
+#define IPTC_IMAGE_TYPE 0x82
+
+
+//--------------------------------------------------------------------------
+// Process IPTC marker. Return FALSE if unable to process marker.
+//
+// IPTC block consists of:
+// - Marker: 1 byte (0xED)
+// - Block length: 2 bytes
+// - IPTC Signature: 14 bytes ("Photoshop 3.0\0")
+// - 8BIM Signature 4 bytes ("8BIM")
+// - IPTC Block start 2 bytes (0x04, 0x04)
+// - IPTC Header length 1 byte
+// - IPTC header Header is padded to even length, counting the length byte
+// - Length 4 bytes
+// - IPTC Data which consists of a number of entries, each of which has the following format:
+// - Signature 2 bytes (0x1C02)
+// - Entry type 1 byte (for defined entry types, see #defines above)
+// - entry length 2 bytes
+// - entry data 'entry length' bytes
+//
+//--------------------------------------------------------------------------
+bool CIptcParse::Process (const unsigned char* const Data, const unsigned short itemlen, IPTCInfo_t *info)
+{
+ if (!info) return false;
+
+ const char IptcSignature1[] = "Photoshop 3.0";
+ const char IptcSignature2[] = "8BIM";
+ const char IptcSignature3[] = {0x04, 0x04};
+
+ // Check IPTC signatures
+ const char* pos = (const char*)(Data + sizeof(short)); // position data pointer after length field
+ const char* maxpos = (const char*)(Data+itemlen);
+ unsigned char headerLen = 0;
+ unsigned char dataLen = 0;
+ memset(info, 0, sizeof(IPTCInfo_t));
+
+ if (itemlen < 25) return false;
+
+ if (memcmp(pos, IptcSignature1, strlen(IptcSignature1)-1) != 0) return false;
+ pos += sizeof(IptcSignature1); // move data pointer to the next field
+
+ if (memcmp(pos, IptcSignature2, strlen(IptcSignature2)-1) != 0) return false;
+ pos += sizeof(IptcSignature2)-1; // move data pointer to the next field
+
+ while (memcmp(pos, IptcSignature3, sizeof(IptcSignature3)) != 0) { // loop on valid Photoshop blocks
+
+ pos += sizeof(IptcSignature3); // move data pointer to the Header Length
+ // Skip header
+ headerLen = *pos; // get header length and move data pointer to the next field
+ pos += (headerLen & 0xfe) + 2; // move data pointer to the next field (Header is padded to even length, counting the length byte)
+
+ pos += 3; // move data pointer to length, assume only one byte, TODO: use all 4 bytes
+
+ dataLen = *pos++;
+ pos += dataLen; // skip data section
+
+ if (memcmp(pos, IptcSignature2, sizeof(IptcSignature2) - 1) != 0) return false;
+ pos += sizeof(IptcSignature2) - 1; // move data pointer to the next field
+ }
+
+ pos += sizeof(IptcSignature3); // move data pointer to the next field
+ if (pos >= maxpos) return false;
+
+ // IPTC section found
+
+ // Skip header
+ headerLen = *pos++; // get header length and move data pointer to the next field
+ pos += headerLen + 1 - (headerLen % 2); // move data pointer to the next field (Header is padded to even length, counting the length byte)
+
+ if (pos + 4 >= maxpos) return false;
+
+ pos += 4; // move data pointer to the next field
+
+ // Now read IPTC data
+ while (pos < (const char*)(Data + itemlen-5))
+ {
+ if (pos + 5 > maxpos) return false;
+
+ short signature = (*pos << 8) + (*(pos+1));
+
+ pos += 2;
+ if (signature != 0x1C01 && signature != 0x1C02)
+ break;
+
+ unsigned char type = *pos++;
+ unsigned short length = (*pos << 8) + (*(pos+1));
+ pos += 2; // Skip tag length
+
+ if (pos + length > maxpos) return false;
+
+ // Process tag here
+ char *tag = NULL;
+ if (signature == 0x1C02)
+ {
+ switch (type)
+ {
+ case IPTC_RECORD_VERSION: tag = info->RecordVersion; break;
+ case IPTC_SUPLEMENTAL_CATEGORIES: tag = info->SupplementalCategories; break;
+ case IPTC_KEYWORDS: tag = info->Keywords; break;
+ case IPTC_CAPTION: tag = info->Caption; break;
+ case IPTC_AUTHOR: tag = info->Author; break;
+ case IPTC_HEADLINE: tag = info->Headline; break;
+ case IPTC_SPECIAL_INSTRUCTIONS: tag = info->SpecialInstructions; break;
+ case IPTC_CATEGORY: tag = info->Category; break;
+ case IPTC_BYLINE: tag = info->Byline; break;
+ case IPTC_BYLINE_TITLE: tag = info->BylineTitle; break;
+ case IPTC_CREDIT: tag = info->Credit; break;
+ case IPTC_SOURCE: tag = info->Source; break;
+ case IPTC_COPYRIGHT_NOTICE: tag = info->CopyrightNotice; break;
+ case IPTC_OBJECT_NAME: tag = info->ObjectName; break;
+ case IPTC_CITY: tag = info->City; break;
+ case IPTC_STATE: tag = info->State; break;
+ case IPTC_COUNTRY: tag = info->Country; break;
+ case IPTC_TRANSMISSION_REFERENCE: tag = info->TransmissionReference; break;
+ case IPTC_DATE: tag = info->Date; break;
+ case IPTC_URGENCY: tag = info->Urgency; break;
+ case IPTC_REFERENCE_SERVICE: tag = info->ReferenceService; break;
+ case IPTC_COUNTRY_CODE: tag = info->CountryCode; break;
+ case IPTC_TIME_CREATED: tag = info->TimeCreated; break;
+ case IPTC_SUB_LOCATION: tag = info->SubLocation; break;
+ case IPTC_IMAGE_TYPE: tag = info->ImageType; break;
+ default:
+ printf("IptcParse: Unrecognised IPTC tag: 0x%02x", type);
+ break;
+ }
+ }
+
+ if (tag)
+ {
+ if (type != IPTC_KEYWORDS || *tag == 0)
+ {
+ strncpy(tag, pos, min(length, MAX_IPTC_STRING - 1));
+ tag[min(length, MAX_IPTC_STRING - 1)] = 0;
+ }
+ else if (type == IPTC_KEYWORDS)
+ {
+ // there may be multiple keywords - lets join them
+ size_t maxLen = MAX_IPTC_STRING - strlen(tag);
+ if (maxLen > 2)
+ {
+ strcat(tag, ", ");
+ strncat(tag, pos, min(length, maxLen - 3));
+ }
+ }
+/* if (id == SLIDESHOW_IPTC_CAPTION)
+ {
+ CExifParse::FixComment(m_IptcInfo[id]); // Ensure comment is printable
+ }*/
+ }
+ pos += length;
+ }
+ return true;
+}
+
diff --git a/xbmc/pictures/IptcParse.h b/xbmc/pictures/IptcParse.h
new file mode 100644
index 0000000..ba00135
--- /dev/null
+++ b/xbmc/pictures/IptcParse.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include "libexif.h"
+
+class CIptcParse
+{
+ public:
+ static bool Process(const unsigned char* const Data, const unsigned short length, IPTCInfo_t *info);
+};
+
diff --git a/xbmc/pictures/JpegParse.cpp b/xbmc/pictures/JpegParse.cpp
new file mode 100644
index 0000000..718ac54
--- /dev/null
+++ b/xbmc/pictures/JpegParse.cpp
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+//--------------------------------------------------------------------------
+// This module gathers information about a digital image file. This includes:
+// - File name and path
+// - File size
+// - Resolution (if available)
+// - IPTC information (if available)
+// - EXIF information (if available)
+// All gathered information is stored in a vector of 'description' and 'value'
+// pairs (where both description and value fields are of CStdString types).
+//--------------------------------------------------------------------------
+
+#include "JpegParse.h"
+
+#include "filesystem/File.h"
+
+#ifdef TARGET_WINDOWS
+#include <windows.h>
+#else
+#include <memory.h>
+#include <cstring>
+typedef unsigned char BYTE;
+#endif
+
+#ifndef min
+#define min(a,b) (a)>(b)?(b):(a)
+#endif
+
+using namespace XFILE;
+
+//--------------------------------------------------------------------------
+#define JPEG_PARSE_STRING_ID_BASE 21500
+enum {
+ ProcessUnknown = JPEG_PARSE_STRING_ID_BASE,
+ ProcessSof0,
+ ProcessSof1,
+ ProcessSof2,
+ ProcessSof3,
+ ProcessSof5,
+ ProcessSof6,
+ ProcessSof7,
+ ProcessSof9,
+ ProcessSof10,
+ ProcessSof11,
+ ProcessSof13,
+ ProcessSof14,
+ ProcessSof15,
+};
+
+
+
+
+//--------------------------------------------------------------------------
+// Constructor
+//--------------------------------------------------------------------------
+CJpegParse::CJpegParse():
+ m_SectionBuffer(NULL)
+{
+ memset(&m_ExifInfo, 0, sizeof(m_ExifInfo));
+ memset(&m_IPTCInfo, 0, sizeof(m_IPTCInfo));
+}
+
+//--------------------------------------------------------------------------
+// Process a SOFn marker. This is useful for the image dimensions
+//--------------------------------------------------------------------------
+void CJpegParse::ProcessSOFn (void)
+{
+ m_ExifInfo.Height = CExifParse::Get16(m_SectionBuffer+3);
+ m_ExifInfo.Width = CExifParse::Get16(m_SectionBuffer+5);
+
+ unsigned char num_components = m_SectionBuffer[7];
+ if (num_components != 3)
+ {
+ m_ExifInfo.IsColor = 0;
+ }
+ else
+ {
+ m_ExifInfo.IsColor = 1;
+ }
+}
+
+
+//--------------------------------------------------------------------------
+// Read a section from a JPEG file. Note that this function allocates memory.
+// It must be called in pair with ReleaseSection
+//--------------------------------------------------------------------------
+bool CJpegParse::GetSection (CFile& infile, const unsigned short sectionLength)
+{
+ if (sectionLength < 2)
+ {
+ printf("JpgParse: invalid section length");
+ return false;
+ }
+
+ m_SectionBuffer = new unsigned char[sectionLength];
+ if (m_SectionBuffer == NULL)
+ {
+ printf("JpgParse: could not allocate memory");
+ return false;
+ }
+ // Store first two pre-read bytes.
+ m_SectionBuffer[0] = (unsigned char)(sectionLength >> 8);
+ m_SectionBuffer[1] = (unsigned char)(sectionLength & 0x00FF);
+
+ unsigned int len = (unsigned int)sectionLength;
+
+ size_t bytesRead = infile.Read(m_SectionBuffer+sizeof(sectionLength), len-sizeof(sectionLength));
+ if (bytesRead != sectionLength-sizeof(sectionLength))
+ {
+ printf("JpgParse: premature end of file?");
+ ReleaseSection();
+ return false;
+ }
+ return true;
+}
+
+//--------------------------------------------------------------------------
+// Deallocate memory allocated in GetSection. This function must always
+// be paired by a preceding GetSection call.
+//--------------------------------------------------------------------------
+void CJpegParse::ReleaseSection (void)
+{
+ delete[] m_SectionBuffer;
+ m_SectionBuffer = NULL;
+}
+
+//--------------------------------------------------------------------------
+// Parse the marker stream until SOS or EOI is seen; infile has already been
+// successfully open
+//--------------------------------------------------------------------------
+bool CJpegParse::ExtractInfo (CFile& infile)
+{
+ // Get file marker (two bytes - must be 0xFFD8 for JPEG files
+ BYTE a;
+ size_t bytesRead = infile.Read(&a, sizeof(BYTE));
+ if ((bytesRead != sizeof(BYTE)) || (a != 0xFF))
+ {
+ return false;
+ }
+ bytesRead = infile.Read(&a, sizeof(BYTE));
+ if ((bytesRead != sizeof(BYTE)) || (a != M_SOI))
+ {
+ return false;
+ }
+
+ for(;;)
+ {
+ BYTE marker = 0;
+ for (a=0; a<7; a++) {
+ bytesRead = infile.Read(&marker, sizeof(BYTE));
+ if (marker != 0xFF)
+ break;
+
+ if (a >= 6)
+ {
+ printf("JpgParse: too many padding bytes");
+ return false;
+ }
+ marker = 0;
+ }
+
+ // Read the length of the section.
+ unsigned short itemlen = 0;
+ bytesRead = infile.Read(&itemlen, sizeof(itemlen));
+ itemlen = CExifParse::Get16(&itemlen);
+
+ if ((bytesRead != sizeof(itemlen)) || (itemlen < sizeof(itemlen)))
+ {
+ printf("JpgParse: invalid marker");
+ return false;
+ }
+
+ switch(marker)
+ {
+ case M_SOS: // stop before hitting compressed data
+ return true;
+
+ case M_EOI: // in case it's a tables-only JPEG stream
+ printf("JpgParse: No image in jpeg!");
+ return false;
+ break;
+
+ case M_COM: // Comment section
+ GetSection(infile, itemlen);
+ if (m_SectionBuffer != NULL)
+ {
+ // CExifParse::FixComment(comment); // Ensure comment is printable
+ unsigned short length = min(itemlen - 2, MAX_COMMENT);
+ strncpy(m_ExifInfo.FileComment, (char *)&m_SectionBuffer[2], length);
+ m_ExifInfo.FileComment[length] = '\0';
+ }
+ ReleaseSection();
+ break;
+
+ case M_SOF0:
+ case M_SOF1:
+ case M_SOF2:
+ case M_SOF3:
+ case M_SOF5:
+ case M_SOF6:
+ case M_SOF7:
+ case M_SOF9:
+ case M_SOF10:
+ case M_SOF11:
+ case M_SOF13:
+ case M_SOF14:
+ case M_SOF15:
+ GetSection(infile, itemlen);
+ if ((m_SectionBuffer != NULL) && (itemlen >= 7))
+ {
+ ProcessSOFn();
+ m_ExifInfo.Process = marker;
+ }
+ ReleaseSection();
+ break;
+
+ case M_IPTC:
+ GetSection(infile, itemlen);
+ if (m_SectionBuffer != NULL)
+ {
+ CIptcParse::Process(m_SectionBuffer, itemlen, &m_IPTCInfo);
+ }
+ ReleaseSection();
+ break;
+
+ case M_EXIF:
+ // Seen files from some 'U-lead' software with Vivitar scanner
+ // that uses marker 31 for non exif stuff. Thus make sure
+ // it says 'Exif' in the section before treating it as exif.
+ GetSection(infile, itemlen);
+ if (m_SectionBuffer != NULL)
+ {
+ CExifParse exif;
+ exif.Process(m_SectionBuffer, itemlen, &m_ExifInfo);
+ }
+ ReleaseSection();
+ break;
+
+ case M_JFIF:
+ // Regular jpegs always have this tag, exif images have the exif
+ // marker instead, although ACDsee will write images with both markers.
+ // this program will re-create this marker on absence of exif marker.
+ // hence no need to keep the copy from the file.
+ // fall through to default case
+ default:
+ // Skip any other sections.
+ GetSection(infile, itemlen);
+ ReleaseSection();
+ break;
+ }
+ }
+ return true;
+}
+
+//--------------------------------------------------------------------------
+// Process a file. Check if it is JPEG. Extract exif/iptc info if it is.
+//--------------------------------------------------------------------------
+bool CJpegParse::Process (const char *picFileName)
+{
+ CFile file;
+
+ if (!file.Open(picFileName))
+ return false;
+
+ // File exists and successfully opened. Start processing
+ // Gather all information about the file
+
+/* // Get file name...
+ CStdString tmp, urlFName, path;
+ CURL url(picFileName);
+ url.GetURLWithoutUserDetails(urlFName);
+ CUtil::Split(urlFName, path, tmp);
+ m_JpegInfo[SLIDESHOW_FILE_NAME] = tmp;
+ // ...then path...
+ m_JpegInfo[SLIDESHOW_FILE_PATH] = path;
+
+ // ...then size...
+ __stat64 fileStat;
+ CFile::Stat(picFileName, &fileStat);
+ float fileSize = (float)fileStat.st_size;
+ tmp = "";
+ if (fileSize > 1024)
+ {
+ fileSize /= 1024;
+ tmp = "KB";
+ }
+ if (fileSize > 1024)
+ {
+ fileSize /= 1024;
+ tmp = "MB";
+ }
+ if (fileSize > 1024)
+ {
+ fileSize /= 1024;
+ tmp = "GB";
+ }
+ tmp.Format("%.2f %s", fileSize, tmp);
+ m_JpegInfo[SLIDESHOW_FILE_SIZE] = tmp;
+
+ // ...then date and time...
+ CDateTime date((time_t)fileStat.st_mtime);
+ tmp.Format("%s %s", date.GetAsLocalizedDate(), date.GetAsLocalizedTime());
+ m_JpegInfo[SLIDESHOW_FILE_DATE] = tmp;*/
+
+ bool result = ExtractInfo(file);
+ file.Close();
+ return result;
+}
+
diff --git a/xbmc/pictures/JpegParse.h b/xbmc/pictures/JpegParse.h
new file mode 100644
index 0000000..11d2ca6
--- /dev/null
+++ b/xbmc/pictures/JpegParse.h
@@ -0,0 +1,63 @@
+#pragma once
+
+#include "ExifParse.h"
+#include "IptcParse.h"
+
+#include <stdio.h>
+
+//--------------------------------------------------------------------------
+// JPEG markers consist of one or more 0xFF bytes, followed by a marker
+// code byte (which is not an FF). Here are the marker codes of interest
+// in this application.
+//--------------------------------------------------------------------------
+
+#define M_SOF0 0xC0 // Start Of Frame N
+#define M_SOF1 0xC1 // N indicates which compression process
+#define M_SOF2 0xC2 // Only SOF0-SOF2 are now in common use
+#define M_SOF3 0xC3
+#define M_SOF5 0xC5 // NB: codes C4 and CC are NOT SOF markers
+#define M_SOF6 0xC6
+#define M_SOF7 0xC7
+#define M_SOF9 0xC9
+#define M_SOF10 0xCA
+#define M_SOF11 0xCB
+#define M_SOF13 0xCD
+#define M_SOF14 0xCE
+#define M_SOF15 0xCF
+#define M_SOI 0xD8 // Start Of Image (beginning of datastream)
+#define M_EOI 0xD9 // End Of Image (end of datastream)
+#define M_SOS 0xDA // Start Of Scan (begins compressed data)
+#define M_JFIF 0xE0 // Jfif marker
+#define M_EXIF 0xE1 // Exif marker
+#define M_COM 0xFE // COMment
+#define M_DQT 0xDB
+#define M_DHT 0xC4
+#define M_DRI 0xDD
+#define M_IPTC 0xED // IPTC marker
+
+namespace XFILE
+{
+ class CFile;
+}
+
+
+class CJpegParse
+{
+ public:
+ CJpegParse();
+ ~CJpegParse(void) = default;
+ bool Process(const char *picFileName);
+ const ExifInfo_t* GetExifInfo() const { return &m_ExifInfo; }
+ const IPTCInfo_t* GetIptcInfo() const { return &m_IPTCInfo; }
+
+ private:
+ bool ExtractInfo(XFILE::CFile& infile);
+ bool GetSection(XFILE::CFile& infile, const unsigned short sectionLength);
+ void ReleaseSection(void);
+ void ProcessSOFn(void);
+
+ unsigned char* m_SectionBuffer;
+ ExifInfo_t m_ExifInfo;
+ IPTCInfo_t m_IPTCInfo;
+};
+
diff --git a/xbmc/pictures/Picture.cpp b/xbmc/pictures/Picture.cpp
new file mode 100644
index 0000000..9c7ec1e
--- /dev/null
+++ b/xbmc/pictures/Picture.cpp
@@ -0,0 +1,588 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include <algorithm>
+
+#include "Picture.h"
+#include "URL.h"
+#include "ServiceBroker.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "FileItem.h"
+#include "filesystem/File.h"
+#include "utils/log.h"
+#include "utils/URIUtils.h"
+#include "guilib/Texture.h"
+#include "guilib/imagefactory.h"
+
+extern "C" {
+#include <libswscale/swscale.h>
+}
+
+using namespace XFILE;
+
+bool CPicture::GetThumbnailFromSurface(const unsigned char* buffer, int width, int height, int stride, const std::string &thumbFile, uint8_t* &result, size_t& result_size)
+{
+ unsigned char *thumb = NULL;
+ unsigned int thumbsize = 0;
+
+ // get an image handler
+ IImage* image = ImageFactory::CreateLoader(thumbFile);
+ if (image == NULL ||
+ !image->CreateThumbnailFromSurface(const_cast<unsigned char*>(buffer), width, height,
+ XB_FMT_A8R8G8B8, stride, thumbFile, thumb, thumbsize))
+ {
+ delete image;
+ return false;
+ }
+
+ // copy the resulting buffer
+ result_size = thumbsize;
+ result = new uint8_t[result_size];
+ memcpy(result, thumb, result_size);
+
+ // release the image buffer and the image handler
+ image->ReleaseThumbnailBuffer();
+ delete image;
+
+ return true;
+}
+
+bool CPicture::CreateThumbnailFromSurface(const unsigned char *buffer, int width, int height, int stride, const std::string &thumbFile)
+{
+ CLog::Log(LOGDEBUG, "cached image '{}' size {}x{}", CURL::GetRedacted(thumbFile), width, height);
+
+ unsigned char *thumb = NULL;
+ unsigned int thumbsize=0;
+ IImage* pImage = ImageFactory::CreateLoader(thumbFile);
+ if(pImage == NULL || !pImage->CreateThumbnailFromSurface(const_cast<unsigned char*>(buffer), width, height, XB_FMT_A8R8G8B8, stride, thumbFile.c_str(), thumb, thumbsize))
+ {
+ CLog::Log(LOGERROR, "Failed to CreateThumbnailFromSurface for {}",
+ CURL::GetRedacted(thumbFile));
+ delete pImage;
+ return false;
+ }
+
+ XFILE::CFile file;
+ const bool ret = file.OpenForWrite(thumbFile, true) &&
+ file.Write(thumb, thumbsize) == static_cast<ssize_t>(thumbsize);
+
+ pImage->ReleaseThumbnailBuffer();
+ delete pImage;
+
+ return ret;
+}
+
+CThumbnailWriter::CThumbnailWriter(unsigned char* buffer, int width, int height, int stride, const std::string& thumbFile):
+ m_thumbFile(thumbFile)
+{
+ m_buffer = buffer;
+ m_width = width;
+ m_height = height;
+ m_stride = stride;
+}
+
+CThumbnailWriter::~CThumbnailWriter()
+{
+ delete m_buffer;
+}
+
+bool CThumbnailWriter::DoWork()
+{
+ bool success = true;
+
+ if (!CPicture::CreateThumbnailFromSurface(m_buffer, m_width, m_height, m_stride, m_thumbFile))
+ {
+ CLog::Log(LOGERROR, "CThumbnailWriter::DoWork unable to write {}",
+ CURL::GetRedacted(m_thumbFile));
+ success = false;
+ }
+
+ delete [] m_buffer;
+ m_buffer = NULL;
+
+ return success;
+}
+
+bool CPicture::ResizeTexture(const std::string& image,
+ CTexture* texture,
+ uint32_t& dest_width,
+ uint32_t& dest_height,
+ uint8_t*& result,
+ size_t& result_size,
+ CPictureScalingAlgorithm::Algorithm
+ scalingAlgorithm /* = CPictureScalingAlgorithm::NoAlgorithm */)
+{
+ if (image.empty() || texture == NULL)
+ return false;
+
+ return ResizeTexture(image, texture->GetPixels(), texture->GetWidth(), texture->GetHeight(), texture->GetPitch(),
+ dest_width, dest_height, result, result_size,
+ scalingAlgorithm);
+}
+
+bool CPicture::ResizeTexture(const std::string &image, uint8_t *pixels, uint32_t width, uint32_t height, uint32_t pitch,
+ uint32_t &dest_width, uint32_t &dest_height, uint8_t* &result, size_t& result_size,
+ CPictureScalingAlgorithm::Algorithm scalingAlgorithm /* = CPictureScalingAlgorithm::NoAlgorithm */)
+{
+ if (image.empty() || pixels == NULL)
+ return false;
+
+ dest_width = std::min(width, dest_width);
+ dest_height = std::min(height, dest_height);
+
+ // if no max width or height is specified, don't resize
+ if (dest_width == 0 && dest_height == 0)
+ {
+ dest_width = width;
+ dest_height = height;
+ }
+ else if (dest_width == 0)
+ {
+ double factor = (double)dest_height / (double)height;
+ dest_width = (uint32_t)(width * factor);
+ }
+ else if (dest_height == 0)
+ {
+ double factor = (double)dest_width / (double)width;
+ dest_height = (uint32_t)(height * factor);
+ }
+
+ // nothing special to do if the dimensions already match
+ if (dest_width >= width || dest_height >= height)
+ return GetThumbnailFromSurface(pixels, dest_width, dest_height, pitch, image, result, result_size);
+
+ // create a buffer large enough for the resulting image
+ GetScale(width, height, dest_width, dest_height);
+
+ // Let's align so that stride is always divisible by 16, and then add some 32 bytes more on top
+ // See: https://github.com/FFmpeg/FFmpeg/blob/75638fe9402f70645bdde4d95672fa640a327300/libswscale/tests/swscale.c#L157
+ uint32_t dest_width_aligned = ((dest_width + 15) & ~0x0f);
+ uint32_t stride = dest_width_aligned * sizeof(uint32_t);
+
+ uint32_t* buffer = new uint32_t[dest_width_aligned * dest_height + 4];
+ if (buffer == NULL)
+ {
+ result = NULL;
+ result_size = 0;
+ return false;
+ }
+
+ if (!ScaleImage(pixels, width, height, pitch, AV_PIX_FMT_BGRA, (uint8_t*)buffer, dest_width,
+ dest_height, stride, AV_PIX_FMT_BGRA, scalingAlgorithm))
+ {
+ delete[] buffer;
+ result = NULL;
+ result_size = 0;
+ return false;
+ }
+
+ bool success = GetThumbnailFromSurface((unsigned char*)buffer, dest_width, dest_height, stride,
+ image, result, result_size);
+ delete[] buffer;
+
+ if (!success)
+ {
+ result = NULL;
+ result_size = 0;
+ }
+
+ return success;
+}
+
+bool CPicture::CacheTexture(CTexture* texture,
+ uint32_t& dest_width,
+ uint32_t& dest_height,
+ const std::string& dest,
+ CPictureScalingAlgorithm::Algorithm
+ scalingAlgorithm /* = CPictureScalingAlgorithm::NoAlgorithm */)
+{
+ return CacheTexture(texture->GetPixels(), texture->GetWidth(), texture->GetHeight(), texture->GetPitch(),
+ texture->GetOrientation(), dest_width, dest_height, dest, scalingAlgorithm);
+}
+
+bool CPicture::CacheTexture(uint8_t *pixels, uint32_t width, uint32_t height, uint32_t pitch, int orientation,
+ uint32_t &dest_width, uint32_t &dest_height, const std::string &dest,
+ CPictureScalingAlgorithm::Algorithm scalingAlgorithm /* = CPictureScalingAlgorithm::NoAlgorithm */)
+{
+ const std::shared_ptr<CAdvancedSettings> advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
+
+ // if no max width or height is specified, don't resize
+ if (dest_width == 0)
+ dest_width = width;
+ if (dest_height == 0)
+ dest_height = height;
+ if (scalingAlgorithm == CPictureScalingAlgorithm::NoAlgorithm)
+ scalingAlgorithm = advancedSettings->m_imageScalingAlgorithm;
+
+ uint32_t max_height = advancedSettings->m_imageRes;
+ if (advancedSettings->m_fanartRes > advancedSettings->m_imageRes)
+ { // 16x9 images larger than the fanart res use that rather than the image res
+ if (fabsf(static_cast<float>(width) / static_cast<float>(height) / (16.0f / 9.0f) - 1.0f)
+ <= 0.01f)
+ {
+ max_height = advancedSettings->m_fanartRes; // use height defined in fanartRes
+ }
+ }
+
+ uint32_t max_width = max_height * 16/9;
+
+ dest_height = std::min(dest_height, max_height);
+ dest_width = std::min(dest_width, max_width);
+
+ if (width > dest_width || height > dest_height || orientation)
+ {
+ bool success = false;
+
+ dest_width = std::min(width, dest_width);
+ dest_height = std::min(height, dest_height);
+
+ // create a buffer large enough for the resulting image
+ GetScale(width, height, dest_width, dest_height);
+
+ // Let's align so that stride is always divisible by 16, and then add some 32 bytes more on top
+ // See: https://github.com/FFmpeg/FFmpeg/blob/75638fe9402f70645bdde4d95672fa640a327300/libswscale/tests/swscale.c#L157
+ uint32_t dest_width_aligned = ((dest_width + 15) & ~0x0f);
+ uint32_t stride = dest_width_aligned * sizeof(uint32_t);
+
+ uint32_t* buffer = new uint32_t[dest_width_aligned * dest_height + 4];
+ if (buffer)
+ {
+ if (ScaleImage(pixels, width, height, pitch, AV_PIX_FMT_BGRA, (uint8_t*)buffer, dest_width,
+ dest_height, stride, AV_PIX_FMT_BGRA, scalingAlgorithm))
+ {
+ if (!orientation ||
+ OrientateImage(buffer, dest_width, dest_height, orientation, dest_width_aligned))
+ {
+ success = CreateThumbnailFromSurface((unsigned char*)buffer, dest_width, dest_height,
+ dest_width_aligned * 4, dest);
+ }
+ }
+ delete[] buffer;
+ }
+ return success;
+ }
+ else
+ { // no orientation needed
+ dest_width = width;
+ dest_height = height;
+ return CreateThumbnailFromSurface(pixels, width, height, pitch, dest);
+ }
+ return false;
+}
+
+bool CPicture::CreateTiledThumb(const std::vector<std::string> &files, const std::string &thumb)
+{
+ if (!files.size())
+ return false;
+
+ unsigned int num_across = (unsigned int)ceil(sqrt((float)files.size()));
+ unsigned int num_down = (files.size() + num_across - 1) / num_across;
+
+ unsigned int imageRes = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_imageRes;
+
+ unsigned int tile_width = imageRes / num_across;
+ unsigned int tile_height = imageRes / num_down;
+ unsigned int tile_gap = 1;
+ bool success = false;
+
+ // create a buffer for the resulting thumb
+ uint32_t *buffer = static_cast<uint32_t *>(calloc(imageRes * imageRes, 4));
+ if (!buffer)
+ return false;
+ for (unsigned int i = 0; i < files.size(); ++i)
+ {
+ int x = i % num_across;
+ int y = i / num_across;
+ // load in the image
+ unsigned int width = tile_width - 2*tile_gap, height = tile_height - 2*tile_gap;
+ std::unique_ptr<CTexture> texture = CTexture::LoadFromFile(files[i], width, height, true);
+ if (texture && texture->GetWidth() && texture->GetHeight())
+ {
+ GetScale(texture->GetWidth(), texture->GetHeight(), width, height);
+
+ // scale appropriately
+ uint32_t *scaled = new uint32_t[width * height];
+ if (ScaleImage(texture->GetPixels(), texture->GetWidth(), texture->GetHeight(),
+ texture->GetPitch(), AV_PIX_FMT_BGRA, (uint8_t*)scaled, width, height,
+ width * 4, AV_PIX_FMT_BGRA))
+ {
+ unsigned int stridePixels{width};
+ if (!texture->GetOrientation() ||
+ OrientateImage(scaled, width, height, texture->GetOrientation(), stridePixels))
+ {
+ success = true; // Flag that we at least had one successful image processed
+ // drop into the texture
+ unsigned int posX = x*tile_width + (tile_width - width)/2;
+ unsigned int posY = y*tile_height + (tile_height - height)/2;
+ uint32_t *dest = buffer + posX + posY * imageRes;
+ uint32_t *src = scaled;
+ for (unsigned int y = 0; y < height; ++y)
+ {
+ memcpy(dest, src, width*4);
+ dest += imageRes;
+ src += stridePixels;
+ }
+ }
+ }
+ delete[] scaled;
+ }
+ }
+ // now save to a file
+ if (success)
+ success = CreateThumbnailFromSurface((uint8_t *)buffer, imageRes, imageRes, imageRes * 4, thumb);
+
+ free(buffer);
+ return success;
+}
+
+void CPicture::GetScale(unsigned int width, unsigned int height, unsigned int &out_width, unsigned int &out_height)
+{
+ float aspect = (float)width / height;
+ if ((unsigned int)(out_width / aspect + 0.5f) > out_height)
+ out_width = (unsigned int)(out_height * aspect + 0.5f);
+ else
+ out_height = (unsigned int)(out_width / aspect + 0.5f);
+}
+
+bool CPicture::ScaleImage(uint8_t* in_pixels,
+ unsigned int in_width,
+ unsigned int in_height,
+ unsigned int in_pitch,
+ AVPixelFormat in_format,
+ uint8_t* out_pixels,
+ unsigned int out_width,
+ unsigned int out_height,
+ unsigned int out_pitch,
+ AVPixelFormat out_format,
+ CPictureScalingAlgorithm::Algorithm
+ scalingAlgorithm /* = CPictureScalingAlgorithm::NoAlgorithm */)
+{
+ struct SwsContext* context =
+ sws_getContext(in_width, in_height, in_format, out_width, out_height, out_format,
+ CPictureScalingAlgorithm::ToSwscale(scalingAlgorithm), NULL, NULL, NULL);
+
+ uint8_t *src[] = { in_pixels, 0, 0, 0 };
+ int srcStride[] = { (int)in_pitch, 0, 0, 0 };
+ uint8_t *dst[] = { out_pixels , 0, 0, 0 };
+ int dstStride[] = { (int)out_pitch, 0, 0, 0 };
+
+ if (context)
+ {
+ sws_scale(context, src, srcStride, 0, in_height, dst, dstStride);
+ sws_freeContext(context);
+ return true;
+ }
+ return false;
+}
+
+bool CPicture::OrientateImage(uint32_t*& pixels,
+ unsigned int& width,
+ unsigned int& height,
+ int orientation,
+ unsigned int& stridePixels)
+{
+ // ideas for speeding these functions up: http://cgit.freedesktop.org/pixman/tree/pixman/pixman-fast-path.c
+ bool out = false;
+ switch (orientation)
+ {
+ case 1:
+ out = FlipHorizontal(pixels, width, height, stridePixels);
+ break;
+ case 2:
+ out = Rotate180CCW(pixels, width, height, stridePixels);
+ break;
+ case 3:
+ out = FlipVertical(pixels, width, height, stridePixels);
+ break;
+ case 4:
+ out = Transpose(pixels, width, height, stridePixels);
+ break;
+ case 5:
+ out = Rotate270CCW(pixels, width, height, stridePixels);
+ break;
+ case 6:
+ out = TransposeOffAxis(pixels, width, height, stridePixels);
+ break;
+ case 7:
+ out = Rotate90CCW(pixels, width, height, stridePixels);
+ break;
+ default:
+ CLog::Log(LOGERROR, "Unknown orientation {}", orientation);
+ break;
+ }
+ return out;
+}
+
+bool CPicture::FlipHorizontal(uint32_t*& pixels,
+ const unsigned int& width,
+ const unsigned int& height,
+ const unsigned int& stridePixels)
+{
+ // this can be done in-place easily enough
+ for (unsigned int y = 0; y < height; ++y)
+ {
+ uint32_t* line = pixels + y * stridePixels;
+ for (unsigned int x = 0; x < width / 2; ++x)
+ std::swap(line[x], line[width - 1 - x]);
+ }
+ return true;
+}
+
+bool CPicture::FlipVertical(uint32_t*& pixels,
+ const unsigned int& width,
+ const unsigned int& height,
+ const unsigned int& stridePixels)
+{
+ // this can be done in-place easily enough
+ for (unsigned int y = 0; y < height / 2; ++y)
+ {
+ uint32_t* line1 = pixels + y * stridePixels;
+ uint32_t* line2 = pixels + (height - 1 - y) * stridePixels;
+ for (unsigned int x = 0; x < width; ++x)
+ std::swap(*line1++, *line2++);
+ }
+ return true;
+}
+
+bool CPicture::Rotate180CCW(uint32_t*& pixels,
+ const unsigned int& width,
+ const unsigned int& height,
+ const unsigned int& stridePixels)
+{
+ // this can be done in-place easily enough
+ for (unsigned int y = 0; y < height / 2; ++y)
+ {
+ uint32_t* line1 = pixels + y * stridePixels;
+ uint32_t* line2 = pixels + (height - 1 - y) * stridePixels + width - 1;
+ for (unsigned int x = 0; x < width; ++x)
+ std::swap(*line1++, *line2--);
+ }
+ if (height % 2)
+ { // height is odd, so flip the middle row as well
+ uint32_t* line = pixels + (height - 1) / 2 * stridePixels;
+ for (unsigned int x = 0; x < width / 2; ++x)
+ std::swap(line[x], line[width - 1 - x]);
+ }
+ return true;
+}
+
+bool CPicture::Rotate90CCW(uint32_t*& pixels,
+ unsigned int& width,
+ unsigned int& height,
+ unsigned int& stridePixels)
+{
+ uint32_t *dest = new uint32_t[width * height * 4];
+ if (dest)
+ {
+ unsigned int d_height = width, d_width = height;
+ for (unsigned int y = 0; y < d_height; y++)
+ {
+ const uint32_t *src = pixels + (d_height - 1 - y); // y-th col from right, starting at top
+ uint32_t *dst = dest + d_width * y; // y-th row from top, starting at left
+ for (unsigned int x = 0; x < d_width; x++)
+ {
+ *dst++ = *src;
+ src += stridePixels;
+ }
+ }
+ delete[] pixels;
+ pixels = dest;
+ std::swap(width, height);
+ stridePixels = width;
+ return true;
+ }
+ return false;
+}
+
+bool CPicture::Rotate270CCW(uint32_t*& pixels,
+ unsigned int& width,
+ unsigned int& height,
+ unsigned int& stridePixels)
+{
+ uint32_t *dest = new uint32_t[width * height * 4];
+ if (!dest)
+ return false;
+
+ unsigned int d_height = width, d_width = height;
+ for (unsigned int y = 0; y < d_height; y++)
+ {
+ const uint32_t* src =
+ pixels + stridePixels * (d_width - 1) + y; // y-th col from left, starting at bottom
+ uint32_t* dst = dest + d_width * y; // y-th row from top, starting at left
+ for (unsigned int x = 0; x < d_width; x++)
+ {
+ *dst++ = *src;
+ src -= stridePixels;
+ }
+ }
+
+ delete[] pixels;
+ pixels = dest;
+ std::swap(width, height);
+ stridePixels = width;
+ return true;
+}
+
+bool CPicture::Transpose(uint32_t*& pixels,
+ unsigned int& width,
+ unsigned int& height,
+ unsigned int& stridePixels)
+{
+ uint32_t *dest = new uint32_t[width * height * 4];
+ if (!dest)
+ return false;
+
+ unsigned int d_height = width, d_width = height;
+ for (unsigned int y = 0; y < d_height; y++)
+ {
+ const uint32_t *src = pixels + y; // y-th col from left, starting at top
+ uint32_t *dst = dest + d_width * y; // y-th row from top, starting at left
+ for (unsigned int x = 0; x < d_width; x++)
+ {
+ *dst++ = *src;
+ src += stridePixels;
+ }
+ }
+
+ delete[] pixels;
+ pixels = dest;
+ std::swap(width, height);
+ stridePixels = width;
+ return true;
+}
+
+bool CPicture::TransposeOffAxis(uint32_t*& pixels,
+ unsigned int& width,
+ unsigned int& height,
+ unsigned int& stridePixels)
+{
+ uint32_t *dest = new uint32_t[width * height * 4];
+ if (!dest)
+ return false;
+
+ unsigned int d_height = width, d_width = height;
+ for (unsigned int y = 0; y < d_height; y++)
+ {
+ const uint32_t* src = pixels + stridePixels * (d_width - 1) +
+ (d_height - 1 - y); // y-th col from right, starting at bottom
+ uint32_t *dst = dest + d_width * y; // y-th row, starting at left
+ for (unsigned int x = 0; x < d_width; x++)
+ {
+ *dst++ = *src;
+ src -= stridePixels;
+ }
+ }
+
+ delete[] pixels;
+ pixels = dest;
+ std::swap(width, height);
+ stridePixels = width;
+ return true;
+}
diff --git a/xbmc/pictures/Picture.h b/xbmc/pictures/Picture.h
new file mode 100644
index 0000000..192982a
--- /dev/null
+++ b/xbmc/pictures/Picture.h
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "pictures/PictureScalingAlgorithm.h"
+#include "utils/Job.h"
+
+#include <cstddef>
+#include <cstdint>
+#include <string>
+#include <vector>
+
+extern "C"
+{
+#include <libavutil/pixfmt.h>
+}
+
+class CTexture;
+
+class CPicture
+{
+public:
+ static bool GetThumbnailFromSurface(const unsigned char* buffer, int width, int height, int stride, const std::string &thumbFile, uint8_t* &result, size_t& result_size);
+ static bool CreateThumbnailFromSurface(const unsigned char* buffer, int width, int height, int stride, const std::string &thumbFile);
+
+ /*! \brief Create a tiled thumb of the given files
+ \param files the files to create the thumb from
+ \param thumb the filename of the thumb
+ */
+ static bool CreateTiledThumb(const std::vector<std::string> &files, const std::string &thumb);
+
+ static bool ResizeTexture(
+ const std::string& image,
+ CTexture* texture,
+ uint32_t& dest_width,
+ uint32_t& dest_height,
+ uint8_t*& result,
+ size_t& result_size,
+ CPictureScalingAlgorithm::Algorithm scalingAlgorithm = CPictureScalingAlgorithm::NoAlgorithm);
+ static bool ResizeTexture(const std::string &image, uint8_t *pixels, uint32_t width, uint32_t height, uint32_t pitch,
+ uint32_t &dest_width, uint32_t &dest_height, uint8_t* &result, size_t& result_size,
+ CPictureScalingAlgorithm::Algorithm scalingAlgorithm = CPictureScalingAlgorithm::NoAlgorithm);
+
+ /*! \brief Cache a texture, resizing, rotating and flipping as needed, and saving as a JPG or PNG
+ \param texture a pointer to a CTexture
+ \param dest_width [in/out] maximum width in pixels of cached version - replaced with actual cached width
+ \param dest_height [in/out] maximum height in pixels of cached version - replaced with actual cached height
+ \param dest the output cache file
+ \return true if successful, false otherwise
+ */
+ static bool CacheTexture(
+ CTexture* texture,
+ uint32_t& dest_width,
+ uint32_t& dest_height,
+ const std::string& dest,
+ CPictureScalingAlgorithm::Algorithm scalingAlgorithm = CPictureScalingAlgorithm::NoAlgorithm);
+ static bool CacheTexture(uint8_t *pixels, uint32_t width, uint32_t height, uint32_t pitch, int orientation,
+ uint32_t &dest_width, uint32_t &dest_height, const std::string &dest,
+ CPictureScalingAlgorithm::Algorithm scalingAlgorithm = CPictureScalingAlgorithm::NoAlgorithm);
+
+ static void GetScale(unsigned int width, unsigned int height, unsigned int &out_width, unsigned int &out_height);
+ static bool ScaleImage(
+ uint8_t* in_pixels,
+ unsigned int in_width,
+ unsigned int in_height,
+ unsigned int in_pitch,
+ AVPixelFormat in_format,
+ uint8_t* out_pixels,
+ unsigned int out_width,
+ unsigned int out_height,
+ unsigned int out_pitch,
+ AVPixelFormat out_format,
+ CPictureScalingAlgorithm::Algorithm scalingAlgorithm = CPictureScalingAlgorithm::NoAlgorithm);
+
+private:
+ static bool OrientateImage(uint32_t*& pixels,
+ unsigned int& width,
+ unsigned int& height,
+ int orientation,
+ unsigned int& stridePixels);
+
+ static bool FlipHorizontal(uint32_t*& pixels,
+ const unsigned int& width,
+ const unsigned int& height,
+ const unsigned int& stridePixels);
+ static bool FlipVertical(uint32_t*& pixels,
+ const unsigned int& width,
+ const unsigned int& height,
+ const unsigned int& stridePixels);
+ static bool Rotate90CCW(uint32_t*& pixels,
+ unsigned int& width,
+ unsigned int& height,
+ unsigned int& stridePixels);
+ static bool Rotate270CCW(uint32_t*& pixels,
+ unsigned int& width,
+ unsigned int& height,
+ unsigned int& stridePixels);
+ static bool Rotate180CCW(uint32_t*& pixels,
+ const unsigned int& width,
+ const unsigned int& height,
+ const unsigned int& stridePixels);
+ static bool Transpose(uint32_t*& pixels,
+ unsigned int& width,
+ unsigned int& height,
+ unsigned int& width_aligned);
+ static bool TransposeOffAxis(uint32_t*& pixels,
+ unsigned int& width,
+ unsigned int& height,
+ unsigned int& stridePixels);
+};
+
+//this class calls CreateThumbnailFromSurface in a CJob, so a png file can be written without halting the render thread
+class CThumbnailWriter : public CJob
+{
+ public:
+ //WARNING: buffer is deleted from DoWork()
+ CThumbnailWriter(unsigned char* buffer, int width, int height, int stride, const std::string& thumbFile);
+ ~CThumbnailWriter() override;
+ bool DoWork() override;
+
+ private:
+ unsigned char* m_buffer;
+ int m_width;
+ int m_height;
+ int m_stride;
+ std::string m_thumbFile;
+};
+
diff --git a/xbmc/pictures/PictureInfoLoader.cpp b/xbmc/pictures/PictureInfoLoader.cpp
new file mode 100644
index 0000000..81b8cfa
--- /dev/null
+++ b/xbmc/pictures/PictureInfoLoader.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PictureInfoLoader.h"
+
+#include "FileItem.h"
+#include "PictureInfoTag.h"
+#include "ServiceBroker.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+
+CPictureInfoLoader::CPictureInfoLoader()
+{
+ m_mapFileItems = new CFileItemList;
+ m_tagReads = 0;
+}
+
+CPictureInfoLoader::~CPictureInfoLoader()
+{
+ StopThread();
+ delete m_mapFileItems;
+}
+
+void CPictureInfoLoader::OnLoaderStart()
+{
+ // Load previously cached items from HD
+ m_mapFileItems->SetPath(m_pVecItems->GetPath());
+ m_mapFileItems->Load();
+ m_mapFileItems->SetFastLookup(true);
+
+ m_tagReads = 0;
+ m_loadTags = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_PICTURES_USETAGS);
+
+ if (m_pProgressCallback)
+ m_pProgressCallback->SetProgressMax(m_pVecItems->GetFileCount());
+}
+
+bool CPictureInfoLoader::LoadItem(CFileItem* pItem)
+{
+ bool result = LoadItemCached(pItem);
+ result |= LoadItemLookup(pItem);
+
+ return result;
+}
+
+bool CPictureInfoLoader::LoadItemCached(CFileItem* pItem)
+{
+ if (!pItem->IsPicture() || pItem->IsZIP() || pItem->IsRAR() || pItem->IsCBR() || pItem->IsCBZ() || pItem->IsInternetStream() || pItem->IsVideo())
+ return false;
+
+ if (pItem->HasPictureInfoTag())
+ return true;
+
+ // Check the cached item
+ CFileItemPtr mapItem = (*m_mapFileItems)[pItem->GetPath()];
+ if (mapItem && mapItem->m_dateTime==pItem->m_dateTime && mapItem->HasPictureInfoTag())
+ { // Query map if we previously cached the file on HD
+ *pItem->GetPictureInfoTag() = *mapItem->GetPictureInfoTag();
+ pItem->SetArt("thumb", mapItem->GetArt("thumb"));
+ return true;
+ }
+
+ return true;
+}
+
+bool CPictureInfoLoader::LoadItemLookup(CFileItem* pItem)
+{
+ if (m_pProgressCallback && !pItem->m_bIsFolder)
+ m_pProgressCallback->SetProgressAdvance();
+
+ if (!pItem->IsPicture() || pItem->IsZIP() || pItem->IsRAR() || pItem->IsCBR() || pItem->IsCBZ() || pItem->IsInternetStream() || pItem->IsVideo())
+ return false;
+
+ if (pItem->HasPictureInfoTag())
+ return false;
+
+ if (m_loadTags)
+ { // Nothing found, load tag from file
+ pItem->GetPictureInfoTag()->Load(pItem->GetPath());
+ m_tagReads++;
+ }
+
+ return true;
+}
+
+void CPictureInfoLoader::OnLoaderFinish()
+{
+ // cleanup cache loaded from HD
+ m_mapFileItems->Clear();
+
+ // Save loaded items to HD
+ if (!m_bStop && m_tagReads > 0)
+ m_pVecItems->Save();
+}
diff --git a/xbmc/pictures/PictureInfoLoader.h b/xbmc/pictures/PictureInfoLoader.h
new file mode 100644
index 0000000..0171fcb
--- /dev/null
+++ b/xbmc/pictures/PictureInfoLoader.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "BackgroundInfoLoader.h"
+
+#include <string>
+
+class CPictureInfoLoader : public CBackgroundInfoLoader
+{
+public:
+ CPictureInfoLoader();
+ ~CPictureInfoLoader() override;
+
+ void UseCacheOnHD(const std::string& strFileName);
+ bool LoadItem(CFileItem* pItem) override;
+ bool LoadItemCached(CFileItem* pItem) override;
+ bool LoadItemLookup(CFileItem* pItem) override;
+
+protected:
+ void OnLoaderStart() override;
+ void OnLoaderFinish() override;
+
+ CFileItemList* m_mapFileItems;
+ unsigned int m_tagReads;
+ bool m_loadTags;
+};
+
diff --git a/xbmc/pictures/PictureInfoTag.cpp b/xbmc/pictures/PictureInfoTag.cpp
new file mode 100644
index 0000000..4aab3e0
--- /dev/null
+++ b/xbmc/pictures/PictureInfoTag.cpp
@@ -0,0 +1,764 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PictureInfoTag.h"
+
+#include "ServiceBroker.h"
+#include "addons/ExtsMimeSupportList.h"
+#include "addons/ImageDecoder.h"
+#include "addons/addoninfo/AddonType.h"
+#include "guilib/guiinfo/GUIInfoLabels.h"
+#include "utils/Archive.h"
+#include "utils/CharsetConverter.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/Variant.h"
+
+#include <algorithm>
+#include <vector>
+
+using namespace KODI::ADDONS;
+
+CPictureInfoTag::ExifInfo::ExifInfo(const ExifInfo_t& other)
+ : CameraMake(other.CameraMake),
+ CameraModel(other.CameraModel),
+ DateTime(other.DateTime),
+ Height(other.Height),
+ Width(other.Width),
+ Orientation(other.Orientation),
+ IsColor(other.IsColor),
+ Process(other.Process),
+ FlashUsed(other.FlashUsed),
+ FocalLength(other.FocalLength),
+ ExposureTime(other.ExposureTime),
+ ApertureFNumber(other.ApertureFNumber),
+ Distance(other.Distance),
+ CCDWidth(other.CCDWidth),
+ ExposureBias(other.ExposureBias),
+ DigitalZoomRatio(other.DigitalZoomRatio),
+ FocalLength35mmEquiv(other.FocalLength35mmEquiv),
+ Whitebalance(other.Whitebalance),
+ MeteringMode(other.MeteringMode),
+ ExposureProgram(other.ExposureProgram),
+ ExposureMode(other.ExposureMode),
+ ISOequivalent(other.ISOequivalent),
+ LightSource(other.LightSource),
+ CommentsCharset(EXIF_COMMENT_CHARSET_CONVERTED),
+ XPCommentsCharset(EXIF_COMMENT_CHARSET_CONVERTED),
+ Comments(Convert(other.CommentsCharset, other.Comments)),
+ FileComment(Convert(EXIF_COMMENT_CHARSET_UNKNOWN, other.FileComment)),
+ XPComment(Convert(other.XPCommentsCharset, other.XPComment)),
+ Description(other.Description),
+ ThumbnailOffset(other.ThumbnailOffset),
+ ThumbnailSize(other.ThumbnailSize),
+ LargestExifOffset(other.LargestExifOffset),
+ ThumbnailAtEnd(other.ThumbnailAtEnd),
+ ThumbnailSizeOffset(other.ThumbnailSizeOffset),
+ DateTimeOffsets(other.DateTimeOffsets, other.DateTimeOffsets + other.numDateTimeTags),
+ GpsInfoPresent(other.GpsInfoPresent),
+ GpsLat(other.GpsLat),
+ GpsLong(other.GpsLong),
+ GpsAlt(other.GpsAlt)
+{
+}
+
+std::string CPictureInfoTag::ExifInfo::Convert(int charset, const char* data)
+{
+ std::string value;
+
+ // The charset used for the UserComment is stored in CommentsCharset:
+ // Ascii, Unicode (UCS2), JIS (X208-1990), Unknown (application specific)
+ if (charset == EXIF_COMMENT_CHARSET_UNICODE)
+ {
+ g_charsetConverter.ucs2ToUTF8(std::u16string(reinterpret_cast<const char16_t*>(data)), value);
+ }
+ else
+ {
+ // Ascii doesn't need to be converted (EXIF_COMMENT_CHARSET_ASCII)
+ // Unknown data can't be converted as it could be any codec (EXIF_COMMENT_CHARSET_UNKNOWN)
+ // JIS data can't be converted as CharsetConverter and iconv lacks support (EXIF_COMMENT_CHARSET_JIS)
+ g_charsetConverter.unknownToUTF8(data, value);
+ }
+
+ return value;
+}
+
+CPictureInfoTag::IPTCInfo::IPTCInfo(const IPTCInfo_t& other)
+ : RecordVersion(other.RecordVersion),
+ SupplementalCategories(other.SupplementalCategories),
+ Keywords(other.Keywords),
+ Caption(other.Caption),
+ Author(other.Author),
+ Headline(other.Headline),
+ SpecialInstructions(other.SpecialInstructions),
+ Category(other.Category),
+ Byline(other.Byline),
+ BylineTitle(other.BylineTitle),
+ Credit(other.Credit),
+ Source(other.Source),
+ CopyrightNotice(other.CopyrightNotice),
+ ObjectName(other.ObjectName),
+ City(other.City),
+ State(other.State),
+ Country(other.Country),
+ TransmissionReference(other.TransmissionReference),
+ Date(other.Date),
+ Urgency(other.Urgency),
+ ReferenceService(other.ReferenceService),
+ CountryCode(other.CountryCode),
+ TimeCreated(other.TimeCreated),
+ SubLocation(other.SubLocation),
+ ImageType(other.ImageType)
+{
+}
+
+void CPictureInfoTag::Reset()
+{
+ m_exifInfo = {};
+ m_iptcInfo = {};
+ m_isLoaded = false;
+ m_isInfoSetExternally = false;
+ m_dateTimeTaken.Reset();
+}
+
+bool CPictureInfoTag::Load(const std::string &path)
+{
+ m_isLoaded = false;
+
+ // Get file extensions to find addon related to it.
+ std::string strExtension = URIUtils::GetExtension(path);
+ StringUtils::ToLower(strExtension);
+ if (!strExtension.empty() && CServiceBroker::IsAddonInterfaceUp())
+ {
+ // Load via available image decoder addons
+ auto addonInfos = CServiceBroker::GetExtsMimeSupportList().GetExtensionSupportedAddonInfos(
+ strExtension, CExtsMimeSupportList::FilterSelect::all);
+ for (const auto& addonInfo : addonInfos)
+ {
+ if (addonInfo.first != ADDON::AddonType::IMAGEDECODER)
+ continue;
+
+ std::unique_ptr<CImageDecoder> result = std::make_unique<CImageDecoder>(addonInfo.second, "");
+ if (result->LoadInfoTag(path, this))
+ {
+ m_isLoaded = true;
+ break;
+ }
+ }
+ }
+
+ // Load by Kodi's included own way
+ if (!m_isLoaded)
+ {
+ ExifInfo_t exifInfo;
+ IPTCInfo_t iptcInfo;
+
+ if (process_jpeg(path.c_str(), &exifInfo, &iptcInfo))
+ {
+ m_exifInfo = ExifInfo(exifInfo);
+ m_iptcInfo = IPTCInfo(iptcInfo);
+ m_isLoaded = true;
+ }
+ }
+
+ ConvertDateTime();
+
+ return m_isLoaded;
+}
+
+void CPictureInfoTag::Archive(CArchive& ar)
+{
+ if (ar.IsStoring())
+ {
+ ar << m_isLoaded;
+ ar << m_isInfoSetExternally;
+ ar << m_exifInfo.ApertureFNumber;
+ ar << m_exifInfo.CameraMake;
+ ar << m_exifInfo.CameraModel;
+ ar << m_exifInfo.CCDWidth;
+ ar << m_exifInfo.Comments;
+ ar << m_exifInfo.Description;
+ ar << m_exifInfo.DateTime;
+ for (std::vector<int>::size_type i = 0; i < MAX_DATE_COPIES; ++i)
+ {
+ if (i < m_exifInfo.DateTimeOffsets.size())
+ ar << m_exifInfo.DateTimeOffsets[i];
+ else
+ ar << static_cast<int>(0);
+ }
+ ar << m_exifInfo.DigitalZoomRatio;
+ ar << m_exifInfo.Distance;
+ ar << m_exifInfo.ExposureBias;
+ ar << m_exifInfo.ExposureMode;
+ ar << m_exifInfo.ExposureProgram;
+ ar << m_exifInfo.ExposureTime;
+ ar << m_exifInfo.FlashUsed;
+ ar << m_exifInfo.FocalLength;
+ ar << m_exifInfo.FocalLength35mmEquiv;
+ ar << m_exifInfo.GpsInfoPresent;
+ ar << m_exifInfo.GpsAlt;
+ ar << m_exifInfo.GpsLat;
+ ar << m_exifInfo.GpsLong;
+ ar << m_exifInfo.Height;
+ ar << m_exifInfo.IsColor;
+ ar << m_exifInfo.ISOequivalent;
+ ar << m_exifInfo.LargestExifOffset;
+ ar << m_exifInfo.LightSource;
+ ar << m_exifInfo.MeteringMode;
+ ar << static_cast<int>(m_exifInfo.DateTimeOffsets.size());
+ ar << m_exifInfo.Orientation;
+ ar << m_exifInfo.Process;
+ ar << m_exifInfo.ThumbnailAtEnd;
+ ar << m_exifInfo.ThumbnailOffset;
+ ar << m_exifInfo.ThumbnailSize;
+ ar << m_exifInfo.ThumbnailSizeOffset;
+ ar << m_exifInfo.Whitebalance;
+ ar << m_exifInfo.Width;
+ ar << m_dateTimeTaken;
+
+ ar << m_iptcInfo.Author;
+ ar << m_iptcInfo.Byline;
+ ar << m_iptcInfo.BylineTitle;
+ ar << m_iptcInfo.Caption;
+ ar << m_iptcInfo.Category;
+ ar << m_iptcInfo.City;
+ ar << m_iptcInfo.Urgency;
+ ar << m_iptcInfo.CopyrightNotice;
+ ar << m_iptcInfo.Country;
+ ar << m_iptcInfo.CountryCode;
+ ar << m_iptcInfo.Credit;
+ ar << m_iptcInfo.Date;
+ ar << m_iptcInfo.Headline;
+ ar << m_iptcInfo.Keywords;
+ ar << m_iptcInfo.ObjectName;
+ ar << m_iptcInfo.ReferenceService;
+ ar << m_iptcInfo.Source;
+ ar << m_iptcInfo.SpecialInstructions;
+ ar << m_iptcInfo.State;
+ ar << m_iptcInfo.SupplementalCategories;
+ ar << m_iptcInfo.TransmissionReference;
+ ar << m_iptcInfo.TimeCreated;
+ ar << m_iptcInfo.SubLocation;
+ ar << m_iptcInfo.ImageType;
+ }
+ else
+ {
+ ar >> m_isLoaded;
+ ar >> m_isInfoSetExternally;
+ ar >> m_exifInfo.ApertureFNumber;
+ ar >> m_exifInfo.CameraMake;
+ ar >> m_exifInfo.CameraModel;
+ ar >> m_exifInfo.CCDWidth;
+ ar >> m_exifInfo.Comments;
+ m_exifInfo.CommentsCharset = EXIF_COMMENT_CHARSET_CONVERTED; // Store and restore the comment charset converted
+ ar >> m_exifInfo.Description;
+ ar >> m_exifInfo.DateTime;
+ m_exifInfo.DateTimeOffsets.clear();
+ m_exifInfo.DateTimeOffsets.reserve(MAX_DATE_COPIES);
+ for (std::vector<int>::size_type i = 0; i < MAX_DATE_COPIES; ++i)
+ {
+ int dateTimeOffset;
+ ar >> dateTimeOffset;
+ m_exifInfo.DateTimeOffsets.push_back(dateTimeOffset);
+ }
+ ar >> m_exifInfo.DigitalZoomRatio;
+ ar >> m_exifInfo.Distance;
+ ar >> m_exifInfo.ExposureBias;
+ ar >> m_exifInfo.ExposureMode;
+ ar >> m_exifInfo.ExposureProgram;
+ ar >> m_exifInfo.ExposureTime;
+ ar >> m_exifInfo.FlashUsed;
+ ar >> m_exifInfo.FocalLength;
+ ar >> m_exifInfo.FocalLength35mmEquiv;
+ ar >> m_exifInfo.GpsInfoPresent;
+ ar >> m_exifInfo.GpsAlt;
+ ar >> m_exifInfo.GpsLat;
+ ar >> m_exifInfo.GpsLong;
+ ar >> m_exifInfo.Height;
+ ar >> m_exifInfo.IsColor;
+ ar >> m_exifInfo.ISOequivalent;
+ ar >> m_exifInfo.LargestExifOffset;
+ ar >> m_exifInfo.LightSource;
+ ar >> m_exifInfo.MeteringMode;
+ int numDateTimeTags;
+ ar >> numDateTimeTags;
+ m_exifInfo.DateTimeOffsets.resize(numDateTimeTags);
+ ar >> m_exifInfo.Orientation;
+ ar >> m_exifInfo.Process;
+ ar >> m_exifInfo.ThumbnailAtEnd;
+ ar >> m_exifInfo.ThumbnailOffset;
+ ar >> m_exifInfo.ThumbnailSize;
+ ar >> m_exifInfo.ThumbnailSizeOffset;
+ ar >> m_exifInfo.Whitebalance;
+ ar >> m_exifInfo.Width;
+ ar >> m_dateTimeTaken;
+
+ ar >> m_iptcInfo.Author;
+ ar >> m_iptcInfo.Byline;
+ ar >> m_iptcInfo.BylineTitle;
+ ar >> m_iptcInfo.Caption;
+ ar >> m_iptcInfo.Category;
+ ar >> m_iptcInfo.City;
+ ar >> m_iptcInfo.Urgency;
+ ar >> m_iptcInfo.CopyrightNotice;
+ ar >> m_iptcInfo.Country;
+ ar >> m_iptcInfo.CountryCode;
+ ar >> m_iptcInfo.Credit;
+ ar >> m_iptcInfo.Date;
+ ar >> m_iptcInfo.Headline;
+ ar >> m_iptcInfo.Keywords;
+ ar >> m_iptcInfo.ObjectName;
+ ar >> m_iptcInfo.ReferenceService;
+ ar >> m_iptcInfo.Source;
+ ar >> m_iptcInfo.SpecialInstructions;
+ ar >> m_iptcInfo.State;
+ ar >> m_iptcInfo.SupplementalCategories;
+ ar >> m_iptcInfo.TransmissionReference;
+ ar >> m_iptcInfo.TimeCreated;
+ ar >> m_iptcInfo.SubLocation;
+ ar >> m_iptcInfo.ImageType;
+ }
+}
+
+void CPictureInfoTag::Serialize(CVariant& value) const
+{
+ value["aperturefnumber"] = m_exifInfo.ApertureFNumber;
+ value["cameramake"] = m_exifInfo.CameraMake;
+ value["cameramodel"] = m_exifInfo.CameraModel;
+ value["ccdwidth"] = m_exifInfo.CCDWidth;
+ value["comments"] = m_exifInfo.Comments;
+ value["description"] = m_exifInfo.Description;
+ value["datetime"] = m_exifInfo.DateTime;
+ for (std::vector<int>::size_type i = 0; i < MAX_DATE_COPIES; ++i)
+ {
+ if (i < m_exifInfo.DateTimeOffsets.size())
+ value["datetimeoffsets"][static_cast<int>(i)] = m_exifInfo.DateTimeOffsets[i];
+ else
+ value["datetimeoffsets"][static_cast<int>(i)] = static_cast<int>(0);
+ }
+ value["digitalzoomratio"] = m_exifInfo.DigitalZoomRatio;
+ value["distance"] = m_exifInfo.Distance;
+ value["exposurebias"] = m_exifInfo.ExposureBias;
+ value["exposuremode"] = m_exifInfo.ExposureMode;
+ value["exposureprogram"] = m_exifInfo.ExposureProgram;
+ value["exposuretime"] = m_exifInfo.ExposureTime;
+ value["flashused"] = m_exifInfo.FlashUsed;
+ value["focallength"] = m_exifInfo.FocalLength;
+ value["focallength35mmequiv"] = m_exifInfo.FocalLength35mmEquiv;
+ value["gpsinfopresent"] = m_exifInfo.GpsInfoPresent;
+ value["gpsinfo"]["alt"] = m_exifInfo.GpsAlt;
+ value["gpsinfo"]["lat"] = m_exifInfo.GpsLat;
+ value["gpsinfo"]["long"] = m_exifInfo.GpsLong;
+ value["height"] = m_exifInfo.Height;
+ value["iscolor"] = m_exifInfo.IsColor;
+ value["isoequivalent"] = m_exifInfo.ISOequivalent;
+ value["largestexifoffset"] = m_exifInfo.LargestExifOffset;
+ value["lightsource"] = m_exifInfo.LightSource;
+ value["meteringmode"] = m_exifInfo.MeteringMode;
+ value["numdatetimetags"] = static_cast<int>(m_exifInfo.DateTimeOffsets.size());
+ value["orientation"] = m_exifInfo.Orientation;
+ value["process"] = m_exifInfo.Process;
+ value["thumbnailatend"] = m_exifInfo.ThumbnailAtEnd;
+ value["thumbnailoffset"] = m_exifInfo.ThumbnailOffset;
+ value["thumbnailsize"] = m_exifInfo.ThumbnailSize;
+ value["thumbnailsizeoffset"] = m_exifInfo.ThumbnailSizeOffset;
+ value["whitebalance"] = m_exifInfo.Whitebalance;
+ value["width"] = m_exifInfo.Width;
+
+ value["author"] = m_iptcInfo.Author;
+ value["byline"] = m_iptcInfo.Byline;
+ value["bylinetitle"] = m_iptcInfo.BylineTitle;
+ value["caption"] = m_iptcInfo.Caption;
+ value["category"] = m_iptcInfo.Category;
+ value["city"] = m_iptcInfo.City;
+ value["urgency"] = m_iptcInfo.Urgency;
+ value["copyrightnotice"] = m_iptcInfo.CopyrightNotice;
+ value["country"] = m_iptcInfo.Country;
+ value["countrycode"] = m_iptcInfo.CountryCode;
+ value["credit"] = m_iptcInfo.Credit;
+ value["date"] = m_iptcInfo.Date;
+ value["headline"] = m_iptcInfo.Headline;
+ value["keywords"] = m_iptcInfo.Keywords;
+ value["objectname"] = m_iptcInfo.ObjectName;
+ value["referenceservice"] = m_iptcInfo.ReferenceService;
+ value["source"] = m_iptcInfo.Source;
+ value["specialinstructions"] = m_iptcInfo.SpecialInstructions;
+ value["state"] = m_iptcInfo.State;
+ value["supplementalcategories"] = m_iptcInfo.SupplementalCategories;
+ value["transmissionreference"] = m_iptcInfo.TransmissionReference;
+ value["timecreated"] = m_iptcInfo.TimeCreated;
+ value["sublocation"] = m_iptcInfo.SubLocation;
+ value["imagetype"] = m_iptcInfo.ImageType;
+}
+
+void CPictureInfoTag::ToSortable(SortItem& sortable, Field field) const
+{
+ if (field == FieldDateTaken && m_dateTimeTaken.IsValid())
+ sortable[FieldDateTaken] = m_dateTimeTaken.GetAsDBDateTime();
+}
+
+const std::string CPictureInfoTag::GetInfo(int info) const
+{
+ if (!m_isLoaded && !m_isInfoSetExternally) // If no metadata has been loaded from the picture file or set with SetInfo(), just return
+ return "";
+
+ std::string value;
+ switch (info)
+ {
+ case SLIDESHOW_RESOLUTION:
+ value = StringUtils::Format("{} x {}", m_exifInfo.Width, m_exifInfo.Height);
+ break;
+ case SLIDESHOW_COLOUR:
+ value = m_exifInfo.IsColor ? "Colour" : "Black and White";
+ break;
+ case SLIDESHOW_PROCESS:
+ switch (m_exifInfo.Process)
+ {
+ case M_SOF0:
+ // don't show it if its the plain old boring 'baseline' process, but do
+ // show it if its something else, like 'progressive' (used on web sometimes)
+ value = "Baseline";
+ break;
+ case M_SOF1: value = "Extended sequential"; break;
+ case M_SOF2: value = "Progressive"; break;
+ case M_SOF3: value = "Lossless"; break;
+ case M_SOF5: value = "Differential sequential"; break;
+ case M_SOF6: value = "Differential progressive"; break;
+ case M_SOF7: value = "Differential lossless"; break;
+ case M_SOF9: value = "Extended sequential, arithmetic coding"; break;
+ case M_SOF10: value = "Progressive, arithmetic coding"; break;
+ case M_SOF11: value = "Lossless, arithmetic coding"; break;
+ case M_SOF13: value = "Differential sequential, arithmetic coding"; break;
+ case M_SOF14: value = "Differential progressive, arithmetic coding"; break;
+ case M_SOF15: value = "Differential lossless, arithmetic coding"; break;
+ default: value = "Unknown"; break;
+ }
+ break;
+ case SLIDESHOW_COMMENT:
+ value = m_exifInfo.FileComment;
+ break;
+ case SLIDESHOW_EXIF_COMMENT:
+ value = m_exifInfo.Comments;
+ break;
+ case SLIDESHOW_EXIF_XPCOMMENT:
+ value = m_exifInfo.XPComment;
+ break;
+ case SLIDESHOW_EXIF_LONG_DATE_TIME:
+ if (m_dateTimeTaken.IsValid())
+ value = m_dateTimeTaken.GetAsLocalizedDateTime(true);
+ break;
+ case SLIDESHOW_EXIF_DATE_TIME:
+ if (m_dateTimeTaken.IsValid())
+ value = m_dateTimeTaken.GetAsLocalizedDateTime();
+ break;
+ case SLIDESHOW_EXIF_LONG_DATE:
+ if (m_dateTimeTaken.IsValid())
+ value = m_dateTimeTaken.GetAsLocalizedDate(true);
+ break;
+ case SLIDESHOW_EXIF_DATE:
+ if (m_dateTimeTaken.IsValid())
+ value = m_dateTimeTaken.GetAsLocalizedDate();
+ break;
+ case SLIDESHOW_EXIF_DESCRIPTION:
+ value = m_exifInfo.Description;
+ break;
+ case SLIDESHOW_EXIF_CAMERA_MAKE:
+ value = m_exifInfo.CameraMake;
+ break;
+ case SLIDESHOW_EXIF_CAMERA_MODEL:
+ value = m_exifInfo.CameraModel;
+ break;
+// case SLIDESHOW_EXIF_SOFTWARE:
+// value = m_exifInfo.Software;
+ case SLIDESHOW_EXIF_APERTURE:
+ if (m_exifInfo.ApertureFNumber)
+ value = StringUtils::Format("{:3.1f}", m_exifInfo.ApertureFNumber);
+ break;
+ case SLIDESHOW_EXIF_ORIENTATION:
+ switch (m_exifInfo.Orientation)
+ {
+ case 1: value = "Top Left"; break;
+ case 2: value = "Top Right"; break;
+ case 3: value = "Bottom Right"; break;
+ case 4: value = "Bottom Left"; break;
+ case 5: value = "Left Top"; break;
+ case 6: value = "Right Top"; break;
+ case 7: value = "Right Bottom"; break;
+ case 8: value = "Left Bottom"; break;
+ }
+ break;
+ case SLIDESHOW_EXIF_FOCAL_LENGTH:
+ if (m_exifInfo.FocalLength)
+ {
+ value = StringUtils::Format("{:4.2f}mm", m_exifInfo.FocalLength);
+ if (m_exifInfo.FocalLength35mmEquiv != 0)
+ value += StringUtils::Format(" (35mm Equivalent = {}mm)", m_exifInfo.FocalLength35mmEquiv);
+ }
+ break;
+ case SLIDESHOW_EXIF_FOCUS_DIST:
+ if (m_exifInfo.Distance < 0)
+ value = "Infinite";
+ else if (m_exifInfo.Distance > 0)
+ value = StringUtils::Format("{:4.2f}m", m_exifInfo.Distance);
+ break;
+ case SLIDESHOW_EXIF_EXPOSURE:
+ switch (m_exifInfo.ExposureProgram)
+ {
+ case 1: value = "Manual"; break;
+ case 2: value = "Program (Auto)"; break;
+ case 3: value = "Aperture priority (Semi-Auto)"; break;
+ case 4: value = "Shutter priority (semi-auto)"; break;
+ case 5: value = "Creative Program (based towards depth of field)"; break;
+ case 6: value = "Action program (based towards fast shutter speed)"; break;
+ case 7: value = "Portrait Mode"; break;
+ case 8: value = "Landscape Mode"; break;
+ }
+ break;
+ case SLIDESHOW_EXIF_EXPOSURE_TIME:
+ if (m_exifInfo.ExposureTime)
+ {
+ if (m_exifInfo.ExposureTime < 0.010f)
+ value = StringUtils::Format("{:6.4f}s", m_exifInfo.ExposureTime);
+ else
+ value = StringUtils::Format("{:5.3f}s", m_exifInfo.ExposureTime);
+ if (m_exifInfo.ExposureTime <= 0.5f)
+ value += StringUtils::Format(" (1/{})", static_cast<int>(0.5f + 1 / m_exifInfo.ExposureTime));
+ }
+ break;
+ case SLIDESHOW_EXIF_EXPOSURE_BIAS:
+ if (m_exifInfo.ExposureBias != 0)
+ value = StringUtils::Format("{:4.2f} EV", m_exifInfo.ExposureBias);
+ break;
+ case SLIDESHOW_EXIF_EXPOSURE_MODE:
+ switch (m_exifInfo.ExposureMode)
+ {
+ case 0: value = "Automatic"; break;
+ case 1: value = "Manual"; break;
+ case 2: value = "Auto bracketing"; break;
+ }
+ break;
+ case SLIDESHOW_EXIF_FLASH_USED:
+ if (m_exifInfo.FlashUsed >= 0)
+ {
+ if (m_exifInfo.FlashUsed & 1)
+ {
+ value = "Yes";
+ switch (m_exifInfo.FlashUsed)
+ {
+ case 0x5: value = "Yes (Strobe light not detected)"; break;
+ case 0x7: value = "Yes (Strobe light detected)"; break;
+ case 0x9: value = "Yes (Manual)"; break;
+ case 0xd: value = "Yes (Manual, return light not detected)"; break;
+ case 0xf: value = "Yes (Manual, return light detected)"; break;
+ case 0x19: value = "Yes (Auto)"; break;
+ case 0x1d: value = "Yes (Auto, return light not detected)"; break;
+ case 0x1f: value = "Yes (Auto, return light detected)"; break;
+ case 0x41: value = "Yes (Red eye reduction mode)"; break;
+ case 0x45: value = "Yes (Red eye reduction mode return light not detected)"; break;
+ case 0x47: value = "Yes (Red eye reduction mode return light detected)"; break;
+ case 0x49: value = "Yes (Manual, red eye reduction mode)"; break;
+ case 0x4d: value = "Yes (Manual, red eye reduction mode, return light not detected)"; break;
+ case 0x4f: value = "Yes (Manual, red eye reduction mode, return light detected)"; break;
+ case 0x59: value = "Yes (Auto, red eye reduction mode)"; break;
+ case 0x5d: value = "Yes (Auto, red eye reduction mode, return light not detected)"; break;
+ case 0x5f: value = "Yes (Auto, red eye reduction mode, return light detected)"; break;
+ }
+ }
+ else
+ value = m_exifInfo.FlashUsed == 0x18 ? "No (Auto)" : "No";
+ }
+ break;
+ case SLIDESHOW_EXIF_WHITE_BALANCE:
+ return m_exifInfo.Whitebalance ? "Manual" : "Auto";
+ case SLIDESHOW_EXIF_LIGHT_SOURCE:
+ switch (m_exifInfo.LightSource)
+ {
+ case 1: value = "Daylight"; break;
+ case 2: value = "Fluorescent"; break;
+ case 3: value = "Incandescent"; break;
+ case 4: value = "Flash"; break;
+ case 9: value = "Fine Weather"; break;
+ case 11: value = "Shade"; break;
+ default:; //Quercus: 17-1-2004 There are many more modes for this, check Exif2.2 specs
+ // If it just says 'unknown' or we don't know it, then
+ // don't bother showing it - it doesn't add any useful information.
+ }
+ break;
+ case SLIDESHOW_EXIF_METERING_MODE:
+ switch (m_exifInfo.MeteringMode)
+ {
+ case 2: value = "Center weight"; break;
+ case 3: value = "Spot"; break;
+ case 5: value = "Matrix"; break;
+ }
+ break;
+ case SLIDESHOW_EXIF_ISO_EQUIV:
+ if (m_exifInfo.ISOequivalent)
+ value = StringUtils::Format("{:2}", m_exifInfo.ISOequivalent);
+ break;
+ case SLIDESHOW_EXIF_DIGITAL_ZOOM:
+ if (m_exifInfo.DigitalZoomRatio)
+ value = StringUtils::Format("{:1.3f}x", m_exifInfo.DigitalZoomRatio);
+ break;
+ case SLIDESHOW_EXIF_CCD_WIDTH:
+ if (m_exifInfo.CCDWidth)
+ value = StringUtils::Format("{:4.2f}mm", m_exifInfo.CCDWidth);
+ break;
+ case SLIDESHOW_EXIF_GPS_LATITUDE:
+ value = m_exifInfo.GpsLat;
+ break;
+ case SLIDESHOW_EXIF_GPS_LONGITUDE:
+ value = m_exifInfo.GpsLong;
+ break;
+ case SLIDESHOW_EXIF_GPS_ALTITUDE:
+ value = m_exifInfo.GpsAlt;
+ break;
+ case SLIDESHOW_IPTC_SUP_CATEGORIES: value = m_iptcInfo.SupplementalCategories; break;
+ case SLIDESHOW_IPTC_KEYWORDS: value = m_iptcInfo.Keywords; break;
+ case SLIDESHOW_IPTC_CAPTION: value = m_iptcInfo.Caption; break;
+ case SLIDESHOW_IPTC_AUTHOR: value = m_iptcInfo.Author; break;
+ case SLIDESHOW_IPTC_HEADLINE: value = m_iptcInfo.Headline; break;
+ case SLIDESHOW_IPTC_SPEC_INSTR: value = m_iptcInfo.SpecialInstructions; break;
+ case SLIDESHOW_IPTC_CATEGORY: value = m_iptcInfo.Category; break;
+ case SLIDESHOW_IPTC_BYLINE: value = m_iptcInfo.Byline; break;
+ case SLIDESHOW_IPTC_BYLINE_TITLE: value = m_iptcInfo.BylineTitle; break;
+ case SLIDESHOW_IPTC_CREDIT: value = m_iptcInfo.Credit; break;
+ case SLIDESHOW_IPTC_SOURCE: value = m_iptcInfo.Source; break;
+ case SLIDESHOW_IPTC_COPYRIGHT_NOTICE: value = m_iptcInfo.CopyrightNotice; break;
+ case SLIDESHOW_IPTC_OBJECT_NAME: value = m_iptcInfo.ObjectName; break;
+ case SLIDESHOW_IPTC_CITY: value = m_iptcInfo.City; break;
+ case SLIDESHOW_IPTC_STATE: value = m_iptcInfo.State; break;
+ case SLIDESHOW_IPTC_COUNTRY: value = m_iptcInfo.Country; break;
+ case SLIDESHOW_IPTC_TX_REFERENCE: value = m_iptcInfo.TransmissionReference; break;
+ case SLIDESHOW_IPTC_DATE: value = m_iptcInfo.Date; break;
+ case SLIDESHOW_IPTC_URGENCY: value = m_iptcInfo.Urgency; break;
+ case SLIDESHOW_IPTC_COUNTRY_CODE: value = m_iptcInfo.CountryCode; break;
+ case SLIDESHOW_IPTC_REF_SERVICE: value = m_iptcInfo.ReferenceService; break;
+ case SLIDESHOW_IPTC_TIMECREATED: value = m_iptcInfo.TimeCreated; break;
+ case SLIDESHOW_IPTC_SUBLOCATION: value = m_iptcInfo.SubLocation; break;
+ case SLIDESHOW_IPTC_IMAGETYPE: value = m_iptcInfo.ImageType; break;
+ default:
+ break;
+ }
+ return value;
+}
+
+int CPictureInfoTag::TranslateString(const std::string &info)
+{
+ if (StringUtils::EqualsNoCase(info, "filename")) return SLIDESHOW_FILE_NAME;
+ else if (StringUtils::EqualsNoCase(info, "path")) return SLIDESHOW_FILE_PATH;
+ else if (StringUtils::EqualsNoCase(info, "filesize")) return SLIDESHOW_FILE_SIZE;
+ else if (StringUtils::EqualsNoCase(info, "filedate")) return SLIDESHOW_FILE_DATE;
+ else if (StringUtils::EqualsNoCase(info, "slideindex")) return SLIDESHOW_INDEX;
+ else if (StringUtils::EqualsNoCase(info, "resolution")) return SLIDESHOW_RESOLUTION;
+ else if (StringUtils::EqualsNoCase(info, "slidecomment")) return SLIDESHOW_COMMENT;
+ else if (StringUtils::EqualsNoCase(info, "colour")) return SLIDESHOW_COLOUR;
+ else if (StringUtils::EqualsNoCase(info, "process")) return SLIDESHOW_PROCESS;
+ else if (StringUtils::EqualsNoCase(info, "exiftime")) return SLIDESHOW_EXIF_DATE_TIME;
+ else if (StringUtils::EqualsNoCase(info, "exifdate")) return SLIDESHOW_EXIF_DATE;
+ else if (StringUtils::EqualsNoCase(info, "longexiftime")) return SLIDESHOW_EXIF_LONG_DATE_TIME;
+ else if (StringUtils::EqualsNoCase(info, "longexifdate")) return SLIDESHOW_EXIF_LONG_DATE;
+ else if (StringUtils::EqualsNoCase(info, "exifdescription")) return SLIDESHOW_EXIF_DESCRIPTION;
+ else if (StringUtils::EqualsNoCase(info, "cameramake")) return SLIDESHOW_EXIF_CAMERA_MAKE;
+ else if (StringUtils::EqualsNoCase(info, "cameramodel")) return SLIDESHOW_EXIF_CAMERA_MODEL;
+ else if (StringUtils::EqualsNoCase(info, "exifcomment")) return SLIDESHOW_EXIF_COMMENT;
+ else if (StringUtils::EqualsNoCase(info, "exifsoftware")) return SLIDESHOW_EXIF_SOFTWARE;
+ else if (StringUtils::EqualsNoCase(info, "aperture")) return SLIDESHOW_EXIF_APERTURE;
+ else if (StringUtils::EqualsNoCase(info, "focallength")) return SLIDESHOW_EXIF_FOCAL_LENGTH;
+ else if (StringUtils::EqualsNoCase(info, "focusdistance")) return SLIDESHOW_EXIF_FOCUS_DIST;
+ else if (StringUtils::EqualsNoCase(info, "exposure")) return SLIDESHOW_EXIF_EXPOSURE;
+ else if (StringUtils::EqualsNoCase(info, "exposuretime")) return SLIDESHOW_EXIF_EXPOSURE_TIME;
+ else if (StringUtils::EqualsNoCase(info, "exposurebias")) return SLIDESHOW_EXIF_EXPOSURE_BIAS;
+ else if (StringUtils::EqualsNoCase(info, "exposuremode")) return SLIDESHOW_EXIF_EXPOSURE_MODE;
+ else if (StringUtils::EqualsNoCase(info, "flashused")) return SLIDESHOW_EXIF_FLASH_USED;
+ else if (StringUtils::EqualsNoCase(info, "whitebalance")) return SLIDESHOW_EXIF_WHITE_BALANCE;
+ else if (StringUtils::EqualsNoCase(info, "lightsource")) return SLIDESHOW_EXIF_LIGHT_SOURCE;
+ else if (StringUtils::EqualsNoCase(info, "meteringmode")) return SLIDESHOW_EXIF_METERING_MODE;
+ else if (StringUtils::EqualsNoCase(info, "isoequivalence")) return SLIDESHOW_EXIF_ISO_EQUIV;
+ else if (StringUtils::EqualsNoCase(info, "digitalzoom")) return SLIDESHOW_EXIF_DIGITAL_ZOOM;
+ else if (StringUtils::EqualsNoCase(info, "ccdwidth")) return SLIDESHOW_EXIF_CCD_WIDTH;
+ else if (StringUtils::EqualsNoCase(info, "orientation")) return SLIDESHOW_EXIF_ORIENTATION;
+ else if (StringUtils::EqualsNoCase(info, "supplementalcategories")) return SLIDESHOW_IPTC_SUP_CATEGORIES;
+ else if (StringUtils::EqualsNoCase(info, "keywords")) return SLIDESHOW_IPTC_KEYWORDS;
+ else if (StringUtils::EqualsNoCase(info, "caption")) return SLIDESHOW_IPTC_CAPTION;
+ else if (StringUtils::EqualsNoCase(info, "author")) return SLIDESHOW_IPTC_AUTHOR;
+ else if (StringUtils::EqualsNoCase(info, "headline")) return SLIDESHOW_IPTC_HEADLINE;
+ else if (StringUtils::EqualsNoCase(info, "specialinstructions")) return SLIDESHOW_IPTC_SPEC_INSTR;
+ else if (StringUtils::EqualsNoCase(info, "category")) return SLIDESHOW_IPTC_CATEGORY;
+ else if (StringUtils::EqualsNoCase(info, "byline")) return SLIDESHOW_IPTC_BYLINE;
+ else if (StringUtils::EqualsNoCase(info, "bylinetitle")) return SLIDESHOW_IPTC_BYLINE_TITLE;
+ else if (StringUtils::EqualsNoCase(info, "credit")) return SLIDESHOW_IPTC_CREDIT;
+ else if (StringUtils::EqualsNoCase(info, "source")) return SLIDESHOW_IPTC_SOURCE;
+ else if (StringUtils::EqualsNoCase(info, "copyrightnotice")) return SLIDESHOW_IPTC_COPYRIGHT_NOTICE;
+ else if (StringUtils::EqualsNoCase(info, "objectname")) return SLIDESHOW_IPTC_OBJECT_NAME;
+ else if (StringUtils::EqualsNoCase(info, "city")) return SLIDESHOW_IPTC_CITY;
+ else if (StringUtils::EqualsNoCase(info, "state")) return SLIDESHOW_IPTC_STATE;
+ else if (StringUtils::EqualsNoCase(info, "country")) return SLIDESHOW_IPTC_COUNTRY;
+ else if (StringUtils::EqualsNoCase(info, "transmissionreference")) return SLIDESHOW_IPTC_TX_REFERENCE;
+ else if (StringUtils::EqualsNoCase(info, "iptcdate")) return SLIDESHOW_IPTC_DATE;
+ else if (StringUtils::EqualsNoCase(info, "urgency")) return SLIDESHOW_IPTC_URGENCY;
+ else if (StringUtils::EqualsNoCase(info, "countrycode")) return SLIDESHOW_IPTC_COUNTRY_CODE;
+ else if (StringUtils::EqualsNoCase(info, "referenceservice")) return SLIDESHOW_IPTC_REF_SERVICE;
+ else if (StringUtils::EqualsNoCase(info, "latitude")) return SLIDESHOW_EXIF_GPS_LATITUDE;
+ else if (StringUtils::EqualsNoCase(info, "longitude")) return SLIDESHOW_EXIF_GPS_LONGITUDE;
+ else if (StringUtils::EqualsNoCase(info, "altitude")) return SLIDESHOW_EXIF_GPS_ALTITUDE;
+ else if (StringUtils::EqualsNoCase(info, "timecreated")) return SLIDESHOW_IPTC_TIMECREATED;
+ else if (StringUtils::EqualsNoCase(info, "sublocation")) return SLIDESHOW_IPTC_SUBLOCATION;
+ else if (StringUtils::EqualsNoCase(info, "imagetype")) return SLIDESHOW_IPTC_IMAGETYPE;
+ return 0;
+}
+
+void CPictureInfoTag::SetInfo(const std::string &key, const std::string& value)
+{
+ int info = TranslateString(key);
+
+ switch (info)
+ {
+ case SLIDESHOW_RESOLUTION:
+ {
+ std::vector<std::string> dimension;
+ StringUtils::Tokenize(value, dimension, ",");
+ if (dimension.size() == 2)
+ {
+ m_exifInfo.Width = atoi(dimension[0].c_str());
+ m_exifInfo.Height = atoi(dimension[1].c_str());
+ m_isInfoSetExternally = true; // Set the internal state to show metadata has been set by call to SetInfo
+ }
+ break;
+ }
+ case SLIDESHOW_EXIF_DATE_TIME:
+ {
+ m_exifInfo.DateTime = value;
+ m_isInfoSetExternally = true; // Set the internal state to show metadata has been set by call to SetInfo
+ ConvertDateTime();
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+const CDateTime& CPictureInfoTag::GetDateTimeTaken() const
+{
+ return m_dateTimeTaken;
+}
+
+void CPictureInfoTag::ConvertDateTime()
+{
+ const std::string& dateTime = m_exifInfo.DateTime;
+ if (dateTime.length() >= 19 && dateTime[0] != ' ')
+ {
+ int year = atoi(dateTime.substr(0, 4).c_str());
+ int month = atoi(dateTime.substr(5, 2).c_str());
+ int day = atoi(dateTime.substr(8, 2).c_str());
+ int hour = atoi(dateTime.substr(11,2).c_str());
+ int min = atoi(dateTime.substr(14,2).c_str());
+ int sec = atoi(dateTime.substr(17,2).c_str());
+ m_dateTimeTaken.SetDateTime(year, month, day, hour, min, sec);
+ }
+}
diff --git a/xbmc/pictures/PictureInfoTag.h b/xbmc/pictures/PictureInfoTag.h
new file mode 100644
index 0000000..eff58fb
--- /dev/null
+++ b/xbmc/pictures/PictureInfoTag.h
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "XBDateTime.h"
+#include "libexif.h"
+#include "utils/IArchivable.h"
+#include "utils/ISerializable.h"
+#include "utils/ISortable.h"
+
+#include <string>
+#include <vector>
+
+namespace KODI
+{
+namespace ADDONS
+{
+class CImageDecoder;
+} /* namespace ADDONS */
+} /* namespace KODI */
+
+class CVariant;
+
+class CPictureInfoTag : public IArchivable, public ISerializable, public ISortable
+{
+ friend class KODI::ADDONS::CImageDecoder;
+
+ // Mimic structs from libexif.h but with C++ types instead of arrays
+ struct ExifInfo
+ {
+ ExifInfo() = default;
+ ExifInfo(const ExifInfo&) = default;
+ ExifInfo(ExifInfo&&) = default;
+ ExifInfo(const ExifInfo_t& other);
+
+ ExifInfo& operator=(const ExifInfo&) = default;
+ ExifInfo& operator=(ExifInfo&&) = default;
+
+ std::string CameraMake;
+ std::string CameraModel;
+ std::string DateTime;
+ int Height{};
+ int Width{};
+ int Orientation{};
+ int IsColor{};
+ int Process{};
+ int FlashUsed{};
+ float FocalLength{};
+ float ExposureTime{};
+ float ApertureFNumber{};
+ float Distance{};
+ float CCDWidth{};
+ float ExposureBias{};
+ float DigitalZoomRatio{};
+ int FocalLength35mmEquiv{};
+ int Whitebalance{};
+ int MeteringMode{};
+ int ExposureProgram{};
+ int ExposureMode{};
+ int ISOequivalent{};
+ int LightSource{};
+ int CommentsCharset{};
+ int XPCommentsCharset{};
+ std::string Comments;
+ std::string FileComment;
+ std::string XPComment;
+ std::string Description;
+
+ unsigned ThumbnailOffset{};
+ unsigned ThumbnailSize{};
+ unsigned LargestExifOffset{};
+
+ char ThumbnailAtEnd{};
+ int ThumbnailSizeOffset{};
+
+ std::vector<int> DateTimeOffsets;
+
+ int GpsInfoPresent{};
+ std::string GpsLat;
+ std::string GpsLong;
+ std::string GpsAlt;
+
+ private:
+ static std::string Convert(int charset, const char* data);
+ };
+
+ struct IPTCInfo
+ {
+ IPTCInfo() = default;
+ IPTCInfo(const IPTCInfo&) = default;
+ IPTCInfo(IPTCInfo&&) = default;
+ IPTCInfo(const IPTCInfo_t& other);
+
+ IPTCInfo& operator=(const IPTCInfo&) = default;
+ IPTCInfo& operator=(IPTCInfo&&) = default;
+
+ std::string RecordVersion;
+ std::string SupplementalCategories;
+ std::string Keywords;
+ std::string Caption;
+ std::string Author;
+ std::string Headline;
+ std::string SpecialInstructions;
+ std::string Category;
+ std::string Byline;
+ std::string BylineTitle;
+ std::string Credit;
+ std::string Source;
+ std::string CopyrightNotice;
+ std::string ObjectName;
+ std::string City;
+ std::string State;
+ std::string Country;
+ std::string TransmissionReference;
+ std::string Date;
+ std::string Urgency;
+ std::string ReferenceService;
+ std::string CountryCode;
+ std::string TimeCreated;
+ std::string SubLocation;
+ std::string ImageType;
+ };
+
+public:
+ CPictureInfoTag() { Reset(); }
+ virtual ~CPictureInfoTag() = default;
+ void Reset();
+ void Archive(CArchive& ar) override;
+ void Serialize(CVariant& value) const override;
+ void ToSortable(SortItem& sortable, Field field) const override;
+ const std::string GetInfo(int info) const;
+
+ bool Loaded() const { return m_isLoaded; }
+ bool Load(const std::string &path);
+
+ void SetInfo(const std::string& key, const std::string& value);
+
+ /**
+ * GetDateTimeTaken() -- Returns the EXIF DateTimeOriginal for current picture
+ *
+ * The exif library returns DateTimeOriginal if available else the other
+ * DateTime tags. See libexif CExifParse::ProcessDir for details.
+ */
+ const CDateTime& GetDateTimeTaken() const;
+private:
+ static int TranslateString(const std::string &info);
+
+ ExifInfo m_exifInfo;
+ IPTCInfo m_iptcInfo;
+ bool m_isLoaded; // Set to true if metadata has been loaded from the picture file successfully
+ bool m_isInfoSetExternally; // Set to true if metadata has been set by an external call to SetInfo
+ CDateTime m_dateTimeTaken;
+ void ConvertDateTime();
+};
+
diff --git a/xbmc/pictures/PictureScalingAlgorithm.cpp b/xbmc/pictures/PictureScalingAlgorithm.cpp
new file mode 100644
index 0000000..65cfcd9
--- /dev/null
+++ b/xbmc/pictures/PictureScalingAlgorithm.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include <algorithm>
+
+extern "C" {
+#include <libswscale/swscale.h>
+}
+
+#include "PictureScalingAlgorithm.h"
+#include "utils/StringUtils.h"
+
+CPictureScalingAlgorithm::Algorithm CPictureScalingAlgorithm::Default = CPictureScalingAlgorithm::Bicubic;
+
+CPictureScalingAlgorithm::AlgorithmMap CPictureScalingAlgorithm::m_algorithms = {
+ { FastBilinear, { "fast_bilinear", SWS_FAST_BILINEAR } },
+ { Bilinear, { "bilinear", SWS_BILINEAR } },
+ { Bicubic, { "bicubic", SWS_BICUBIC } },
+ { Experimental, { "experimental", SWS_X } },
+ { NearestNeighbor, { "nearest_neighbor", SWS_POINT } },
+ { AveragingArea, { "averaging_area", SWS_AREA } },
+ { Bicublin, { "bicublin", SWS_BICUBLIN } },
+ { Gaussian, { "gaussian", SWS_GAUSS } },
+ { Sinc, { "sinc", SWS_SINC } },
+ { Lanczos, { "lanczos", SWS_LANCZOS } },
+ { BicubicSpline, { "bicubic_spline", SWS_SPLINE } },
+};
+
+CPictureScalingAlgorithm::Algorithm CPictureScalingAlgorithm::FromString(const std::string& scalingAlgorithm)
+{
+ const auto& algorithm = std::find_if(m_algorithms.begin(), m_algorithms.end(),
+ [&scalingAlgorithm](const std::pair<Algorithm, ScalingAlgorithm>& algo) { return StringUtils::EqualsNoCase(algo.second.name, scalingAlgorithm); });
+ if (algorithm != m_algorithms.end())
+ return algorithm->first;
+
+ return NoAlgorithm;
+}
+
+std::string CPictureScalingAlgorithm::ToString(Algorithm scalingAlgorithm)
+{
+ const auto& algorithm = m_algorithms.find(scalingAlgorithm);
+ if (algorithm != m_algorithms.end())
+ return algorithm->second.name;
+
+ return "";
+}
+
+int CPictureScalingAlgorithm::ToSwscale(const std::string& scalingAlgorithm)
+{
+ return ToSwscale(FromString(scalingAlgorithm));
+}
+
+int CPictureScalingAlgorithm::ToSwscale(Algorithm scalingAlgorithm)
+{
+ const auto& algorithm = m_algorithms.find(scalingAlgorithm);
+ if (algorithm != m_algorithms.end())
+ return algorithm->second.swscale;
+
+ return ToSwscale(Default);
+}
diff --git a/xbmc/pictures/PictureScalingAlgorithm.h b/xbmc/pictures/PictureScalingAlgorithm.h
new file mode 100644
index 0000000..d5aa543
--- /dev/null
+++ b/xbmc/pictures/PictureScalingAlgorithm.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <map>
+#include <string>
+
+class CPictureScalingAlgorithm
+{
+public:
+ typedef enum Algorithm
+ {
+ NoAlgorithm,
+ FastBilinear,
+ Bilinear,
+ Bicubic,
+ Experimental,
+ NearestNeighbor,
+ AveragingArea,
+ Bicublin,
+ Gaussian,
+ Sinc,
+ Lanczos,
+ BicubicSpline
+ } Algorithm;
+
+ static Algorithm Default;
+
+ static Algorithm FromString(const std::string& scalingAlgorithm);
+ static std::string ToString(Algorithm scalingAlgorithm);
+ static int ToSwscale(const std::string& scalingAlgorithm);
+ static int ToSwscale(Algorithm scalingAlgorithm);
+
+private:
+ CPictureScalingAlgorithm();
+
+ typedef struct ScalingAlgorithm
+ {
+ std::string name;
+ int swscale;
+ } ScalingAlgorithm;
+
+ typedef std::map<CPictureScalingAlgorithm::Algorithm, CPictureScalingAlgorithm::ScalingAlgorithm> AlgorithmMap;
+ static AlgorithmMap m_algorithms;
+};
diff --git a/xbmc/pictures/PictureThumbLoader.cpp b/xbmc/pictures/PictureThumbLoader.cpp
new file mode 100644
index 0000000..0cdd56c
--- /dev/null
+++ b/xbmc/pictures/PictureThumbLoader.cpp
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PictureThumbLoader.h"
+
+#include "FileItem.h"
+#include "GUIUserMessages.h"
+#include "Picture.h"
+#include "ServiceBroker.h"
+#include "TextureCache.h"
+#include "URL.h"
+#include "filesystem/Directory.h"
+#include "filesystem/MultiPathDirectory.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/FileExtensionProvider.h"
+#include "utils/FileUtils.h"
+#include "utils/URIUtils.h"
+#include "video/VideoThumbLoader.h"
+
+using namespace XFILE;
+
+CPictureThumbLoader::CPictureThumbLoader() : CThumbLoader(), CJobQueue(true, 1, CJob::PRIORITY_LOW_PAUSABLE)
+{
+ m_regenerateThumbs = false;
+}
+
+CPictureThumbLoader::~CPictureThumbLoader()
+{
+ StopThread();
+}
+
+void CPictureThumbLoader::OnLoaderFinish()
+{
+ m_regenerateThumbs = false;
+ CThumbLoader::OnLoaderFinish();
+}
+
+bool CPictureThumbLoader::LoadItem(CFileItem* pItem)
+{
+ bool result = LoadItemCached(pItem);
+ result |= LoadItemLookup(pItem);
+
+ return result;
+}
+
+bool CPictureThumbLoader::LoadItemCached(CFileItem* pItem)
+{
+ if (pItem->m_bIsShareOrDrive
+ || pItem->IsParentFolder())
+ return false;
+
+ if (pItem->HasArt("thumb") && m_regenerateThumbs)
+ {
+ CServiceBroker::GetTextureCache()->ClearCachedImage(pItem->GetArt("thumb"));
+ if (m_textureDatabase->Open())
+ {
+ m_textureDatabase->ClearTextureForPath(pItem->GetPath(), "thumb");
+ m_textureDatabase->Close();
+ }
+ pItem->SetArt("thumb", "");
+ }
+
+ std::string thumb;
+ if (pItem->IsPicture() && !pItem->IsZIP() && !pItem->IsRAR() && !pItem->IsCBZ() && !pItem->IsCBR() && !pItem->IsPlayList())
+ { // load the thumb from the image file
+ thumb = pItem->HasArt("thumb") ? pItem->GetArt("thumb") : CTextureUtils::GetWrappedThumbURL(pItem->GetPath());
+ }
+ else if (pItem->IsVideo() && !pItem->IsZIP() && !pItem->IsRAR() && !pItem->IsCBZ() && !pItem->IsCBR() && !pItem->IsPlayList())
+ { // video
+ CVideoThumbLoader loader;
+ if (!loader.FillThumb(*pItem))
+ {
+ std::string thumbURL = CVideoThumbLoader::GetEmbeddedThumbURL(*pItem);
+ if (CServiceBroker::GetTextureCache()->HasCachedImage(thumbURL))
+ {
+ thumb = thumbURL;
+ }
+ else if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYVIDEOS_EXTRACTTHUMB) && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYVIDEOS_EXTRACTFLAGS))
+ {
+ CFileItem item(*pItem);
+ CThumbExtractor* extract = new CThumbExtractor(item, pItem->GetPath(), true, thumbURL);
+ AddJob(extract);
+ thumb.clear();
+ }
+ }
+ }
+ else if (!pItem->HasArt("thumb"))
+ { // folder, zip, cbz, rar, cbr, playlist may have a previously cached image
+ thumb = GetCachedImage(*pItem, "thumb");
+ }
+ if (!thumb.empty())
+ {
+ CServiceBroker::GetTextureCache()->BackgroundCacheImage(thumb);
+ pItem->SetArt("thumb", thumb);
+ }
+ pItem->FillInDefaultIcon();
+ return true;
+}
+
+bool CPictureThumbLoader::LoadItemLookup(CFileItem* pItem)
+{
+ return false;
+}
+
+void CPictureThumbLoader::OnJobComplete(unsigned int jobID, bool success, CJob* job)
+{
+ if (success)
+ {
+ CThumbExtractor* loader = static_cast<CThumbExtractor*>(job);
+ loader->m_item.SetPath(loader->m_listpath);
+ CFileItemPtr pItem(new CFileItem(loader->m_item));
+ CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, pItem);
+ CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
+ }
+ CJobQueue::OnJobComplete(jobID, success, job);
+}
+
+void CPictureThumbLoader::ProcessFoldersAndArchives(CFileItem *pItem)
+{
+ if (pItem->HasArt("thumb"))
+ return;
+
+ CTextureDatabase db;
+ db.Open();
+ if (pItem->IsCBR() || pItem->IsCBZ())
+ {
+ std::string strTBN(URIUtils::ReplaceExtension(pItem->GetPath(),".tbn"));
+ if (CFileUtils::Exists(strTBN))
+ {
+ db.SetTextureForPath(pItem->GetPath(), "thumb", strTBN);
+ CServiceBroker::GetTextureCache()->BackgroundCacheImage(strTBN);
+ pItem->SetArt("thumb", strTBN);
+ return;
+ }
+ }
+ if ((pItem->m_bIsFolder || pItem->IsCBR() || pItem->IsCBZ()) && !pItem->m_bIsShareOrDrive
+ && !pItem->IsParentFolder() && !pItem->IsPath("add"))
+ {
+ // first check for a folder.jpg
+ std::string thumb = "folder.jpg";
+ CURL pathToUrl = pItem->GetURL();
+ if (pItem->IsCBR())
+ {
+ pathToUrl = URIUtils::CreateArchivePath("rar",pItem->GetURL(),"");
+ thumb = "cover.jpg";
+ }
+ if (pItem->IsCBZ())
+ {
+ pathToUrl = URIUtils::CreateArchivePath("zip",pItem->GetURL(),"");
+ thumb = "cover.jpg";
+ }
+ if (pItem->IsMultiPath())
+ pathToUrl = CURL(CMultiPathDirectory::GetFirstPath(pItem->GetPath()));
+ thumb = URIUtils::AddFileToFolder(pathToUrl.Get(), thumb);
+ if (CFileUtils::Exists(thumb))
+ {
+ db.SetTextureForPath(pItem->GetPath(), "thumb", thumb);
+ CServiceBroker::GetTextureCache()->BackgroundCacheImage(thumb);
+ pItem->SetArt("thumb", thumb);
+ return;
+ }
+ if (!pItem->IsPlugin())
+ {
+ // we load the directory, grab 4 random thumb files (if available) and then generate
+ // the thumb.
+
+ CFileItemList items;
+
+ CDirectory::GetDirectory(pathToUrl, items, CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(), DIR_FLAG_NO_FILE_DIRS);
+
+ // create the folder thumb by choosing 4 random thumbs within the folder and putting
+ // them into one thumb.
+ // count the number of images
+ for (int i=0; i < items.Size();)
+ {
+ if (!items[i]->IsPicture() || items[i]->IsZIP() || items[i]->IsRAR() || items[i]->IsPlayList())
+ {
+ items.Remove(i);
+ }
+ else
+ i++;
+ }
+
+ if (items.IsEmpty())
+ {
+ if (pItem->IsCBZ() || pItem->IsCBR())
+ {
+ CDirectory::GetDirectory(pathToUrl, items, CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(), DIR_FLAG_NO_FILE_DIRS);
+ for (int i=0;i<items.Size();++i)
+ {
+ CFileItemPtr item = items[i];
+ if (item->m_bIsFolder)
+ {
+ ProcessFoldersAndArchives(item.get());
+ pItem->SetArt("thumb", items[i]->GetArt("thumb"));
+ pItem->SetArt("icon", items[i]->GetArt("icon"));
+ return;
+ }
+ }
+ }
+ return; // no images in this folder
+ }
+
+ // randomize them
+ items.Randomize();
+
+ if (items.Size() < 4 || pItem->IsCBR() || pItem->IsCBZ())
+ { // less than 4 items, so just grab the first thumb
+ items.Sort(SortByLabel, SortOrderAscending);
+ std::string thumb = CTextureUtils::GetWrappedThumbURL(items[0]->GetPath());
+ db.SetTextureForPath(pItem->GetPath(), "thumb", thumb);
+ CServiceBroker::GetTextureCache()->BackgroundCacheImage(thumb);
+ pItem->SetArt("thumb", thumb);
+ }
+ else
+ {
+ // ok, now we've got the files to get the thumbs from, lets create it...
+ // we basically load the 4 images and combine them
+ std::vector<std::string> files;
+ files.reserve(4);
+ for (int thumb = 0; thumb < 4; thumb++)
+ files.push_back(items[thumb]->GetPath());
+ std::string thumb = CTextureUtils::GetWrappedImageURL(pItem->GetPath(), "picturefolder");
+ std::string relativeCacheFile = CTextureCache::GetCacheFile(thumb) + ".png";
+ if (CPicture::CreateTiledThumb(files, CTextureCache::GetCachedPath(relativeCacheFile)))
+ {
+ CTextureDetails details;
+ details.file = relativeCacheFile;
+ details.width = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_imageRes;
+ details.height = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_imageRes;
+ CServiceBroker::GetTextureCache()->AddCachedTexture(thumb, details);
+ db.SetTextureForPath(pItem->GetPath(), "thumb", thumb);
+ pItem->SetArt("thumb", CTextureCache::GetCachedPath(relativeCacheFile));
+ }
+ }
+ }
+ // refill in the icon to get it to update
+ pItem->FillInDefaultIcon();
+ }
+}
diff --git a/xbmc/pictures/PictureThumbLoader.h b/xbmc/pictures/PictureThumbLoader.h
new file mode 100644
index 0000000..664968e
--- /dev/null
+++ b/xbmc/pictures/PictureThumbLoader.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "ThumbLoader.h"
+#include "utils/JobManager.h"
+
+class CPictureThumbLoader : public CThumbLoader, public CJobQueue
+{
+public:
+ CPictureThumbLoader();
+ ~CPictureThumbLoader() override;
+
+ bool LoadItem(CFileItem* pItem) override;
+ bool LoadItemCached(CFileItem* pItem) override;
+ bool LoadItemLookup(CFileItem* pItem) override;
+ void SetRegenerateThumbs(bool regenerate) { m_regenerateThumbs = regenerate; }
+ static void ProcessFoldersAndArchives(CFileItem *pItem);
+
+ /*!
+ \brief Callback from CThumbExtractor on completion of a generated image
+
+ Performs the callbacks and updates the GUI.
+
+ \sa CImageLoader, IJobCallback
+ */
+ void OnJobComplete(unsigned int jobID, bool success, CJob *job) override;
+
+protected:
+ void OnLoaderFinish() override;
+
+private:
+ bool m_regenerateThumbs;
+};
diff --git a/xbmc/pictures/SlideShowPicture.cpp b/xbmc/pictures/SlideShowPicture.cpp
new file mode 100644
index 0000000..63ec8b9
--- /dev/null
+++ b/xbmc/pictures/SlideShowPicture.cpp
@@ -0,0 +1,1016 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "SlideShowPicture.h"
+
+#include "ServiceBroker.h"
+#include "guilib/Texture.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/Settings.h"
+#include "settings/SettingsComponent.h"
+#include "windowing/GraphicContext.h"
+#include "windowing/WinSystem.h"
+
+#include <mutex>
+
+#ifndef _USE_MATH_DEFINES
+#define _USE_MATH_DEFINES
+#endif
+#include <math.h>
+
+#if defined(HAS_GL)
+#include "rendering/gl/RenderSystemGL.h"
+#include "utils/GLUtils.h"
+#elif defined(HAS_GLES)
+#include "rendering/gles/RenderSystemGLES.h"
+#include "utils/GLUtils.h"
+#elif defined(TARGET_WINDOWS)
+#include "guilib/TextureDX.h"
+#include "rendering/dx/DeviceResources.h"
+#include "rendering/dx/RenderContext.h"
+
+#include <DirectXMath.h>
+using namespace DirectX;
+using namespace Microsoft::WRL;
+#endif
+
+#include <cstddef>
+
+#define IMMEDIATE_TRANSITION_TIME 20
+
+#define PICTURE_MOVE_AMOUNT 0.02f
+#define PICTURE_MOVE_AMOUNT_ANALOG 0.01f
+#define PICTURE_VIEW_BOX_COLOR 0xffffff00 // YELLOW
+#define PICTURE_VIEW_BOX_BACKGROUND 0xff000000 // BLACK
+
+#define FPS 25
+
+static float zoomamount[10] = { 1.0f, 1.2f, 1.5f, 2.0f, 2.8f, 4.0f, 6.0f, 9.0f, 13.5f, 20.0f };
+
+CSlideShowPic::CSlideShowPic() : m_pImage(nullptr)
+{
+ m_bIsLoaded = false;
+ m_bIsFinished = false;
+ m_bDrawNextImage = false;
+ m_bTransitionImmediately = false;
+
+ m_bCanMoveHorizontally = false;
+ m_bCanMoveVertically = false;
+}
+
+CSlideShowPic::~CSlideShowPic()
+{
+ Close();
+}
+
+void CSlideShowPic::Close()
+{
+ std::unique_lock<CCriticalSection> lock(m_textureAccess);
+ m_pImage.reset();
+ m_bIsLoaded = false;
+ m_bIsFinished = false;
+ m_bDrawNextImage = false;
+ m_bTransitionImmediately = false;
+ m_bIsDirty = true;
+ m_alpha = 0;
+#ifdef HAS_DX
+ m_vb = nullptr;
+#endif
+}
+
+void CSlideShowPic::Reset(DISPLAY_EFFECT dispEffect, TRANSITION_EFFECT transEffect)
+{
+ std::unique_lock<CCriticalSection> lock(m_textureAccess);
+ if (m_pImage)
+ SetTexture_Internal(m_iSlideNumber, std::move(m_pImage), dispEffect, transEffect);
+ else
+ Close();
+}
+
+bool CSlideShowPic::DisplayEffectNeedChange(DISPLAY_EFFECT newDispEffect) const
+{
+ if (m_displayEffect == newDispEffect)
+ return false;
+ if (newDispEffect == EFFECT_RANDOM && m_displayEffect != EFFECT_NONE && m_displayEffect != EFFECT_NO_TIMEOUT)
+ return false;
+ return true;
+}
+
+void CSlideShowPic::SetTexture(int iSlideNumber,
+ std::unique_ptr<CTexture> pTexture,
+ DISPLAY_EFFECT dispEffect,
+ TRANSITION_EFFECT transEffect)
+{
+ std::unique_lock<CCriticalSection> lock(m_textureAccess);
+ Close();
+ SetTexture_Internal(iSlideNumber, std::move(pTexture), dispEffect, transEffect);
+}
+
+void CSlideShowPic::SetTexture_Internal(int iSlideNumber,
+ std::unique_ptr<CTexture> pTexture,
+ DISPLAY_EFFECT dispEffect,
+ TRANSITION_EFFECT transEffect)
+{
+ std::unique_lock<CCriticalSection> lock(m_textureAccess);
+ m_bPause = false;
+ m_bNoEffect = false;
+ m_bTransitionImmediately = false;
+ m_iSlideNumber = iSlideNumber;
+
+ m_bIsDirty = true;
+ m_pImage = std::move(pTexture);
+ m_fWidth = static_cast<float>(m_pImage->GetWidth());
+ m_fHeight = static_cast<float>(m_pImage->GetHeight());
+ if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_SLIDESHOW_HIGHQUALITYDOWNSCALING))
+ { // activate mipmapping when high quality downscaling is 'on'
+ m_pImage->SetMipmapping();
+ }
+ // reset our counter
+ m_iCounter = 0;
+ // initialize our transition effect
+ m_transitionStart.type = transEffect;
+ m_transitionStart.start = 0;
+
+ // initialize our display effect
+ if (dispEffect == EFFECT_RANDOM)
+ {
+ if (((m_fWidth / m_fHeight) > 1.9f) || ((m_fHeight / m_fWidth) > 1.9f))
+ m_displayEffect = EFFECT_PANORAMA;
+ else
+ m_displayEffect = (DISPLAY_EFFECT)((rand() % (EFFECT_RANDOM - 1)) + 1);
+ }
+ else
+ m_displayEffect = dispEffect;
+
+ // the +1's make sure it actually occurs
+ float fadeTime = 0.2f;
+ if (m_displayEffect != EFFECT_NO_TIMEOUT)
+ fadeTime = std::min(0.2f*CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_SLIDESHOW_STAYTIME), 3.0f);
+ m_transitionStart.length = (int)(CServiceBroker::GetWinSystem()->GetGfxContext().GetFPS() * fadeTime); // transition time in frames
+ m_transitionEnd.type = transEffect;
+ m_transitionEnd.length = m_transitionStart.length;
+ m_transitionTemp.type = TRANSITION_NONE;
+ m_fTransitionAngle = 0;
+ m_fTransitionZoom = 0;
+ m_fAngle = 0.0f;
+ if (m_pImage->GetOrientation() == 7)
+ { // rotate to 270 degrees
+ m_fAngle = 270.0f;
+ }
+ if (m_pImage->GetOrientation() == 2)
+ { // rotate to 180 degrees
+ m_fAngle = 180.0f;
+ }
+ if (m_pImage->GetOrientation() == 5)
+ { // rotate to 90 degrees
+ m_fAngle = 90.0f;
+ }
+ m_fZoomAmount = 1;
+ m_fZoomLeft = 0;
+ m_fZoomTop = 0;
+ m_fPosX = m_fPosY = 0.0f;
+ m_fPosZ = 1.0f;
+ m_fVelocityX = m_fVelocityY = m_fVelocityZ = 0.0f;
+ int iFrames = std::max((int)(CServiceBroker::GetWinSystem()->GetGfxContext().GetFPS() * CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_SLIDESHOW_STAYTIME)), 1);
+ if (m_displayEffect == EFFECT_PANORAMA)
+ {
+ RESOLUTION_INFO res = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo();
+ float fScreenWidth = (float)res.Overscan.right - res.Overscan.left;
+ float fScreenHeight = (float)res.Overscan.bottom - res.Overscan.top;
+
+ if (m_fWidth > m_fHeight)
+ {
+ iFrames = (int)(iFrames * (m_fWidth - m_fHeight) / m_fHeight);
+ m_iTotalFrames = m_transitionStart.length + m_transitionEnd.length + iFrames;
+
+ m_fPosX = 0.5f - (fScreenWidth / fScreenHeight) * (m_fHeight / m_fWidth) * 0.5f;
+ if (rand() % 2)
+ m_fPosX = -m_fPosX;
+ m_fVelocityX = -m_fPosX * 2.0f / m_iTotalFrames;
+ }
+ else
+ {
+ iFrames = (int)(iFrames * (m_fHeight - (0.5f * m_fWidth)) / m_fWidth);
+ m_iTotalFrames = m_transitionStart.length + m_transitionEnd.length + iFrames;
+
+ m_fPosY = 0.5f - (fScreenHeight / fScreenWidth) * (m_fWidth / m_fHeight) * 0.5f;
+ if (rand() % 2)
+ m_fPosY = -m_fPosY;
+ m_fVelocityY = -m_fPosY * 2.0f / m_iTotalFrames;
+ }
+ }
+ else
+ {
+ m_iTotalFrames = m_transitionStart.length + m_transitionEnd.length + iFrames;
+
+ if (m_displayEffect == EFFECT_FLOAT)
+ {
+ // Calculate start and end positions
+ // choose a random direction
+ float angle = (rand() % 1000) / 1000.0f * 2 * (float)M_PI;
+ m_fPosX = cos(angle) * CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_slideshowPanAmount * m_iTotalFrames * 0.00005f;
+ m_fPosY = sin(angle) * CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_slideshowPanAmount * m_iTotalFrames * 0.00005f;
+ m_fVelocityX = -m_fPosX * 2.0f / m_iTotalFrames;
+ m_fVelocityY = -m_fPosY * 2.0f / m_iTotalFrames;
+ }
+ else if (m_displayEffect == EFFECT_ZOOM)
+ {
+ m_fPosZ = 1.0f;
+ m_fVelocityZ = 0.0001f * CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_slideshowZoomAmount;
+ }
+ }
+
+ m_transitionEnd.start = m_transitionStart.length + iFrames;
+
+ m_bIsFinished = false;
+ m_bDrawNextImage = false;
+ m_bIsLoaded = true;
+}
+
+void CSlideShowPic::SetOriginalSize(int iOriginalWidth, int iOriginalHeight, bool bFullSize)
+{
+ m_iOriginalWidth = iOriginalWidth;
+ m_iOriginalHeight = iOriginalHeight;
+ m_bFullSize = bFullSize;
+}
+
+int CSlideShowPic::GetOriginalWidth()
+{
+ int iAngle = (int)(m_fAngle / 90.0f + 0.4f);
+ if (iAngle % 2)
+ return m_iOriginalHeight;
+ else
+ return m_iOriginalWidth;
+}
+
+int CSlideShowPic::GetOriginalHeight()
+{
+ int iAngle = (int)(m_fAngle / 90.0f + 0.4f);
+ if (iAngle % 2)
+ return m_iOriginalWidth;
+ else
+ return m_iOriginalHeight;
+}
+
+void CSlideShowPic::UpdateTexture(std::unique_ptr<CTexture> pTexture)
+{
+ std::unique_lock<CCriticalSection> lock(m_textureAccess);
+ m_pImage = std::move(pTexture);
+ m_fWidth = static_cast<float>(m_pImage->GetWidth());
+ m_fHeight = static_cast<float>(m_pImage->GetHeight());
+ m_bIsDirty = true;
+}
+
+static CRect GetRectangle(const float x[4], const float y[4])
+{
+ CRect rect;
+ rect.x1 = *std::min_element(x, x+4);
+ rect.y1 = *std::min_element(y, y+4);
+ rect.x2 = *std::max_element(x, x+4);
+ rect.y2 = *std::max_element(y, y+4);
+ return rect;
+}
+
+void CSlideShowPic::UpdateVertices(float cur_x[4], float cur_y[4], const float new_x[4], const float new_y[4], CDirtyRegionList &dirtyregions)
+{
+ const size_t count = sizeof(float)*4;
+ if(memcmp(cur_x, new_x, count)
+ || memcmp(cur_y, new_y, count)
+ || m_bIsDirty)
+ {
+ dirtyregions.push_back(CDirtyRegion(GetRectangle(cur_x, cur_y)));
+ dirtyregions.push_back(CDirtyRegion(GetRectangle(new_x, new_y)));
+ memcpy(cur_x, new_x, count);
+ memcpy(cur_y, new_y, count);
+ }
+}
+
+void CSlideShowPic::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions)
+{
+ if (!m_pImage || !m_bIsLoaded || m_bIsFinished) return ;
+ UTILS::COLOR::Color alpha = m_alpha;
+ if (m_iCounter <= m_transitionStart.length)
+ { // do start transition
+ if (m_transitionStart.type == CROSSFADE)
+ { // fade in at 1x speed
+ alpha = (UTILS::COLOR::Color)((float)m_iCounter / (float)m_transitionStart.length * 255.0f);
+ }
+ else if (m_transitionStart.type == FADEIN_FADEOUT)
+ { // fade in at 2x speed, then keep solid
+ alpha =
+ (UTILS::COLOR::Color)((float)m_iCounter / (float)m_transitionStart.length * 255.0f * 2);
+ if (alpha > 255) alpha = 255;
+ }
+ else // m_transitionEffect == TRANSITION_NONE
+ {
+ alpha = 0xFF; // opaque
+ }
+ }
+ bool bPaused = m_bPause | (m_fZoomAmount != 1.0f);
+ // check if we're doing a temporary effect (such as rotate + zoom)
+ if (m_transitionTemp.type != TRANSITION_NONE)
+ {
+ bPaused = true;
+ if (m_iCounter >= m_transitionTemp.start)
+ {
+ if (m_iCounter >= m_transitionTemp.start + m_transitionTemp.length)
+ { // we're finished this transition
+ if (m_transitionTemp.type == TRANSITION_ZOOM)
+ { // correct for any introduced inaccuracies.
+ int i;
+ for (i = 0; i < 10; i++)
+ {
+ if (fabs(m_fZoomAmount - zoomamount[i]) < 0.01f * zoomamount[i])
+ {
+ m_fZoomAmount = zoomamount[i];
+ break;
+ }
+ }
+ m_bNoEffect = (m_fZoomAmount != 1.0f); // turn effect rendering back on.
+ }
+ m_transitionTemp.type = TRANSITION_NONE;
+ }
+ else
+ {
+ if (m_transitionTemp.type == TRANSITION_ROTATE)
+ m_fAngle += m_fTransitionAngle;
+ if (m_transitionTemp.type == TRANSITION_ZOOM)
+ m_fZoomAmount += m_fTransitionZoom;
+ }
+ }
+ }
+ // now just display
+ if (!m_bNoEffect && !bPaused)
+ {
+ if (m_displayEffect == EFFECT_PANORAMA)
+ {
+ m_fPosX += m_fVelocityX;
+ m_fPosY += m_fVelocityY;
+ }
+ else if (m_displayEffect == EFFECT_FLOAT)
+ {
+ m_fPosX += m_fVelocityX;
+ m_fPosY += m_fVelocityY;
+ float fMoveAmount = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_slideshowPanAmount * m_iTotalFrames * 0.0001f;
+ if (m_fPosX > fMoveAmount)
+ {
+ m_fPosX = fMoveAmount;
+ m_fVelocityX = -m_fVelocityX;
+ }
+ if (m_fPosX < -fMoveAmount)
+ {
+ m_fPosX = -fMoveAmount;
+ m_fVelocityX = -m_fVelocityX;
+ }
+ if (m_fPosY > fMoveAmount)
+ {
+ m_fPosY = fMoveAmount;
+ m_fVelocityY = -m_fVelocityY;
+ }
+ if (m_fPosY < -fMoveAmount)
+ {
+ m_fPosY = -fMoveAmount;
+ m_fVelocityY = -m_fVelocityY;
+ }
+ }
+ else if (m_displayEffect == EFFECT_ZOOM)
+ {
+ m_fPosZ += m_fVelocityZ;
+/* if (m_fPosZ > 1.0f + 0.01f*CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt("Slideshow.ZoomAmount"))
+ {
+ m_fPosZ = 1.0f + 0.01f * CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt("Slideshow.ZoomAmount");
+ m_fVelocityZ = -m_fVelocityZ;
+ }
+ if (m_fPosZ < 1.0f)
+ {
+ m_fPosZ = 1.0f;
+ m_fVelocityZ = -m_fVelocityZ;
+ }*/
+ }
+ }
+ if (m_displayEffect != EFFECT_NO_TIMEOUT && bPaused && !m_bTransitionImmediately)
+ { // paused - increment the last transition start time
+ m_transitionEnd.start++;
+ }
+ if (m_iCounter >= m_transitionEnd.start)
+ { // do end transition
+// CLog::Log(LOGDEBUG,"Transitioning");
+ m_bDrawNextImage = true;
+ if (m_transitionEnd.type == CROSSFADE)
+ { // fade out at 1x speed
+ alpha = 255 - (UTILS::COLOR::Color)((float)(m_iCounter - m_transitionEnd.start) /
+ (float)m_transitionEnd.length * 255.0f);
+ }
+ else if (m_transitionEnd.type == FADEIN_FADEOUT)
+ { // keep solid, then fade out at 2x speed
+ alpha = (UTILS::COLOR::Color)(
+ (float)(m_transitionEnd.length - m_iCounter + m_transitionEnd.start) /
+ (float)m_transitionEnd.length * 255.0f * 2);
+ if (alpha > 255) alpha = 255;
+ }
+ else // m_transitionEffect == TRANSITION_NONE
+ {
+ alpha = 0xFF; // opaque
+ }
+ }
+ if (alpha != m_alpha)
+ {
+ m_alpha = alpha;
+ m_bIsDirty = true;
+ }
+ if (m_displayEffect != EFFECT_NO_TIMEOUT || m_iCounter < m_transitionStart.length || m_iCounter >= m_transitionEnd.start || (m_iCounter >= m_transitionTemp.start && m_iCounter < m_transitionTemp.start + m_transitionTemp.length))
+ {
+ /* this really annoying. there's non-stop logging when viewing a pic outside of the slideshow
+ if (m_displayEffect == EFFECT_NO_TIMEOUT)
+ CLog::Log(LOGDEBUG, "Incrementing counter ({}) while not in slideshow (startlength={},endstart={},endlength={})", m_iCounter, m_transitionStart.length, m_transitionEnd.start, m_transitionEnd.length);
+ */
+ m_iCounter++;
+ }
+ if (m_iCounter > m_transitionEnd.start + m_transitionEnd.length)
+ m_bIsFinished = true;
+
+ RESOLUTION_INFO info = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo();
+
+ // calculate where we should render (and how large it should be)
+ // calculate aspect ratio correction factor
+ float fOffsetX = (float)info.Overscan.left;
+ float fOffsetY = (float)info.Overscan.top;
+ float fScreenWidth = (float)info.Overscan.right - info.Overscan.left;
+ float fScreenHeight = (float)info.Overscan.bottom - info.Overscan.top;
+ float fPixelRatio = info.fPixelRatio;
+
+ // Rotate the image as needed
+ float x[4];
+ float y[4];
+ float si = sin(m_fAngle / 180.0f * static_cast<float>(M_PI));
+ float co = cos(m_fAngle / 180.0f * static_cast<float>(M_PI));
+ x[0] = -m_fWidth * co + m_fHeight * si;
+ y[0] = -m_fWidth * si - m_fHeight * co;
+ x[1] = m_fWidth * co + m_fHeight * si;
+ y[1] = m_fWidth * si - m_fHeight * co;
+ x[2] = m_fWidth * co - m_fHeight * si;
+ y[2] = m_fWidth * si + m_fHeight * co;
+ x[3] = -m_fWidth * co - m_fHeight * si;
+ y[3] = -m_fWidth * si + m_fHeight * co;
+
+ // calculate our scale amounts
+ float fSourceAR = m_fWidth / m_fHeight;
+ float fSourceInvAR = 1 / fSourceAR;
+ float fAR = si * si * (fSourceInvAR - fSourceAR) + fSourceAR;
+
+ //float fOutputFrameAR = fAR / fPixelRatio;
+
+ float fScaleNorm = fScreenWidth / m_fWidth;
+ float fScaleInv = fScreenWidth / m_fHeight;
+
+ bool bFillScreen = false;
+ float fComp = 1.0f + 0.01f * CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_slideshowBlackBarCompensation;
+ float fScreenRatio = fScreenWidth / fScreenHeight * fPixelRatio;
+ // work out if we should be compensating the zoom to minimize blackbars
+ // we should compute this based on the % of black bars on screen perhaps??
+ //! @todo change m_displayEffect != EFFECT_NO_TIMEOUT to whether we're running the slideshow
+ if (m_displayEffect != EFFECT_NO_TIMEOUT && m_displayEffect != EFFECT_NONE && fScreenRatio < fSourceAR * fComp && fSourceAR < fScreenRatio * fComp)
+ bFillScreen = true;
+ if ((!bFillScreen && fScreenWidth*fPixelRatio > fScreenHeight*fSourceAR) || (bFillScreen && fScreenWidth*fPixelRatio < fScreenHeight*fSourceAR))
+ fScaleNorm = fScreenHeight / (m_fHeight * fPixelRatio);
+ bFillScreen = false;
+ if (m_displayEffect != EFFECT_NO_TIMEOUT && m_displayEffect != EFFECT_NONE && fScreenRatio < fSourceInvAR * fComp && fSourceInvAR < fScreenRatio * fComp)
+ bFillScreen = true;
+ if ((!bFillScreen && fScreenWidth*fPixelRatio > fScreenHeight*fSourceInvAR) || (bFillScreen && fScreenWidth*fPixelRatio < fScreenHeight*fSourceInvAR))
+ fScaleInv = fScreenHeight / (m_fWidth * fPixelRatio);
+
+ float fScale = si * si * (fScaleInv - fScaleNorm) + fScaleNorm;
+ // scale if we need to due to the effect we're using
+ if (m_displayEffect == EFFECT_PANORAMA)
+ {
+ if (m_fWidth > m_fHeight)
+ fScale *= m_fWidth / fScreenWidth * fScreenHeight / m_fHeight;
+ else
+ fScale *= m_fHeight / fScreenHeight * fScreenWidth / m_fWidth;
+ }
+ if (m_displayEffect == EFFECT_FLOAT)
+ fScale *= (1.0f + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_slideshowPanAmount * m_iTotalFrames * 0.0001f);
+ if (m_displayEffect == EFFECT_ZOOM)
+ fScale *= m_fPosZ;
+ // zoom image
+ fScale *= m_fZoomAmount;
+
+ // calculate the resultant coordinates
+ for (int i = 0; i < 4; i++)
+ {
+ x[i] *= fScale * 0.5f; // as the offsets x[] and y[] are from center
+ y[i] *= fScale * fPixelRatio * 0.5f;
+ // center it
+ x[i] += 0.5f * fScreenWidth + fOffsetX;
+ y[i] += 0.5f * fScreenHeight + fOffsetY;
+ }
+ // shift if we're zooming
+ if (m_fZoomAmount > 1)
+ {
+ float minx = x[0];
+ float maxx = x[0];
+ float miny = y[0];
+ float maxy = y[0];
+ for (int i = 1; i < 4; i++)
+ {
+ if (x[i] < minx) minx = x[i];
+ if (x[i] > maxx) maxx = x[i];
+ if (y[i] < miny) miny = y[i];
+ if (y[i] > maxy) maxy = y[i];
+ }
+ float w = maxx - minx;
+ float h = maxy - miny;
+ m_bCanMoveHorizontally = (w >= fScreenWidth);
+ m_bCanMoveVertically = (h >= fScreenHeight);
+ if (w >= fScreenWidth)
+ { // must have no black bars
+ if (minx + m_fZoomLeft*w > fOffsetX)
+ m_fZoomLeft = (fOffsetX - minx) / w;
+ if (maxx + m_fZoomLeft*w < fOffsetX + fScreenWidth)
+ m_fZoomLeft = (fScreenWidth + fOffsetX - maxx) / w;
+ for (float& i : x)
+ i += w * m_fZoomLeft;
+ }
+ if (h >= fScreenHeight)
+ { // must have no black bars
+ if (miny + m_fZoomTop*h > fOffsetY)
+ m_fZoomTop = (fOffsetY - miny) / h;
+ if (maxy + m_fZoomTop*h < fOffsetY + fScreenHeight)
+ m_fZoomTop = (fScreenHeight + fOffsetY - maxy) / h;
+ for (float& i : y)
+ i += m_fZoomTop * h;
+ }
+ }
+ // add offset from display effects
+ for (int i = 0; i < 4; i++)
+ {
+ x[i] += m_fPosX * m_fWidth * fScale;
+ y[i] += m_fPosY * m_fHeight * fScale;
+ }
+
+ UpdateVertices(m_ax, m_ay, x, y, dirtyregions);
+
+ // now render the image in the top right corner if we're zooming
+ if (m_fZoomAmount == 1 || m_bIsComic)
+ {
+ const float empty[4] = {};
+ UpdateVertices(m_bx, m_by, empty, empty, dirtyregions);
+ UpdateVertices(m_sx, m_sy, empty, empty, dirtyregions);
+ UpdateVertices(m_ox, m_oy, empty, empty, dirtyregions);
+ m_bIsDirty = false;
+ return;
+ }
+
+ float sx[4], sy[4];
+ sx[0] = -m_fWidth * co + m_fHeight * si;
+ sy[0] = -m_fWidth * si - m_fHeight * co;
+ sx[1] = m_fWidth * co + m_fHeight * si;
+ sy[1] = m_fWidth * si - m_fHeight * co;
+ sx[2] = m_fWidth * co - m_fHeight * si;
+ sy[2] = m_fWidth * si + m_fHeight * co;
+ sx[3] = -m_fWidth * co - m_fHeight * si;
+ sy[3] = -m_fWidth * si + m_fHeight * co;
+ // convert to the appropriate scale
+ float fSmallArea = fScreenWidth * fScreenHeight / 50;
+ float fSmallWidth = sqrt(fSmallArea * fAR / fPixelRatio); // fAR*height = width, so total area*far = width*width
+ float fSmallHeight = fSmallArea / fSmallWidth;
+ float fSmallX = fOffsetX + fScreenWidth * 0.95f - fSmallWidth * 0.5f;
+ float fSmallY = fOffsetY + fScreenHeight * 0.05f + fSmallHeight * 0.5f;
+ fScaleNorm = fSmallWidth / m_fWidth;
+ fScaleInv = fSmallWidth / m_fHeight;
+ fScale = si * si * (fScaleInv - fScaleNorm) + fScaleNorm;
+ for (int i = 0; i < 4; i++)
+ {
+ sx[i] *= fScale * 0.5f;
+ sy[i] *= fScale * fPixelRatio * 0.5f;
+ }
+ // calculate a black border
+ float bx[4];
+ float by[4];
+ for (int i = 0; i < 4; i++)
+ {
+ if (sx[i] > 0)
+ bx[i] = sx[i] + 1;
+ else
+ bx[i] = sx[i] - 1;
+ if (sy[i] > 0)
+ by[i] = sy[i] + 1;
+ else
+ by[i] = sy[i] - 1;
+ sx[i] += fSmallX;
+ sy[i] += fSmallY;
+ bx[i] += fSmallX;
+ by[i] += fSmallY;
+ }
+
+ fSmallX -= fSmallWidth * 0.5f;
+ fSmallY -= fSmallHeight * 0.5f;
+
+ UpdateVertices(m_bx, m_by, bx, by, dirtyregions);
+ UpdateVertices(m_sx, m_sy, sx, sy, dirtyregions);
+
+ // now we must render the wireframe image of the view window
+ // work out the direction of the top of pic vector
+ float scale;
+ if (fabs(x[1] - x[0]) > fabs(x[3] - x[0]))
+ scale = (sx[1] - sx[0]) / (x[1] - x[0]);
+ else
+ scale = (sx[3] - sx[0]) / (x[3] - x[0]);
+ float ox[4];
+ float oy[4];
+ ox[0] = (fOffsetX - x[0]) * scale + sx[0];
+ oy[0] = (fOffsetY - y[0]) * scale + sy[0];
+ ox[1] = (fOffsetX + fScreenWidth - x[0]) * scale + sx[0];
+ oy[1] = (fOffsetY - y[0]) * scale + sy[0];
+ ox[2] = (fOffsetX + fScreenWidth - x[0]) * scale + sx[0];
+ oy[2] = (fOffsetY + fScreenHeight - y[0]) * scale + sy[0];
+ ox[3] = (fOffsetX - x[0]) * scale + sx[0];
+ oy[3] = (fOffsetY + fScreenHeight - y[0]) * scale + sy[0];
+ // crop to within the range of our piccy
+ for (int i = 0; i < 4; i++)
+ {
+ if (ox[i] < fSmallX) ox[i] = fSmallX;
+ if (ox[i] > fSmallX + fSmallWidth) ox[i] = fSmallX + fSmallWidth;
+ if (oy[i] < fSmallY) oy[i] = fSmallY;
+ if (oy[i] > fSmallY + fSmallHeight) oy[i] = fSmallY + fSmallHeight;
+ }
+
+ UpdateVertices(m_ox, m_oy, ox, oy, dirtyregions);
+ m_bIsDirty = false;
+}
+
+void CSlideShowPic::Keep()
+{
+ // this is called if we need to keep the current pic on screen
+ // to wait for the next pic to load
+ if (!m_bDrawNextImage) return ; // don't need to keep pic
+ // hold off the start of the next frame
+ m_transitionEnd.start = m_iCounter;
+}
+
+bool CSlideShowPic::StartTransition()
+{
+ // this is called if we need to start transitioning immediately to the new picture
+ if (m_bDrawNextImage) return false; // don't need to do anything as we are already transitioning
+ // decrease the number of display frame
+ m_transitionEnd.start = m_iCounter;
+ m_bTransitionImmediately = true;
+ return true;
+}
+
+void CSlideShowPic::Pause(bool bPause)
+{
+ if (!m_bDrawNextImage)
+ m_bPause = bPause;
+}
+
+void CSlideShowPic::SetInSlideshow(bool slideshow)
+{
+ if (slideshow && m_displayEffect == EFFECT_NO_TIMEOUT)
+ m_displayEffect = EFFECT_NONE;
+}
+
+int CSlideShowPic::GetTransitionTime(int iType) const
+{
+ if (iType == 0) // start transition
+ return m_transitionStart.length;
+ else // iType == 1 // end transition
+ return m_transitionEnd.length;
+}
+
+void CSlideShowPic::SetTransitionTime(int iType, int iTime)
+{
+ if (iType == 0) // start transition
+ m_transitionStart.length = iTime;
+ else // iType == 1 // end transition
+ m_transitionEnd.length = iTime;
+}
+
+void CSlideShowPic::Rotate(float fRotateAngle, bool immediate /* = false */)
+{
+ if (m_bDrawNextImage) return;
+ if (m_transitionTemp.type == TRANSITION_ZOOM) return;
+ if (immediate)
+ {
+ m_fAngle += fRotateAngle;
+ return;
+ }
+
+ // if there is a rotation ongoing already
+ // add the new angle to the old destination angle
+ if (m_transitionTemp.type == TRANSITION_ROTATE &&
+ m_transitionTemp.start + m_transitionTemp.length > m_iCounter)
+ {
+ int remainder = m_transitionTemp.start + m_transitionTemp.length - m_iCounter;
+ fRotateAngle += m_fTransitionAngle * remainder;
+ }
+
+ m_transitionTemp.type = TRANSITION_ROTATE;
+ m_transitionTemp.start = m_iCounter;
+ m_transitionTemp.length = IMMEDIATE_TRANSITION_TIME;
+ m_fTransitionAngle = fRotateAngle / (float)m_transitionTemp.length;
+ // reset the timer
+ m_transitionEnd.start = m_iCounter + m_transitionStart.length + (int)(CServiceBroker::GetWinSystem()->GetGfxContext().GetFPS() * CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_SLIDESHOW_STAYTIME));
+}
+
+void CSlideShowPic::Zoom(float fZoom, bool immediate /* = false */)
+{
+ if (m_bDrawNextImage) return;
+ if (m_transitionTemp.type == TRANSITION_ROTATE) return;
+ if (immediate)
+ {
+ m_fZoomAmount = fZoom;
+ return;
+ }
+ m_transitionTemp.type = TRANSITION_ZOOM;
+ m_transitionTemp.start = m_iCounter;
+ m_transitionTemp.length = IMMEDIATE_TRANSITION_TIME;
+ m_fTransitionZoom = (fZoom - m_fZoomAmount) / (float)m_transitionTemp.length;
+ // reset the timer
+ m_transitionEnd.start = m_iCounter + m_transitionStart.length + (int)(CServiceBroker::GetWinSystem()->GetGfxContext().GetFPS() * CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_SLIDESHOW_STAYTIME));
+ // turn off the render effects until we're back down to normal zoom
+ m_bNoEffect = true;
+}
+
+void CSlideShowPic::Move(float fDeltaX, float fDeltaY)
+{
+ m_fZoomLeft += fDeltaX;
+ m_fZoomTop += fDeltaY;
+ // reset the timer
+ // m_transitionEnd.start = m_iCounter + m_transitionStart.length + (int)(CServiceBroker::GetWinSystem()->GetGfxContext().GetFPS() * CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_SLIDESHOW_STAYTIME));
+}
+
+void CSlideShowPic::Render()
+{
+ std::unique_lock<CCriticalSection> lock(m_textureAccess);
+
+ Render(m_ax, m_ay, m_pImage.get(), (m_alpha << 24) | 0xFFFFFF);
+
+ // now render the image in the top right corner if we're zooming
+ if (m_fZoomAmount == 1.0f || m_bIsComic) return ;
+
+ Render(m_bx, m_by, NULL, PICTURE_VIEW_BOX_BACKGROUND);
+ Render(m_sx, m_sy, m_pImage.get(), 0xFFFFFFFF);
+ Render(m_ox, m_oy, NULL, PICTURE_VIEW_BOX_COLOR);
+}
+
+#ifdef HAS_DX
+bool CSlideShowPic::UpdateVertexBuffer(Vertex* vertices)
+{
+ if (!m_vb) // create new
+ {
+ CD3D11_BUFFER_DESC desc(sizeof(Vertex) * 5, D3D11_BIND_VERTEX_BUFFER, D3D11_USAGE_DYNAMIC, D3D11_CPU_ACCESS_WRITE);
+ D3D11_SUBRESOURCE_DATA initData = {};
+ initData.pSysMem = vertices;
+ initData.SysMemPitch = sizeof(Vertex) * 5;
+ if (SUCCEEDED(DX::DeviceResources::Get()->GetD3DDevice()->CreateBuffer(&desc, &initData, m_vb.ReleaseAndGetAddressOf())))
+ return true;
+ }
+ else // update
+ {
+ ID3D11DeviceContext* pContext = DX::DeviceResources::Get()->GetD3DContext();
+ D3D11_MAPPED_SUBRESOURCE res;
+ if (SUCCEEDED(pContext->Map(m_vb.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &res)))
+ {
+ memcpy(res.pData, vertices, sizeof(Vertex) * 5);
+ pContext->Unmap(m_vb.Get(), 0);
+ return true;
+ }
+ }
+
+ return false;
+}
+#endif
+
+void CSlideShowPic::Render(float* x, float* y, CTexture* pTexture, UTILS::COLOR::Color color)
+{
+#ifdef HAS_DX
+ Vertex vertex[5];
+ for (int i = 0; i < 4; i++)
+ {
+ vertex[i].pos = XMFLOAT3( x[i], y[i], 0);
+ CD3DHelper::XMStoreColor(&vertex[i].color, color);
+ vertex[i].texCoord = XMFLOAT2(0.0f, 0.0f);
+ vertex[i].texCoord2 = XMFLOAT2(0.0f, 0.0f);
+ }
+
+ if (pTexture)
+ {
+ vertex[1].texCoord.x = vertex[2].texCoord.x = (float) pTexture->GetWidth() / pTexture->GetTextureWidth();
+ vertex[2].texCoord.y = vertex[3].texCoord.y = (float) pTexture->GetHeight() / pTexture->GetTextureHeight();
+ }
+ else
+ {
+ vertex[1].texCoord.x = vertex[2].texCoord.x = 1.0f;
+ vertex[2].texCoord.y = vertex[3].texCoord.y = 1.0f;
+ }
+ vertex[4] = vertex[0]; // Not used when pTexture != NULL
+
+ CGUIShaderDX* pGUIShader = DX::Windowing()->GetGUIShader();
+ pGUIShader->Begin(SHADER_METHOD_RENDER_TEXTURE_BLEND);
+
+ // Set state to render the image
+ if (pTexture)
+ {
+ pTexture->LoadToGPU();
+ CDXTexture* dxTexture = reinterpret_cast<CDXTexture*>(pTexture);
+ ID3D11ShaderResourceView* shaderRes = dxTexture->GetShaderResource();
+ pGUIShader->SetShaderViews(1, &shaderRes);
+ pGUIShader->DrawQuad(vertex[0], vertex[1], vertex[2], vertex[3]);
+ }
+ else
+ {
+ if (!UpdateVertexBuffer(vertex))
+ return;
+
+ ComPtr<ID3D11DeviceContext> pContext = DX::DeviceResources::Get()->GetD3DContext();
+
+ unsigned stride = sizeof(Vertex);
+ unsigned offset = 0;
+ pContext->IASetVertexBuffers(0, 1, m_vb.GetAddressOf(), &stride, &offset);
+ pContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_LINESTRIP);
+
+ pGUIShader->Draw(5, 0);
+ pGUIShader->RestoreBuffers();
+ }
+
+#elif defined(HAS_GL)
+ CRenderSystemGL *renderSystem = dynamic_cast<CRenderSystemGL*>(CServiceBroker::GetRenderSystem());
+ if (pTexture)
+ {
+ pTexture->LoadToGPU();
+ pTexture->BindToUnit(0);
+
+ glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
+ glEnable(GL_BLEND);
+
+ renderSystem->EnableShader(ShaderMethodGL::SM_TEXTURE);
+ }
+ else
+ {
+ renderSystem->EnableShader(ShaderMethodGL::SM_DEFAULT);
+ }
+
+ float u1 = 0, u2 = 1, v1 = 0, v2 = 1;
+ if (pTexture)
+ {
+ u2 = (float)pTexture->GetWidth() / pTexture->GetTextureWidth();
+ v2 = (float)pTexture->GetHeight() / pTexture->GetTextureHeight();
+ }
+
+ GLubyte colour[4];
+ GLubyte idx[4] = {0, 1, 3, 2}; //determines order of the vertices
+ GLuint vertexVBO;
+ GLuint indexVBO;
+ struct PackedVertex
+ {
+ float x, y, z;
+ float u1, v1;
+ } vertex[4];
+
+ // Setup vertex position values
+ vertex[0].x = x[0];
+ vertex[0].y = y[0];
+ vertex[0].z = 0;
+ vertex[0].u1 = u1;
+ vertex[0].v1 = v1;
+
+ vertex[1].x = x[1];
+ vertex[1].y = y[1];
+ vertex[1].z = 0;
+ vertex[1].u1 = u2;
+ vertex[1].v1 = v1;
+
+ vertex[2].x = x[2];
+ vertex[2].y = y[2];
+ vertex[2].z = 0;
+ vertex[2].u1 = u2;
+ vertex[2].v1 = v2;
+
+ vertex[3].x = x[3];
+ vertex[3].y = y[3];
+ vertex[3].z = 0;
+ vertex[3].u1 = u1;
+ vertex[3].v1 = v2;
+
+ GLint posLoc = renderSystem->ShaderGetPos();
+ GLint tex0Loc = renderSystem->ShaderGetCoord0();
+ GLint uniColLoc= renderSystem->ShaderGetUniCol();
+
+ glGenBuffers(1, &vertexVBO);
+ glBindBuffer(GL_ARRAY_BUFFER, vertexVBO);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(PackedVertex)*4, &vertex[0], GL_STATIC_DRAW);
+
+ glVertexAttribPointer(posLoc, 3, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, x)));
+ glVertexAttribPointer(tex0Loc, 2, GL_FLOAT, 0, sizeof(PackedVertex),
+ reinterpret_cast<const GLvoid*>(offsetof(PackedVertex, u1)));
+
+ glEnableVertexAttribArray(posLoc);
+ glEnableVertexAttribArray(tex0Loc);
+
+ // Setup Colour values
+ colour[0] = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::R, color);
+ colour[1] = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::G, color);
+ colour[2] = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::B, color);
+ colour[3] = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::A, color);
+
+ glUniform4f(uniColLoc,(colour[0] / 255.0f), (colour[1] / 255.0f),
+ (colour[2] / 255.0f), (colour[3] / 255.0f));
+
+ glGenBuffers(1, &indexVBO);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexVBO);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLubyte)*4, idx, GL_STATIC_DRAW);
+
+ glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, 0);
+
+ glDisableVertexAttribArray(posLoc);
+ glDisableVertexAttribArray(tex0Loc);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+ glDeleteBuffers(1, &vertexVBO);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+ glDeleteBuffers(1, &indexVBO);
+
+ renderSystem->DisableShader();
+
+#elif defined(HAS_GLES)
+ CRenderSystemGLES *renderSystem = dynamic_cast<CRenderSystemGLES*>(CServiceBroker::GetRenderSystem());
+ if (pTexture)
+ {
+ pTexture->LoadToGPU();
+ pTexture->BindToUnit(0);
+
+ glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);
+ glEnable(GL_BLEND); // Turn Blending On
+
+ renderSystem->EnableGUIShader(ShaderMethodGLES::SM_TEXTURE);
+ }
+ else
+ {
+ renderSystem->EnableGUIShader(ShaderMethodGLES::SM_DEFAULT);
+ }
+
+ float u1 = 0, u2 = 1, v1 = 0, v2 = 1;
+ if (pTexture)
+ {
+ u2 = (float)pTexture->GetWidth() / pTexture->GetTextureWidth();
+ v2 = (float)pTexture->GetHeight() / pTexture->GetTextureHeight();
+ }
+
+ GLubyte col[4];
+ GLfloat ver[4][3];
+ GLfloat tex[4][2];
+ GLubyte idx[4] = {0, 1, 3, 2}; //determines order of triangle strip
+
+ GLint posLoc = renderSystem->GUIShaderGetPos();
+ GLint tex0Loc = renderSystem->GUIShaderGetCoord0();
+ GLint uniColLoc= renderSystem->GUIShaderGetUniCol();
+
+ glVertexAttribPointer(posLoc, 3, GL_FLOAT, 0, 0, ver);
+ glVertexAttribPointer(tex0Loc, 2, GL_FLOAT, 0, 0, tex);
+
+ glEnableVertexAttribArray(posLoc);
+ glEnableVertexAttribArray(tex0Loc);
+
+ // Setup Colour values
+ col[0] = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::R, color);
+ col[1] = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::G, color);
+ col[2] = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::B, color);
+ col[3] = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::A, color);
+
+ if (CServiceBroker::GetWinSystem()->UseLimitedColor())
+ {
+ col[0] = (235 - 16) * col[0] / 255 + 16;
+ col[1] = (235 - 16) * col[1] / 255 + 16;
+ col[2] = (235 - 16) * col[2] / 255 + 16;
+ }
+
+ for (int i=0; i<4; i++)
+ {
+ // Setup vertex position values
+ ver[i][0] = x[i];
+ ver[i][1] = y[i];
+ ver[i][2] = 0.0f;
+ }
+ // Setup texture coordinates
+ tex[0][0] = tex[3][0] = u1;
+ tex[0][1] = tex[1][1] = v1;
+ tex[1][0] = tex[2][0] = u2;
+ tex[2][1] = tex[3][1] = v2;
+
+ glUniform4f(uniColLoc,(col[0] / 255.0f), (col[1] / 255.0f), (col[2] / 255.0f), (col[3] / 255.0f));
+ glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, idx);
+
+ glDisableVertexAttribArray(posLoc);
+ glDisableVertexAttribArray(tex0Loc);
+
+ renderSystem->DisableGUIShader();
+
+#endif
+}
diff --git a/xbmc/pictures/SlideShowPicture.h b/xbmc/pictures/SlideShowPicture.h
new file mode 100644
index 0000000..b557cc5
--- /dev/null
+++ b/xbmc/pictures/SlideShowPicture.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "guilib/DirtyRegion.h"
+#include "threads/CriticalSection.h"
+#include "utils/ColorUtils.h"
+
+#include <string>
+#ifdef HAS_DX
+#include "guilib/GUIShaderDX.h"
+#include <wrl/client.h>
+#endif
+
+#include <memory>
+
+class CTexture;
+
+class CSlideShowPic
+{
+public:
+ enum DISPLAY_EFFECT { EFFECT_NONE = 0, EFFECT_FLOAT, EFFECT_ZOOM, EFFECT_RANDOM, EFFECT_PANORAMA, EFFECT_NO_TIMEOUT };
+ enum TRANSITION_EFFECT { TRANSITION_NONE = 0, FADEIN_FADEOUT, CROSSFADE, TRANSITION_ZOOM, TRANSITION_ROTATE };
+
+ struct TRANSITION
+ {
+ TRANSITION_EFFECT type = TRANSITION_NONE;
+ int start = 0;
+ int length = 0;
+ };
+
+ CSlideShowPic();
+ ~CSlideShowPic();
+
+ void SetTexture(int iSlideNumber,
+ std::unique_ptr<CTexture> pTexture,
+ DISPLAY_EFFECT dispEffect = EFFECT_RANDOM,
+ TRANSITION_EFFECT transEffect = FADEIN_FADEOUT);
+ void UpdateTexture(std::unique_ptr<CTexture> pTexture);
+
+ bool IsLoaded() const { return m_bIsLoaded; }
+ void UnLoad() { m_bIsLoaded = false; }
+ void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions);
+ void Render();
+ void Close();
+ void Reset(DISPLAY_EFFECT dispEffect = EFFECT_RANDOM, TRANSITION_EFFECT transEffect = FADEIN_FADEOUT);
+ DISPLAY_EFFECT DisplayEffect() const { return m_displayEffect; }
+ bool DisplayEffectNeedChange(DISPLAY_EFFECT newDispEffect) const;
+ bool IsStarted() const { return m_iCounter > 0; }
+ bool IsFinished() const { return m_bIsFinished; }
+ bool DrawNextImage() const { return m_bDrawNextImage; }
+
+ int GetWidth() const { return (int)m_fWidth; }
+ int GetHeight() const { return (int)m_fHeight; }
+
+ void Keep();
+ bool StartTransition();
+ int GetTransitionTime(int iType) const;
+ void SetTransitionTime(int iType, int iTime);
+
+ int SlideNumber() const { return m_iSlideNumber; }
+
+ void Zoom(float fZoomAmount, bool immediate = false);
+ void Rotate(float fRotateAngle, bool immediate = false);
+ void Pause(bool bPause);
+ void SetInSlideshow(bool slideshow);
+ void SetOriginalSize(int iOriginalWidth, int iOriginalHeight, bool bFullSize);
+ bool FullSize() const { return m_bFullSize; }
+ int GetOriginalWidth();
+ int GetOriginalHeight();
+
+ void Move(float dX, float dY);
+ float GetZoom() const { return m_fZoomAmount; }
+
+ bool m_bIsComic;
+ bool m_bCanMoveHorizontally;
+ bool m_bCanMoveVertically;
+private:
+ void SetTexture_Internal(int iSlideNumber,
+ std::unique_ptr<CTexture> pTexture,
+ DISPLAY_EFFECT dispEffect = EFFECT_RANDOM,
+ TRANSITION_EFFECT transEffect = FADEIN_FADEOUT);
+ void UpdateVertices(float cur_x[4], float cur_y[4], const float new_x[4], const float new_y[4], CDirtyRegionList &dirtyregions);
+ void Render(float* x, float* y, CTexture* pTexture, UTILS::COLOR::Color color);
+ std::unique_ptr<CTexture> m_pImage;
+
+ int m_iOriginalWidth;
+ int m_iOriginalHeight;
+ int m_iSlideNumber;
+ bool m_bIsLoaded;
+ bool m_bIsFinished;
+ bool m_bDrawNextImage;
+ bool m_bIsDirty;
+ std::string m_strFileName;
+ float m_fWidth;
+ float m_fHeight;
+ UTILS::COLOR::Color m_alpha = 0;
+ // stuff relative to middle position
+ float m_fPosX;
+ float m_fPosY;
+ float m_fPosZ;
+ float m_fVelocityX;
+ float m_fVelocityY;
+ float m_fVelocityZ;
+ float m_fZoomAmount;
+ float m_fZoomLeft;
+ float m_fZoomTop;
+ float m_ax[4]{}, m_ay[4]{};
+ float m_sx[4]{}, m_sy[4]{};
+ float m_bx[4]{}, m_by[4]{};
+ float m_ox[4]{}, m_oy[4]{};
+
+ // transition and display effects
+ DISPLAY_EFFECT m_displayEffect = EFFECT_NONE;
+ TRANSITION m_transitionStart;
+ TRANSITION m_transitionEnd;
+ TRANSITION m_transitionTemp; // used for rotations + zooms
+ float m_fAngle; // angle (between 0 and 2pi to display the image)
+ float m_fTransitionAngle;
+ float m_fTransitionZoom;
+ int m_iCounter = 0;
+ int m_iTotalFrames;
+ bool m_bPause;
+ bool m_bNoEffect;
+ bool m_bFullSize;
+ bool m_bTransitionImmediately;
+
+ CCriticalSection m_textureAccess;
+#ifdef HAS_DX
+ Microsoft::WRL::ComPtr<ID3D11Buffer> m_vb;
+ bool UpdateVertexBuffer(Vertex *vertices);
+#endif
+};
diff --git a/xbmc/pictures/libexif.cpp b/xbmc/pictures/libexif.cpp
new file mode 100644
index 0000000..3ee75b9
--- /dev/null
+++ b/xbmc/pictures/libexif.cpp
@@ -0,0 +1,35 @@
+// libexif.cpp : Defines the entry point for the console application.
+//
+
+#include "libexif.h"
+
+#include "JpegParse.h"
+
+#ifdef TARGET_WINDOWS
+#include <windows.h>
+#else
+#include <memory.h>
+#include <cstring>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+bool process_jpeg(const char *filename, ExifInfo_t *exifInfo, IPTCInfo_t *iptcInfo)
+{
+ if (!exifInfo || !iptcInfo) return false;
+ CJpegParse jpeg;
+ memset(exifInfo, 0, sizeof(ExifInfo_t));
+ memset(iptcInfo, 0, sizeof(IPTCInfo_t));
+ if (jpeg.Process(filename))
+ {
+ memcpy(exifInfo, jpeg.GetExifInfo(), sizeof(ExifInfo_t));
+ memcpy(iptcInfo, jpeg.GetIptcInfo(), sizeof(IPTCInfo_t));
+ return true;
+ }
+ return false;
+}
+#ifdef __cplusplus
+}
+#endif
diff --git a/xbmc/pictures/libexif.h b/xbmc/pictures/libexif.h
new file mode 100644
index 0000000..567255e
--- /dev/null
+++ b/xbmc/pictures/libexif.h
@@ -0,0 +1,138 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef _DLL
+#ifdef WIN32
+#define EXIF_EXPORT __declspec(dllexport)
+#else
+#define EXIF_EXPORT
+#endif
+#else
+#define EXIF_EXPORT
+#endif
+
+//--------------------------------------------------------------------------
+// JPEG markers consist of one or more 0xFF bytes, followed by a marker
+// code byte (which is not an FF). Here are the marker codes of interest
+// in this application.
+//--------------------------------------------------------------------------
+
+#define M_SOF0 0xC0 // Start Of Frame N
+#define M_SOF1 0xC1 // N indicates which compression process
+#define M_SOF2 0xC2 // Only SOF0-SOF2 are now in common use
+#define M_SOF3 0xC3
+#define M_SOF5 0xC5 // NB: codes C4 and CC are NOT SOF markers
+#define M_SOF6 0xC6
+#define M_SOF7 0xC7
+#define M_SOF9 0xC9
+#define M_SOF10 0xCA
+#define M_SOF11 0xCB
+#define M_SOF13 0xCD
+#define M_SOF14 0xCE
+#define M_SOF15 0xCF
+#define M_SOI 0xD8 // Start Of Image (beginning of datastream)
+#define M_EOI 0xD9 // End Of Image (end of datastream)
+#define M_SOS 0xDA // Start Of Scan (begins compressed data)
+#define M_JFIF 0xE0 // Jfif marker
+#define M_EXIF 0xE1 // Exif marker
+#define M_COM 0xFE // COMment
+#define M_DQT 0xDB
+#define M_DHT 0xC4
+#define M_DRI 0xDD
+#define M_IPTC 0xED // IPTC marker
+
+#define MAX_IPTC_STRING 256
+
+typedef struct {
+ char RecordVersion[MAX_IPTC_STRING];
+ char SupplementalCategories[MAX_IPTC_STRING];
+ char Keywords[MAX_IPTC_STRING];
+ char Caption[MAX_IPTC_STRING];
+ char Author[MAX_IPTC_STRING];
+ char Headline[MAX_IPTC_STRING];
+ char SpecialInstructions[MAX_IPTC_STRING];
+ char Category[MAX_IPTC_STRING];
+ char Byline[MAX_IPTC_STRING];
+ char BylineTitle[MAX_IPTC_STRING];
+ char Credit[MAX_IPTC_STRING];
+ char Source[MAX_IPTC_STRING];
+ char CopyrightNotice[MAX_IPTC_STRING];
+ char ObjectName[MAX_IPTC_STRING];
+ char City[MAX_IPTC_STRING];
+ char State[MAX_IPTC_STRING];
+ char Country[MAX_IPTC_STRING];
+ char TransmissionReference[MAX_IPTC_STRING];
+ char Date[MAX_IPTC_STRING];
+ char Urgency[MAX_IPTC_STRING];
+ char ReferenceService[MAX_IPTC_STRING];
+ char CountryCode[MAX_IPTC_STRING];
+ char TimeCreated[MAX_IPTC_STRING];
+ char SubLocation[MAX_IPTC_STRING];
+ char ImageType[MAX_IPTC_STRING];
+} IPTCInfo_t;
+
+#define EXIF_COMMENT_CHARSET_CONVERTED -1 // Comments contains converted data
+#define EXIF_COMMENT_CHARSET_UNKNOWN 0 // Exif: Unknown
+#define EXIF_COMMENT_CHARSET_ASCII 2 // Exif: Ascii
+#define EXIF_COMMENT_CHARSET_UNICODE 3 // Exif: Unicode (UTF-16)
+#define EXIF_COMMENT_CHARSET_JIS 4 // Exif: JIS X208-1990
+
+#define MAX_COMMENT 2000
+#define MAX_DATE_COPIES 10
+
+typedef struct {
+ char CameraMake [33];
+ char CameraModel [41];
+ char DateTime [21];
+ int Height, Width;
+ int Orientation;
+ int IsColor;
+ int Process;
+ int FlashUsed;
+ float FocalLength;
+ float ExposureTime;
+ float ApertureFNumber;
+ float Distance;
+ float CCDWidth;
+ float ExposureBias;
+ float DigitalZoomRatio;
+ int FocalLength35mmEquiv; // Exif 2.2 tag - usually not present.
+ int Whitebalance;
+ int MeteringMode;
+ int ExposureProgram;
+ int ExposureMode;
+ int ISOequivalent;
+ int LightSource;
+ int CommentsCharset; // EXIF_COMMENT_CHARSET_*
+ int XPCommentsCharset;
+ char Comments[MAX_COMMENT + 1]; // +1 for null termination
+ char FileComment[MAX_COMMENT + 1];
+ char XPComment[MAX_COMMENT + 1];
+ char Description[MAX_COMMENT + 1];
+
+ unsigned ThumbnailOffset; // Exif offset to thumbnail
+ unsigned ThumbnailSize; // Size of thumbnail.
+ unsigned LargestExifOffset; // Last exif data referenced (to check if thumbnail is at end)
+
+ char ThumbnailAtEnd; // Exif header ends with the thumbnail
+ // (we can only modify the thumbnail if its at the end)
+ int ThumbnailSizeOffset;
+
+ int DateTimeOffsets[MAX_DATE_COPIES];
+ int numDateTimeTags;
+
+ int GpsInfoPresent;
+ char GpsLat[31];
+ char GpsLong[31];
+ char GpsAlt[20];
+} ExifInfo_t;
+
+EXIF_EXPORT bool process_jpeg(const char *filename, ExifInfo_t *exifInfo, IPTCInfo_t *iptcInfo);
+
+#ifdef __cplusplus
+}
+#endif
+