2018-06-26 14:09:39 +02:00
# include "messages/Image.hpp"
2018-04-27 22:11:19 +02:00
Sort and force grouping of includes (#4172)
This change enforces strict include grouping using IncludeCategories
In addition to adding this to the .clang-format file and applying it in the tests/src and src directories, I also did the following small changes:
In ChatterSet.hpp, I changed lrucache to a <>include
In Irc2.hpp, I change common/SignalVector.hpp to a "project-include"
In AttachedWindow.cpp, NativeMessaging.cpp, WindowsHelper.hpp, BaseWindow.cpp, and StreamerMode.cpp, I disabled clang-format for the windows-includes
In WindowDescriptors.hpp, I added the missing vector include. It was previously not needed because the include was handled by another file that was previously included first.
clang-format minimum version has been bumped, so Ubuntu version used in the check-formatting job has been bumped to 22.04 (which is the latest LTS)
2022-11-27 19:32:53 +01:00
# include "Application.hpp"
# include "common/Common.hpp"
# include "common/NetworkRequest.hpp"
2022-12-31 15:41:01 +01:00
# include "common/NetworkResult.hpp"
# include "common/Outcome.hpp"
Sort and force grouping of includes (#4172)
This change enforces strict include grouping using IncludeCategories
In addition to adding this to the .clang-format file and applying it in the tests/src and src directories, I also did the following small changes:
In ChatterSet.hpp, I changed lrucache to a <>include
In Irc2.hpp, I change common/SignalVector.hpp to a "project-include"
In AttachedWindow.cpp, NativeMessaging.cpp, WindowsHelper.hpp, BaseWindow.cpp, and StreamerMode.cpp, I disabled clang-format for the windows-includes
In WindowDescriptors.hpp, I added the missing vector include. It was previously not needed because the include was handled by another file that was previously included first.
clang-format minimum version has been bumped, so Ubuntu version used in the check-formatting job has been bumped to 22.04 (which is the latest LTS)
2022-11-27 19:32:53 +01:00
# include "common/QLogging.hpp"
# include "debug/AssertInGuiThread.hpp"
# include "debug/Benchmark.hpp"
2023-04-09 12:18:56 +02:00
# include "singletons/Emotes.hpp"
# include "singletons/helper/GifTimer.hpp"
# include "singletons/WindowManager.hpp"
# include "util/DebugCount.hpp"
# include "util/PostToThread.hpp"
Sort and force grouping of includes (#4172)
This change enforces strict include grouping using IncludeCategories
In addition to adding this to the .clang-format file and applying it in the tests/src and src directories, I also did the following small changes:
In ChatterSet.hpp, I changed lrucache to a <>include
In Irc2.hpp, I change common/SignalVector.hpp to a "project-include"
In AttachedWindow.cpp, NativeMessaging.cpp, WindowsHelper.hpp, BaseWindow.cpp, and StreamerMode.cpp, I disabled clang-format for the windows-includes
In WindowDescriptors.hpp, I added the missing vector include. It was previously not needed because the include was handled by another file that was previously included first.
clang-format minimum version has been bumped, so Ubuntu version used in the check-formatting job has been bumped to 22.04 (which is the latest LTS)
2022-11-27 19:32:53 +01:00
# include <boost/functional/hash.hpp>
2019-09-22 10:27:05 +02:00
# include <QBuffer>
# include <QImageReader>
# include <QNetworkAccessManager>
# include <QNetworkReply>
# include <QNetworkRequest>
# include <QTimer>
Sort and force grouping of includes (#4172)
This change enforces strict include grouping using IncludeCategories
In addition to adding this to the .clang-format file and applying it in the tests/src and src directories, I also did the following small changes:
In ChatterSet.hpp, I changed lrucache to a <>include
In Irc2.hpp, I change common/SignalVector.hpp to a "project-include"
In AttachedWindow.cpp, NativeMessaging.cpp, WindowsHelper.hpp, BaseWindow.cpp, and StreamerMode.cpp, I disabled clang-format for the windows-includes
In WindowDescriptors.hpp, I added the missing vector include. It was previously not needed because the include was handled by another file that was previously included first.
clang-format minimum version has been bumped, so Ubuntu version used in the check-formatting job has been bumped to 22.04 (which is the latest LTS)
2022-11-27 19:32:53 +01:00
2019-09-22 10:27:05 +02:00
# include <functional>
2022-09-04 13:23:14 +02:00
# include <queue>
2019-09-22 10:27:05 +02:00
# include <thread>
2017-01-11 18:52:09 +01:00
2022-09-04 13:23:14 +02:00
// Duration between each check of every Image instance
const auto IMAGE_POOL_CLEANUP_INTERVAL = std : : chrono : : minutes ( 1 ) ;
// Duration since last usage of Image pixmap before expiration of frames
const auto IMAGE_POOL_IMAGE_LIFETIME = std : : chrono : : minutes ( 10 ) ;
2021-02-13 19:17:22 +01:00
2017-01-18 21:30:23 +01:00
namespace chatterino {
2018-09-20 13:09:37 +02:00
namespace detail {
2018-08-15 22:46:20 +02:00
// Frames
Frames : : Frames ( )
{
DebugCount : : increase ( " images " ) ;
2018-08-11 17:15:17 +02:00
}
2018-08-06 18:25:47 +02:00
2022-09-04 13:23:14 +02:00
Frames : : Frames ( QVector < Frame < QPixmap > > & & frames )
: items_ ( std : : move ( frames ) )
2018-08-15 22:46:20 +02:00
{
assertInGuiThread ( ) ;
DebugCount : : increase ( " images " ) ;
2022-09-04 13:23:14 +02:00
if ( ! this - > empty ( ) )
{
DebugCount : : increase ( " loaded images " ) ;
}
2018-08-11 17:15:17 +02:00
2018-10-21 13:43:02 +02:00
if ( this - > animated ( ) )
{
2018-08-15 22:46:20 +02:00
DebugCount : : increase ( " animated images " ) ;
2018-08-06 18:25:47 +02:00
2018-08-15 22:46:20 +02:00
this - > gifTimerConnection_ =
2020-11-08 12:02:19 +01:00
getApp ( ) - > emotes - > gifTimer . signal . connect ( [ this ] {
this - > advance ( ) ;
} ) ;
2018-08-15 22:46:20 +02:00
}
2020-02-16 14:24:11 +01:00
2020-11-08 12:02:19 +01:00
auto totalLength =
std : : accumulate ( this - > items_ . begin ( ) , this - > items_ . end ( ) , 0UL ,
[ ] ( auto init , auto & & frame ) {
return init + frame . duration ;
} ) ;
2020-02-16 14:24:11 +01:00
2020-04-19 21:05:40 +02:00
if ( totalLength = = 0 )
{
this - > durationOffset_ = 0 ;
}
else
{
this - > durationOffset_ = std : : min < int > (
int ( getApp ( ) - > emotes - > gifTimer . position ( ) % totalLength ) ,
60000 ) ;
}
2020-02-16 14:24:11 +01:00
this - > processOffset ( ) ;
2023-05-27 14:18:08 +02:00
DebugCount : : increase ( " image bytes " , this - > memoryUsage ( ) ) ;
DebugCount : : increase ( " image bytes (ever loaded) " , this - > memoryUsage ( ) ) ;
2018-08-15 22:46:20 +02:00
}
2018-08-06 18:25:47 +02:00
2018-08-15 22:46:20 +02:00
Frames : : ~ Frames ( )
{
assertInGuiThread ( ) ;
DebugCount : : decrease ( " images " ) ;
2022-09-04 13:23:14 +02:00
if ( ! this - > empty ( ) )
{
DebugCount : : decrease ( " loaded images " ) ;
}
2018-08-11 17:15:17 +02:00
2018-10-21 13:43:02 +02:00
if ( this - > animated ( ) )
{
2018-08-15 22:46:20 +02:00
DebugCount : : decrease ( " animated images " ) ;
2018-08-11 17:15:17 +02:00
}
2023-05-27 14:18:08 +02:00
DebugCount : : decrease ( " image bytes " , this - > memoryUsage ( ) ) ;
DebugCount : : increase ( " image bytes (ever unloaded) " ,
this - > memoryUsage ( ) ) ;
2018-08-11 17:15:17 +02:00
2018-08-15 22:46:20 +02:00
this - > gifTimerConnection_ . disconnect ( ) ;
2018-08-06 18:25:47 +02:00
}
2023-05-27 14:18:08 +02:00
int64_t Frames : : memoryUsage ( ) const
{
int64_t usage = 0 ;
for ( const auto & frame : this - > items_ )
{
auto sz = frame . image . size ( ) ;
auto area = sz . width ( ) * sz . height ( ) ;
auto memory = area * frame . image . depth ( ) ;
usage + = memory ;
}
return usage ;
}
2018-08-15 22:46:20 +02:00
void Frames : : advance ( )
{
2022-09-04 19:25:34 +02:00
this - > durationOffset_ + = GIF_FRAME_LENGTH ;
2020-02-16 14:24:11 +01:00
this - > processOffset ( ) ;
}
2018-08-06 18:25:47 +02:00
2020-02-16 14:24:11 +01:00
void Frames : : processOffset ( )
{
2020-04-19 21:05:40 +02:00
if ( this - > items_ . isEmpty ( ) )
{
return ;
}
2018-10-21 13:43:02 +02:00
while ( true )
{
2018-08-15 22:46:20 +02:00
this - > index_ % = this - > items_ . size ( ) ;
2018-08-06 18:25:47 +02:00
2018-10-21 13:43:02 +02:00
if ( this - > durationOffset_ > this - > items_ [ this - > index_ ] . duration )
{
2018-08-15 22:46:20 +02:00
this - > durationOffset_ - = this - > items_ [ this - > index_ ] . duration ;
this - > index_ = ( this - > index_ + 1 ) % this - > items_ . size ( ) ;
2018-10-21 13:43:02 +02:00
}
else
{
2018-08-15 22:46:20 +02:00
break ;
}
}
2018-08-06 18:25:47 +02:00
}
2022-09-04 13:23:14 +02:00
void Frames : : clear ( )
{
assertInGuiThread ( ) ;
if ( ! this - > empty ( ) )
{
DebugCount : : decrease ( " loaded images " ) ;
}
2023-05-27 14:18:08 +02:00
DebugCount : : decrease ( " image bytes " , this - > memoryUsage ( ) ) ;
DebugCount : : increase ( " image bytes (ever unloaded) " ,
this - > memoryUsage ( ) ) ;
2022-09-04 13:23:14 +02:00
this - > items_ . clear ( ) ;
this - > index_ = 0 ;
this - > durationOffset_ = 0 ;
this - > gifTimerConnection_ . disconnect ( ) ;
}
bool Frames : : empty ( ) const
{
return this - > items_ . empty ( ) ;
}
2018-08-15 22:46:20 +02:00
bool Frames : : animated ( ) const
{
return this - > items_ . size ( ) > 1 ;
}
2018-08-06 18:25:47 +02:00
2018-08-15 22:46:20 +02:00
boost : : optional < QPixmap > Frames : : current ( ) const
{
2018-10-21 13:43:02 +02:00
if ( this - > items_ . size ( ) = = 0 )
return boost : : none ;
2018-08-15 22:46:20 +02:00
return this - > items_ [ this - > index_ ] . image ;
2018-08-06 18:25:47 +02:00
}
2018-08-15 22:46:20 +02:00
boost : : optional < QPixmap > Frames : : first ( ) const
{
2018-10-21 13:43:02 +02:00
if ( this - > items_ . size ( ) = = 0 )
return boost : : none ;
2018-08-15 22:46:20 +02:00
return this - > items_ . front ( ) . image ;
2018-08-06 18:25:47 +02:00
}
2018-08-15 22:46:20 +02:00
// functions
QVector < Frame < QImage > > readFrames ( QImageReader & reader , const Url & url )
{
QVector < Frame < QImage > > frames ;
2022-09-04 13:23:14 +02:00
frames . reserve ( reader . imageCount ( ) ) ;
2018-08-06 18:25:47 +02:00
2018-08-15 22:46:20 +02:00
QImage image ;
2018-10-21 13:43:02 +02:00
for ( int index = 0 ; index < reader . imageCount ( ) ; + + index )
{
if ( reader . read ( & image ) )
{
2022-01-15 20:23:08 +01:00
// It seems that browsers have special logic for fast animations.
// This implements Chrome and Firefox's behavior which uses
// a duration of 100 ms for any frames that specify a duration of <= 10 ms.
// See http://webkit.org/b/36082 for more information.
// https://github.com/SevenTV/chatterino7/issues/46#issuecomment-1010595231
int duration = reader . nextImageDelay ( ) ;
if ( duration < = 10 )
duration = 100 ;
duration = std : : max ( 20 , duration ) ;
2022-09-04 13:23:14 +02:00
frames . push_back ( Frame < QImage > { std : : move ( image ) , duration } ) ;
2018-08-15 22:46:20 +02:00
}
2018-08-09 18:39:46 +02:00
}
2018-10-21 13:43:02 +02:00
if ( frames . size ( ) = = 0 )
{
2020-11-21 16:20:10 +01:00
qCDebug ( chatterinoImage )
< < " Error while reading image " < < url . string < < " : ' "
< < reader . errorString ( ) < < " ' " ;
2018-08-15 22:46:20 +02:00
}
2018-08-09 18:39:46 +02:00
2018-08-15 22:46:20 +02:00
return frames ;
}
2018-08-09 18:39:46 +02:00
2018-08-15 22:46:20 +02:00
// parsed
template < typename Assign >
void assignDelayed (
std : : queue < std : : pair < Assign , QVector < Frame < QPixmap > > > > & queued ,
std : : mutex & mutex , std : : atomic_bool & loadedEventQueued )
{
2018-08-09 18:39:46 +02:00
std : : lock_guard < std : : mutex > lock ( mutex ) ;
2018-08-15 22:46:20 +02:00
int i = 0 ;
2018-10-21 13:43:02 +02:00
while ( ! queued . empty ( ) )
{
2022-09-04 13:23:14 +02:00
auto front = std : : move ( queued . front ( ) ) ;
2018-08-15 22:46:20 +02:00
queued . pop ( ) ;
2022-09-04 13:23:14 +02:00
// Call Assign with the vector of frames
front . first ( std : : move ( front . second ) ) ;
2018-10-21 13:43:02 +02:00
if ( + + i > 50 )
{
2018-08-15 22:46:20 +02:00
QTimer : : singleShot ( 3 , [ & ] {
assignDelayed ( queued , mutex , loadedEventQueued ) ;
} ) ;
return ;
}
}
2018-08-09 18:39:46 +02:00
2018-08-15 22:46:20 +02:00
getApp ( ) - > windows - > forceLayoutChannelViews ( ) ;
2023-04-09 12:18:56 +02:00
2018-08-15 22:46:20 +02:00
loadedEventQueued = false ;
}
2018-08-09 18:39:46 +02:00
2018-08-15 22:46:20 +02:00
template < typename Assign >
auto makeConvertCallback ( const QVector < Frame < QImage > > & parsed ,
Assign assign )
{
2022-09-04 13:23:14 +02:00
static std : : queue < std : : pair < Assign , QVector < Frame < QPixmap > > > > queued ;
static std : : mutex mutex ;
static std : : atomic_bool loadedEventQueued { false } ;
2018-08-15 22:46:20 +02:00
return [ parsed , assign ] {
// convert to pixmap
2022-09-04 13:23:14 +02:00
QVector < Frame < QPixmap > > frames ;
frames . reserve ( parsed . size ( ) ) ;
2018-08-15 22:46:20 +02:00
std : : transform ( parsed . begin ( ) , parsed . end ( ) ,
std : : back_inserter ( frames ) , [ ] ( auto & frame ) {
return Frame < QPixmap > {
QPixmap : : fromImage ( frame . image ) ,
frame . duration } ;
} ) ;
// put into stack
std : : lock_guard < std : : mutex > lock ( mutex ) ;
queued . emplace ( assign , frames ) ;
2018-10-21 13:43:02 +02:00
if ( ! loadedEventQueued )
{
2018-08-15 22:46:20 +02:00
loadedEventQueued = true ;
QTimer : : singleShot ( 100 , [ = ] {
assignDelayed ( queued , mutex , loadedEventQueued ) ;
} ) ;
}
} ;
}
2018-09-20 13:09:37 +02:00
} // namespace detail
2017-01-18 21:30:23 +01:00
2018-08-02 14:23:27 +02:00
// IMAGE2
2019-10-07 20:03:15 +02:00
Image : : ~ Image ( )
{
2023-02-11 20:20:46 +01:00
# ifndef DISABLE_IMAGE_EXPIRATION_POOL
2022-09-04 13:23:14 +02:00
ImageExpirationPool : : instance ( ) . removeImagePtr ( this ) ;
2023-02-11 20:20:46 +01:00
# endif
2022-09-04 13:23:14 +02:00
if ( this - > empty_ & & ! this - > frames_ )
2021-02-13 19:17:22 +01:00
{
// No data in this image, don't bother trying to release it
// The reason we do this check is that we keep a few (or one) static empty image around that are deconstructed at the end of the programs lifecycle, and we want to prevent the isGuiThread call to be called after the QApplication has been exited
return ;
}
2022-10-30 14:01:54 +01:00
// Ensure the destructor for our frames is called in the GUI thread
// If the Image destructor is called outside of the GUI thread, move the
// ownership of the frames to the GUI thread, otherwise the frames will be
// destructed as part as we go out of scope
2019-10-07 20:03:15 +02:00
if ( ! isGuiThread ( ) )
{
2020-11-08 12:02:19 +01:00
postToThread ( [ frames = this - > frames_ . release ( ) ] ( ) {
delete frames ;
} ) ;
2019-10-07 20:03:15 +02:00
}
}
2018-08-02 14:23:27 +02:00
ImagePtr Image : : fromUrl ( const Url & url , qreal scale )
2017-01-05 16:07:20 +01:00
{
2018-08-02 14:23:27 +02:00
static std : : unordered_map < Url , std : : weak_ptr < Image > > cache ;
static std : : mutex mutex ;
std : : lock_guard < std : : mutex > lock ( mutex ) ;
auto shared = cache [ url ] . lock ( ) ;
2018-10-21 13:43:02 +02:00
if ( ! shared )
{
2018-08-02 14:23:27 +02:00
cache [ url ] = shared = ImagePtr ( new Image ( url , scale ) ) ;
2018-10-21 13:43:02 +02:00
}
2018-08-02 14:23:27 +02:00
return shared ;
2017-01-05 16:07:20 +01:00
}
2022-09-04 13:23:14 +02:00
ImagePtr Image : : fromResourcePixmap ( const QPixmap & pixmap , qreal scale )
2018-04-06 16:37:30 +02:00
{
2022-09-04 13:23:14 +02:00
using key_t = std : : pair < const QPixmap * , qreal > ;
static std : : unordered_map < key_t , std : : weak_ptr < Image > , boost : : hash < key_t > >
cache ;
static std : : mutex mutex ;
std : : lock_guard < std : : mutex > lock ( mutex ) ;
auto it = cache . find ( { & pixmap , scale } ) ;
if ( it ! = cache . end ( ) )
{
auto shared = it - > second . lock ( ) ;
if ( shared )
{
return shared ;
}
else
{
cache . erase ( it ) ;
}
}
auto newImage = ImagePtr ( new Image ( scale ) ) ;
newImage - > setPixmap ( pixmap ) ;
2019-08-01 13:30:58 +02:00
2022-09-04 13:23:14 +02:00
// store in cache
cache . insert ( { { & pixmap , scale } , std : : weak_ptr < Image > ( newImage ) } ) ;
2019-08-01 13:30:58 +02:00
2022-09-04 13:23:14 +02:00
return newImage ;
2018-08-02 14:23:27 +02:00
}
2018-04-06 16:37:30 +02:00
2018-08-02 14:23:27 +02:00
ImagePtr Image : : getEmpty ( )
{
static auto empty = ImagePtr ( new Image ) ;
return empty ;
}
2018-04-06 16:37:30 +02:00
2022-12-31 15:41:01 +01:00
ImagePtr getEmptyImagePtr ( )
{
return Image : : getEmpty ( ) ;
}
2018-08-02 14:23:27 +02:00
Image : : Image ( )
2018-08-06 18:25:47 +02:00
: empty_ ( true )
2018-08-02 14:23:27 +02:00
{
}
Image : : Image ( const Url & url , qreal scale )
2018-08-06 18:25:47 +02:00
: url_ ( url )
, scale_ ( scale )
, shouldLoad_ ( true )
2018-09-20 13:09:37 +02:00
, frames_ ( std : : make_unique < detail : : Frames > ( ) )
2018-08-02 14:23:27 +02:00
{
2017-01-11 18:52:09 +01:00
}
2019-08-01 13:30:58 +02:00
Image : : Image ( qreal scale )
2018-08-06 18:25:47 +02:00
: scale_ ( scale )
2019-08-01 13:30:58 +02:00
, frames_ ( std : : make_unique < detail : : Frames > ( ) )
{
}
void Image : : setPixmap ( const QPixmap & pixmap )
2018-08-02 14:23:27 +02:00
{
2019-08-01 13:30:58 +02:00
auto setFrames = [ shared = this - > shared_from_this ( ) , pixmap ] ( ) {
shared - > frames_ = std : : make_unique < detail : : Frames > (
QVector < detail : : Frame < QPixmap > > { detail : : Frame < QPixmap > { pixmap , 1 } } ) ;
} ;
if ( isGuiThread ( ) )
{
setFrames ( ) ;
}
else
{
postToThread ( setFrames ) ;
}
2018-08-02 14:23:27 +02:00
}
2017-10-27 20:09:02 +02:00
2018-08-06 18:25:47 +02:00
const Url & Image : : url ( ) const
2018-08-02 14:23:27 +02:00
{
return this - > url_ ;
}
2017-10-27 20:09:02 +02:00
2019-08-13 13:42:38 +02:00
bool Image : : loaded ( ) const
{
assertInGuiThread ( ) ;
return bool ( this - > frames_ - > current ( ) ) ;
}
boost : : optional < QPixmap > Image : : pixmapOrLoad ( ) const
2018-08-02 14:23:27 +02:00
{
assertInGuiThread ( ) ;
2018-04-06 17:46:12 +02:00
2022-09-04 13:23:14 +02:00
// Mark the image as just used.
// Any time this Image is painted, this method is invoked.
// See src/messages/layouts/MessageLayoutElement.cpp ImageLayoutElement::paint, for example.
this - > lastUsed_ = std : : chrono : : steady_clock : : now ( ) ;
2019-08-21 01:44:19 +02:00
this - > load ( ) ;
return this - > frames_ - > current ( ) ;
}
void Image : : load ( ) const
{
assertInGuiThread ( ) ;
2018-10-21 13:43:02 +02:00
if ( this - > shouldLoad_ )
{
2022-09-04 13:23:14 +02:00
Image * this2 = const_cast < Image * > ( this ) ;
this2 - > shouldLoad_ = false ;
this2 - > actuallyLoad ( ) ;
2023-02-11 20:20:46 +01:00
# ifndef DISABLE_IMAGE_EXPIRATION_POOL
2022-09-04 13:23:14 +02:00
ImageExpirationPool : : instance ( ) . addImagePtr ( this2 - > shared_from_this ( ) ) ;
2023-02-11 20:20:46 +01:00
# endif
2018-08-02 14:23:27 +02:00
}
}
2018-04-16 23:48:30 +02:00
2018-08-06 18:25:47 +02:00
qreal Image : : scale ( ) const
2018-08-02 14:23:27 +02:00
{
return this - > scale_ ;
}
2017-10-27 20:09:02 +02:00
2018-08-10 18:56:17 +02:00
bool Image : : isEmpty ( ) const
2018-08-02 14:23:27 +02:00
{
2018-08-06 18:25:47 +02:00
return this - > empty_ ;
2018-08-02 14:23:27 +02:00
}
2017-10-27 20:09:02 +02:00
2018-08-06 18:25:47 +02:00
bool Image : : animated ( ) const
2018-08-02 14:23:27 +02:00
{
2018-08-06 18:25:47 +02:00
assertInGuiThread ( ) ;
2017-10-27 20:09:02 +02:00
2018-08-11 17:15:17 +02:00
return this - > frames_ - > animated ( ) ;
2018-08-02 14:23:27 +02:00
}
2017-10-27 20:09:02 +02:00
2018-08-06 18:25:47 +02:00
int Image : : width ( ) const
2018-08-02 14:23:27 +02:00
{
2018-08-06 18:25:47 +02:00
assertInGuiThread ( ) ;
2018-04-16 23:48:30 +02:00
2018-08-11 17:15:17 +02:00
if ( auto pixmap = this - > frames_ - > first ( ) )
2019-09-22 10:27:05 +02:00
return int ( pixmap - > width ( ) * this - > scale_ ) ;
2018-08-06 18:25:47 +02:00
else
return 16 ;
2018-08-02 14:23:27 +02:00
}
2018-04-18 17:20:33 +02:00
2018-08-06 18:25:47 +02:00
int Image : : height ( ) const
2018-08-02 14:23:27 +02:00
{
2018-08-06 18:25:47 +02:00
assertInGuiThread ( ) ;
2017-10-27 20:09:02 +02:00
2018-08-11 17:15:17 +02:00
if ( auto pixmap = this - > frames_ - > first ( ) )
2019-08-20 23:29:11 +02:00
return int ( pixmap - > height ( ) * this - > scale_ ) ;
2018-08-06 18:25:47 +02:00
else
return 16 ;
2018-08-02 14:23:27 +02:00
}
2018-01-19 22:45:33 +01:00
2019-08-21 01:44:19 +02:00
void Image : : actuallyLoad ( )
2018-08-02 14:23:27 +02:00
{
2019-08-20 18:51:23 +02:00
NetworkRequest ( this - > url ( ) . string )
. concurrent ( )
2019-08-20 20:08:49 +02:00
. cache ( )
2019-08-21 00:01:27 +02:00
. onSuccess ( [ weak = weakOf ( this ) ] ( auto result ) - > Outcome {
2019-08-20 18:51:23 +02:00
auto shared = weak . lock ( ) ;
if ( ! shared )
return Failure ;
auto data = result . getData ( ) ;
// const cast since we are only reading from it
QBuffer buffer ( const_cast < QByteArray * > ( & data ) ) ;
buffer . open ( QIODevice : : ReadOnly ) ;
QImageReader reader ( & buffer ) ;
2021-06-24 22:54:36 +02:00
2022-05-28 13:48:31 +02:00
if ( ! reader . canRead ( ) )
{
qCDebug ( chatterinoImage )
< < " Error: image cant be read " < < shared - > url ( ) . string ;
2022-11-14 14:32:51 +01:00
shared - > empty_ = true ;
2022-05-28 13:48:31 +02:00
return Failure ;
}
const auto size = reader . size ( ) ;
if ( size . isEmpty ( ) )
{
2022-11-14 14:32:51 +01:00
shared - > empty_ = true ;
2022-05-28 13:48:31 +02:00
return Failure ;
}
// returns 1 for non-animated formats
if ( reader . imageCount ( ) < = 0 )
{
qCDebug ( chatterinoImage )
< < " Error: image has less than 1 frame "
< < shared - > url ( ) . string < < " : " < < reader . errorString ( ) ;
2022-11-14 14:32:51 +01:00
shared - > empty_ = true ;
2022-05-28 13:48:31 +02:00
return Failure ;
}
2021-07-03 22:11:10 +02:00
// use "double" to prevent int overflows
2022-05-28 13:48:31 +02:00
if ( double ( size . width ( ) ) * double ( size . height ( ) ) *
2021-07-03 22:11:10 +02:00
double ( reader . imageCount ( ) ) * 4.0 >
double ( Image : : maxBytesRam ) )
2021-06-24 22:54:36 +02:00
{
qCDebug ( chatterinoImage ) < < " image too large in RAM " ;
2022-11-14 14:32:51 +01:00
shared - > empty_ = true ;
2021-06-24 22:54:36 +02:00
return Failure ;
}
2019-08-21 00:01:27 +02:00
auto parsed = detail : : readFrames ( reader , shared - > url ( ) ) ;
2019-08-20 18:51:23 +02:00
2022-09-04 13:23:14 +02:00
postToThread ( makeConvertCallback ( parsed , [ weak ] ( auto & & frames ) {
2019-08-20 18:51:23 +02:00
if ( auto shared = weak . lock ( ) )
2022-09-04 13:23:14 +02:00
{
shared - > frames_ =
std : : make_unique < detail : : Frames > ( std : : move ( frames ) ) ;
}
2019-08-20 18:51:23 +02:00
} ) ) ;
return Success ;
} )
2019-09-19 19:03:50 +02:00
. onError ( [ weak = weakOf ( this ) ] ( auto /*result*/ ) {
2019-08-20 18:51:23 +02:00
auto shared = weak . lock ( ) ;
if ( ! shared )
return false ;
2019-09-22 10:27:05 +02:00
// fourtf: is this the right thing to do?
2019-08-20 18:51:23 +02:00
shared - > empty_ = true ;
return true ;
} )
. execute ( ) ;
2017-09-12 19:06:16 +02:00
}
2022-09-04 13:23:14 +02:00
void Image : : expireFrames ( )
{
assertInGuiThread ( ) ;
this - > frames_ - > clear ( ) ;
this - > shouldLoad_ = true ; // Mark as needing load again
}
2023-02-11 20:20:46 +01:00
# ifndef DISABLE_IMAGE_EXPIRATION_POOL
2022-09-04 13:23:14 +02:00
ImageExpirationPool : : ImageExpirationPool ( )
2023-04-09 12:18:56 +02:00
: freeTimer_ ( new QTimer )
2022-09-04 13:23:14 +02:00
{
2023-04-09 12:18:56 +02:00
QObject : : connect ( this - > freeTimer_ , & QTimer : : timeout , [ this ] {
2022-09-04 13:23:14 +02:00
if ( isGuiThread ( ) )
{
this - > freeOld ( ) ;
}
else
{
postToThread ( [ this ] {
this - > freeOld ( ) ;
} ) ;
}
} ) ;
2023-04-09 12:18:56 +02:00
this - > freeTimer_ - > start (
2022-09-04 13:23:14 +02:00
std : : chrono : : duration_cast < std : : chrono : : milliseconds > (
IMAGE_POOL_CLEANUP_INTERVAL ) ) ;
}
ImageExpirationPool & ImageExpirationPool : : instance ( )
{
static ImageExpirationPool instance ;
return instance ;
}
void ImageExpirationPool : : addImagePtr ( ImagePtr imgPtr )
{
std : : lock_guard < std : : mutex > lock ( this - > mutex_ ) ;
this - > allImages_ . emplace ( imgPtr . get ( ) , std : : weak_ptr < Image > ( imgPtr ) ) ;
}
void ImageExpirationPool : : removeImagePtr ( Image * rawPtr )
{
std : : lock_guard < std : : mutex > lock ( this - > mutex_ ) ;
this - > allImages_ . erase ( rawPtr ) ;
}
2023-05-27 14:18:08 +02:00
void ImageExpirationPool : : freeAll ( )
{
{
std : : lock_guard < std : : mutex > lock ( this - > mutex_ ) ;
for ( auto it = this - > allImages_ . begin ( ) ; it ! = this - > allImages_ . end ( ) ; )
{
auto img = it - > second . lock ( ) ;
img - > expireFrames ( ) ;
it = this - > allImages_ . erase ( it ) ;
}
}
this - > freeOld ( ) ;
}
2022-09-04 13:23:14 +02:00
void ImageExpirationPool : : freeOld ( )
{
std : : lock_guard < std : : mutex > lock ( this - > mutex_ ) ;
size_t numExpired = 0 ;
size_t eligible = 0 ;
auto now = std : : chrono : : steady_clock : : now ( ) ;
for ( auto it = this - > allImages_ . begin ( ) ; it ! = this - > allImages_ . end ( ) ; )
{
auto img = it - > second . lock ( ) ;
if ( ! img )
{
// This can only really happen from a race condition because ~Image
// should remove itself from the ImageExpirationPool automatically.
it = this - > allImages_ . erase ( it ) ;
continue ;
}
if ( img - > frames_ - > empty ( ) )
{
// No frame data, nothing to do
+ + it ;
continue ;
}
+ + eligible ;
// Check if image has expired and, if so, expire its frame data
auto diff = now - img - > lastUsed_ ;
if ( diff > IMAGE_POOL_IMAGE_LIFETIME )
{
+ + numExpired ;
img - > expireFrames ( ) ;
// erase without mutex locking issue
it = this - > allImages_ . erase ( it ) ;
continue ;
}
+ + it ;
}
2023-02-11 20:20:46 +01:00
# ifndef NDEBUG
2022-09-04 13:23:14 +02:00
qCDebug ( chatterinoImage ) < < " freed frame data for " < < numExpired < < " / "
< < eligible < < " eligible images " ;
2023-02-11 20:20:46 +01:00
# endif
2023-05-27 14:18:08 +02:00
DebugCount : : set ( " last image gc: expired " , numExpired ) ;
DebugCount : : set ( " last image gc: eligible " , eligible ) ;
DebugCount : : set ( " last image gc: left after gc " , this - > allImages_ . size ( ) ) ;
2022-09-04 13:23:14 +02:00
}
2023-02-11 20:20:46 +01:00
# endif
2017-04-14 17:52:22 +02:00
} // namespace chatterino