diff --git a/Telegram/SourceFiles/app.cpp b/Telegram/SourceFiles/app.cpp index d54927271..f8cafc0c9 100644 --- a/Telegram/SourceFiles/app.cpp +++ b/Telegram/SourceFiles/app.cpp @@ -74,6 +74,7 @@ namespace { DocumentItems documentItems; WebPageItems webPageItems; SharedContactItems sharedContactItems; + GifItems gifItems; typedef QMap > RepliesTo; RepliesTo repliesTo; @@ -1991,6 +1992,7 @@ namespace App { ::documentItems.clear(); ::webPageItems.clear(); ::sharedContactItems.clear(); + ::gifItems.clear(); ::repliesTo.clear(); lastPhotos.clear(); lastPhotosMap.clear(); @@ -2425,6 +2427,18 @@ namespace App { return ::sharedContactItems; } + void regGifItem(ClipReader *reader, HistoryItem *item) { + ::gifItems.insert(reader, item); + } + + void unregGifItem(ClipReader *reader) { + ::gifItems.remove(reader); + } + + const GifItems &gifItems() { + return ::gifItems; + } + QString phoneFromSharedContact(int32 userId) { SharedContactItems::const_iterator i = ::sharedContactItems.constFind(userId); if (i != ::sharedContactItems.cend()) { diff --git a/Telegram/SourceFiles/app.h b/Telegram/SourceFiles/app.h index 23ab34bd3..465c2fa7c 100644 --- a/Telegram/SourceFiles/app.h +++ b/Telegram/SourceFiles/app.h @@ -39,6 +39,7 @@ typedef QMap AudioItems; typedef QMap DocumentItems; typedef QMap WebPageItems; typedef QMap SharedContactItems; +typedef QMap GifItems; struct ReplyMarkup { ReplyMarkup(int32 flags = 0) : flags(flags) { } @@ -250,6 +251,10 @@ namespace App { const SharedContactItems &sharedContactItems(); QString phoneFromSharedContact(int32 userId); + void regGifItem(ClipReader *reader, HistoryItem *item); + void unregGifItem(ClipReader *reader); + const GifItems &gifItems(); + void regMuted(PeerData *peer, int32 changeIn); void unregMuted(PeerData *peer); void updateMuted(); diff --git a/Telegram/SourceFiles/config.h b/Telegram/SourceFiles/config.h index 8a5457413..3888f20d6 100644 --- a/Telegram/SourceFiles/config.h +++ b/Telegram/SourceFiles/config.h @@ -83,6 +83,7 @@ enum { LocalEncryptKeySize = 256, // 2048 bit AnimationTimerDelta = 7, + ClipThreadsCount = 8, SaveRecentEmojisTimeout = 3000, // 3 secs SaveWindowPositionTimeout = 1000, // 1 sec @@ -108,12 +109,14 @@ enum { AudioVoiceMsgUpdateView = 100, // 100ms AudioVoiceMsgChannels = 2, // stereo AudioVoiceMsgBufferSize = 1024 * 1024, // 1 Mb buffers - AudioVoiceMsgInMemory = 1024 * 1024, // 1 Mb audio is hold in memory and auto loaded + AudioVoiceMsgInMemory = 2 * 1024 * 1024, // 2 Mb audio is hold in memory and auto loaded AudioPauseDeviceTimeout = 3000, // pause in 3 secs after playing is over - StickerInMemory = 1024 * 1024, // 1024 Kb stickers hold in memory, auto loaded and displayed inline + StickerInMemory = 2 * 1024 * 1024, // 1 Mb stickers hold in memory, auto loaded and displayed inline StickerMaxSize = 2048, // 2048x2048 is a max image size for sticker + AnimationInMemory = 2 * 1024 * 1024, // 2 Mb gif and mp4 animations held in memory while playing + MediaViewImageSizeLimit = 100 * 1024 * 1024, // show up to 100mb jpg/png/gif docs in app MaxZoomLevel = 7, // x8 ZoomToScreenLevel = 1024, // just constant diff --git a/Telegram/SourceFiles/gui/animation.cpp b/Telegram/SourceFiles/gui/animation.cpp index 4f6004062..2fe92a5fb 100644 --- a/Telegram/SourceFiles/gui/animation.cpp +++ b/Telegram/SourceFiles/gui/animation.cpp @@ -27,6 +27,8 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org namespace { AnimationManager *_manager = 0; + QVector _clipThreads; + QVector _clipManagers; }; namespace anim { @@ -79,13 +81,24 @@ namespace anim { } void startManager() { - delete _manager; + stopManager(); + _manager = new AnimationManager(); + } void stopManager() { delete _manager; _manager = 0; + if (!_clipThreads.isEmpty()) { + for (int32 i = 0, l = _clipThreads.size(); i < l; ++i) { + _clipThreads.at(i)->quit(); + _clipThreads.at(i)->wait(); + delete _clipManagers.at(i); + } + _clipThreads.clear(); + _clipManagers.clear(); + } } } @@ -105,6 +118,27 @@ void Animation::stop() { _manager->stop(this); } +void AnimationManager::clipReinit(ClipReader *reader) { + const GifItems &items(App::gifItems()); + GifItems::const_iterator it = items.constFind(reader); + if (it != items.cend()) { + it.value()->initDimensions(); + if (App::main()) emit App::main()->itemResized(it.value(), true); + } +} + +void AnimationManager::clipRedraw(ClipReader *reader) { + if (reader->currentDisplayed()) { + return; + } + + const GifItems &items(App::gifItems()); + GifItems::const_iterator it = items.constFind(reader); + if (it != items.cend()) { + Ui::redrawHistoryItem(it.value()); + } +} + void AnimatedGif::step_frame(float64 ms, bool timer) { int32 f = frame; while (f < images.size() && ms > delays[f]) { @@ -261,29 +295,460 @@ const QPixmap &AnimatedGif::current(int32 width, int32 height, bool rounded) { return frames[frame]; } -ClipReader::ClipReader(const FileLocation &location, const QByteArray &data) : _state(ClipStopped) -, _location(location.isEmpty() ? 0 : new FileLocation(location)) -, _data(data) +QPixmap _prepareFrame(const ClipFrameRequest &request, const QImage &original, QImage &cache, bool smooth) { + bool badSize = (original.width() != request.framew) || (original.height() != request.frameh); + bool needOuter = (request.outerw != request.framew) || (request.outerh != request.frameh); + if (badSize || needOuter || request.rounded) { + int32 factor(request.factor); + bool fill = false; + if (cache.width() != request.outerw || cache.height() != request.outerh) { + cache = QImage(request.outerw, request.outerh, QImage::Format_ARGB32_Premultiplied); + if (request.framew < request.outerw || request.frameh < request.outerh || original.hasAlphaChannel()) { + fill = true; + } + cache.setDevicePixelRatio(factor); + } + { + Painter p(&cache); + if (fill) p.fillRect(0, 0, cache.width() / factor, cache.height() / factor, st::black); + if (smooth && badSize) p.setRenderHint(QPainter::SmoothPixmapTransform); + QRect to((request.outerw - request.framew) / (2 * factor), (request.outerh - request.frameh) / (2 * factor), request.framew / factor, request.frameh / factor); + QRect from(0, 0, original.width() / factor, original.height() / factor); + p.drawImage(to, original, from, Qt::ColorOnly); + } + if (request.rounded) { + imageRound(cache); + } + return QPixmap::fromImage(cache, Qt::ColorOnly); + } + return QPixmap::fromImage(original, Qt::ColorOnly); +} + +ClipReader::ClipReader(const FileLocation &location, const QByteArray &data) : _state(ClipReading) , _width(0) , _height(0) -, _rounded(false) -, _currentDisplayed(true) { +, _currentDisplayed(1) +, _private(0) { + if (_clipThreads.size() < ClipThreadsCount) { + _threadIndex = _clipThreads.size(); + _clipThreads.push_back(new QThread()); + _clipManagers.push_back(new ClipReadManager(_clipThreads.back())); + _clipThreads.back()->start(); + } else { + _threadIndex = rand() % _clipThreads.size(); + int32 loadLevel = 0x7FFFFFFF; + for (int32 i = 0, l = _clipThreads.size(); i < l; ++i) { + int32 level = _clipManagers.at(i)->loadLevel(); + if (level < loadLevel) { + _threadIndex = i; + loadLevel = level; + } + } + } + _clipManagers.at(_threadIndex)->append(this, location, data); } -void ClipReader::start(int32 framew, int32 frameh, bool rounded) { - _rounded = rounded; +void ClipReader::start(int32 framew, int32 frameh, int32 outerw, int32 outerh, bool rounded) { + int32 factor(cIntRetinaFactor()); + _request.factor = factor; + _request.framew = framew * factor; + _request.frameh = frameh * factor; + _request.outerw = outerw * factor; + _request.outerh = outerh * factor; + _request.rounded = rounded; + _clipManagers.at(_threadIndex)->start(this); } -const QPixmap &ClipReader::current(int32 framew, int32 frameh) { - _currentDisplayed = true; - return _current; +QPixmap ClipReader::current(int32 framew, int32 frameh, int32 outerw, int32 outerh) { + _currentDisplayed.storeRelease(1); + + int32 factor(cIntRetinaFactor()); + QPixmap result(_current); + if (result.width() == outerw * factor && result.height() == outerh * factor) { + return result; + } + + _request.framew = framew * factor; + _request.frameh = frameh * factor; + _request.outerw = outerw * factor; + _request.outerh = outerh * factor; + + QImage current(_currentOriginal); + result = _current = QPixmap(); + result = _current = _prepareFrame(_request, current, _cacheForResize, true); + + _clipManagers.at(_threadIndex)->update(this); + + return result; +} + +bool ClipReader::ready() const { + if (_width && _height) return true; + + QImage first(_currentOriginal); + if (first.isNull()) return false; + + _width = first.width(); + _height = first.height(); + return true; +} + +int32 ClipReader::width() const { + return _width; +} + +int32 ClipReader::height() const { + return _height; } ClipState ClipReader::state() const { return _state; } -ClipReader::~ClipReader() { - delete _location; - setBadPointer(_location); +void ClipReader::stop() { + _clipManagers.at(_threadIndex)->stop(this); + _width = _height = 0; +} + +void ClipReader::error() { + _private = 0; + _state = ClipError; +} + +ClipReader::~ClipReader() { + stop(); +} + +class ClipReaderPrivate { +public: + + ClipReaderPrivate(ClipReader *reader, const FileLocation &location, const QByteArray &data) : _interface(reader) + , _state(ClipReading) + , _data(data) + , _location(_data.isEmpty() ? new FileLocation(location) : 0) + , _accessed(false) + , _buffer(_data.isEmpty() ? 0 : &_data) + , _reader(0) + , _currentMs(0) + , _nextUpdateMs(0) { + + if (_data.isEmpty() && !_location->accessEnable()) { + error(); + return; + } + _accessed = true; + } + + ClipProcessResult start(uint64 ms) { + _nextUpdateMs = ms + 86400 * 1000ULL; + if (!_reader && !restartReader(true)) { + return error(); + } + if (_currentOriginal.isNull()) { + if (!readNextFrame(_currentOriginal)) { + return error(); + } + --_framesLeft; + return ClipProcessReinit; + } + return ClipProcessWait; + } + + ClipProcessResult process(uint64 ms) { // -1 - do nothing, 0 - update, 1 - reinit + if (_state == ClipError) return ClipProcessError; + + if (!_request.valid()) { + return start(ms); + } + + if (_current.isNull()) { // first frame read, but not yet prepared + _currentOriginal.setDevicePixelRatio(_request.factor); + + _currentMs = ms; + _current = _prepareFrame(_request, _currentOriginal, _currentCache, true); + + if (!prepareNextFrame()) { + return error(); + } + return ClipProcessStarted; + } else if (ms >= _nextUpdateMs) { + swapBuffers(); + return ClipProcessRedraw; + } + return ClipProcessWait; + } + + ClipProcessResult finishProcess(uint64 ms) { + if (!prepareNextFrame()) { + return error(); + } + + if (ms >= _nextUpdateMs) { // we are late + swapBuffers(ms); // keep up + return ClipProcessRedraw; + } + return ClipProcessWait; + } + + uint64 nextFrameDelay() { + return qMax(_reader->nextImageDelay(), 5); + } + + void swapBuffers(uint64 ms = 0) { + _currentMs = qMax(ms, _nextUpdateMs); + qSwap(_currentOriginal, _nextOriginal); + qSwap(_current, _next); + qSwap(_currentCache, _nextCache); + } + + bool readNextFrame(QImage &to) { + QImage frame; // QGifHandler always reads first to internal QImage and returns it + if (!_reader->read(&frame)) { + return false; + } + int32 w = frame.width(), h = frame.height(); + if (to.width() == w && to.height() == h && to.format() == frame.format()) { + if (to.byteCount() != frame.byteCount()) { + int bpl = qMin(to.bytesPerLine(), frame.bytesPerLine()); + for (int i = 0; i < h; ++i) { + memcpy(to.scanLine(i), frame.constScanLine(i), bpl); + } + } else { + memcpy(to.bits(), frame.constBits(), frame.byteCount()); + } + } else { + to = frame.copy(); + } + return true; + } + + bool prepareNextFrame() { + _nextUpdateMs = _currentMs + nextFrameDelay(); + if (!_framesLeft) { + if (_reader->jumpToImage(0)) { + _framesLeft = _reader->imageCount(); + } else if (!restartReader()) { + return false; + } + } + if (!readNextFrame(_nextOriginal)) { + return false; + } + _nextOriginal.setDevicePixelRatio(_request.factor); + --_framesLeft; + _next = QPixmap(); + _next = _prepareFrame(_request, _nextOriginal, _nextCache, true); + return true; + } + + bool restartReader(bool first = false) { + if (first && _data.isEmpty() && QFileInfo(_location->name()).size() <= AnimationInMemory) { + QFile f(_location->name()); + if (f.open(QIODevice::ReadOnly)) { + _data = f.readAll(); + if (f.error() == QFile::NoError) { + _buffer.setBuffer(&_data); + } else { + _data = QByteArray(); + } + } + } else if (!_data.isEmpty()) { + _buffer.close(); + } + delete _reader; + + if (_data.isEmpty()) { + _reader = new QImageReader(_location->name()); + } else { + _reader = new QImageReader(&_buffer); + } + if (!_reader->canRead() || !_reader->supportsAnimation()) { + return false; + } + _framesLeft = _reader->imageCount(); + if (_framesLeft < 1) { + return false; + } + return true; + } + + ClipProcessResult error() { + stop(); + _state = ClipError; + return ClipProcessError; + } + + void stop() { + delete _reader; + _reader = 0; + + if (_location) { + if (_accessed) { + _location->accessDisable(); + } + delete _location; + _location = 0; + } + _accessed = false; + } + + ~ClipReaderPrivate() { + stop(); + setBadPointer(_location); + setBadPointer(_reader); + } + +private: + + ClipReader *_interface; + ClipState _state; + + QByteArray _data; + FileLocation *_location; + bool _accessed; + + QBuffer _buffer; + QImageReader *_reader; + + ClipFrameRequest _request; + QPixmap _current, _next; + QImage _currentOriginal, _nextOriginal, _currentCache, _nextCache; + + int32 _framesLeft; + uint64 _currentMs, _nextUpdateMs; + + friend class ClipReadManager; + +}; + +ClipReadManager::ClipReadManager(QThread *thread) : _processingInThread(0) { + moveToThread(thread); + connect(thread, SIGNAL(started()), this, SLOT(process())); + connect(this, SIGNAL(processDelayed()), this, SLOT(process()), Qt::QueuedConnection); + + _timer.setSingleShot(true); + _timer.moveToThread(thread); + connect(&_timer, SIGNAL(timeout()), this, SLOT(process())); + + connect(this, SIGNAL(reinit(ClipReader*)), _manager, SLOT(clipReinit(ClipReader*))); + connect(this, SIGNAL(redraw(ClipReader*)), _manager, SLOT(clipRedraw(ClipReader*))); +} + +void ClipReadManager::append(ClipReader *reader, const FileLocation &location, const QByteArray &data) { + reader->_private = new ClipReaderPrivate(reader, location, data); + update(reader); +} + +void ClipReadManager::start(ClipReader *reader) { + update(reader); +} + +void ClipReadManager::update(ClipReader *reader) { + QMutexLocker lock(&_readerPointersMutex); + _readerPointers.insert(reader, reader->_private); + emit processDelayed(); +} + +void ClipReadManager::stop(ClipReader *reader) { + QMutexLocker lock(&_readerPointersMutex); + _readerPointers.remove(reader); + emit processDelayed(); +} + +bool ClipReadManager::handleProcessResult(ClipReaderPrivate *reader, ClipProcessResult result) { + QMutexLocker lock(&_readerPointersMutex); + ReaderPointers::iterator it = _readerPointers.find(reader->_interface); + if (result == ClipProcessError) { + if (it != _readerPointers.cend()) { + it.key()->error(); + _readerPointers.erase(it); + it = _readerPointers.end(); + } + } + if (it == _readerPointers.cend()) { + return false; + } + + if (result == ClipProcessReinit || result == ClipProcessRedraw || result == ClipProcessStarted) { + it.key()->_current = reader->_current; + it.key()->_currentOriginal = reader->_currentOriginal; + it.key()->_currentDisplayed.storeRelease(0); + if (result == ClipProcessReinit) { + emit reinit(it.key()); + } else if (result == ClipProcessRedraw) { + emit redraw(it.key()); + } + } + return true; +} + +ClipReadManager::ResultHandleState ClipReadManager::handleResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms) { + if (!handleProcessResult(reader, result)) { + delete reader; + return ResultHandleRemove; + } + + _processingInThread->eventDispatcher()->processEvents(QEventLoop::AllEvents); + if (_processingInThread->isInterruptionRequested()) { + return ResultHandleStop; + } + + if (result == ClipProcessRedraw) { + return handleResult(reader, reader->finishProcess(ms), ms); + } + + return ResultHandleContinue; +} + +void ClipReadManager::process() { + if (_processingInThread) return; + + _timer.stop(); + _processingInThread = thread(); + + uint64 ms = getms(), minms = ms + 86400 * 1000ULL; + { + QMutexLocker lock(&_readerPointersMutex); + for (ReaderPointers::iterator i = _readerPointers.begin(), e = _readerPointers.end(); i != e; ++i) { + if (i.value()) { + Readers::iterator it = _readers.find(i.value()); + if (it == _readers.cend()) { + _readers.insert(i.value(), 0); + } else { + it.value() = ms; + } + i.value()->_request = i.key()->_request; + i.value() = 0; + } + } + } + + for (Readers::iterator i = _readers.begin(), e = _readers.end(); i != e;) { + if (i.value() <= ms) { + ClipProcessResult result = i.key()->process(ms); + + ResultHandleState state = handleResult(i.key(), result, ms); + if (state == ResultHandleRemove) { + i = _readers.erase(i); + continue; + } else if (state == ResultHandleStop) { + _processingInThread = 0; + return; + } + i.value() = i.key()->_nextUpdateMs; + } + if (i.value() < minms) { + minms = i.value(); + } + ++i; + } + + ms = getms(); + if (minms <= ms) { + _timer.start(1); + } else { + _timer.start(minms - ms); + } + + _processingInThread = 0; } diff --git a/Telegram/SourceFiles/gui/animation.h b/Telegram/SourceFiles/gui/animation.h index 81d01efb2..d2c9b4e5f 100644 --- a/Telegram/SourceFiles/gui/animation.h +++ b/Telegram/SourceFiles/gui/animation.h @@ -335,6 +335,8 @@ AnimationCallbacks *animation(Param param, Type *obj, typename AnimationCallback return new AnimationCallbacksAbsoluteWithParam(param, obj, method); } +class ClipReader; + class AnimationManager : public QObject { Q_OBJECT @@ -403,6 +405,9 @@ public slots: } } + void clipReinit(ClipReader *reader); + void clipRedraw(ClipReader *reader); + private: typedef QMap AnimatingObjects; @@ -472,19 +477,45 @@ private: }; enum ClipState { - ClipPlaying, - ClipStopped, + ClipReading, + ClipError, }; +struct ClipFrameRequest { + ClipFrameRequest() : factor(0), framew(0), frameh(0), outerw(0), outerh(0), rounded(false) { + } + bool valid() const { + return factor > 0; + } + int32 factor; + int32 framew, frameh; + int32 outerw, outerh; + bool rounded; +}; + +class ClipReaderPrivate; class ClipReader { public: ClipReader(const FileLocation &location, const QByteArray &data); - void start(int32 framew, int32 frameh, bool rounded); - const QPixmap ¤t(int32 framew, int32 frameh); + void start(int32 framew, int32 frameh, int32 outerw, int32 outerh, bool rounded); + QPixmap current(int32 framew, int32 frameh, int32 outerw, int32 outerh); + bool currentDisplayed() const { + return _currentDisplayed.loadAcquire() > 0; + } + + int32 width() const; + int32 height() const; ClipState state() const; + bool started() const { + return _request.valid(); + } + bool ready() const; + + void stop(); + void error(); ~ClipReader(); @@ -492,12 +523,74 @@ private: ClipState _state; - FileLocation *_location; - QByteArray _data; - int32 _width, _height; - bool _rounded; + ClipFrameRequest _request; + + mutable int32 _width, _height; QPixmap _current; - bool _currentDisplayed; + QImage _currentOriginal, _cacheForResize; + QAtomicInt _currentDisplayed; + int32 _threadIndex; + + friend class ClipReadManager; + + ClipReaderPrivate *_private; + +}; + +enum ClipProcessResult { + ClipProcessError, + ClipProcessStarted, + ClipProcessReinit, + ClipProcessRedraw, + ClipProcessWait, +}; + +class ClipReadManager : public QObject { + Q_OBJECT + +public: + + ClipReadManager(QThread *thread); + int32 loadLevel() const { + return _loadLevel.loadAcquire(); + } + void append(ClipReader *reader, const FileLocation &location, const QByteArray &data); + void start(ClipReader *reader); + void update(ClipReader *reader); + void stop(ClipReader *reader); + +signals: + + void processDelayed(); + + void reinit(ClipReader *reader); + void redraw(ClipReader *reader); + +public slots: + + void process(); + +private: + + QAtomicInt _loadLevel; + typedef QMap ReaderPointers; + ReaderPointers _readerPointers; + QMutex _readerPointersMutex; + + bool handleProcessResult(ClipReaderPrivate *reader, ClipProcessResult result); + + enum ResultHandleState { + ResultHandleRemove, + ResultHandleStop, + ResultHandleContinue, + }; + ResultHandleState handleResult(ClipReaderPrivate *reader, ClipProcessResult result, uint64 ms); + + typedef QMap Readers; + Readers _readers; + + QTimer _timer; + QThread *_processingInThread; }; diff --git a/Telegram/SourceFiles/history.cpp b/Telegram/SourceFiles/history.cpp index 3815e262e..3982977e4 100644 --- a/Telegram/SourceFiles/history.cpp +++ b/Telegram/SourceFiles/history.cpp @@ -4569,7 +4569,8 @@ ImagePtr HistoryDocument::replyPreview() { HistoryGif::HistoryGif(DocumentData *document) : HistoryFileMedia() , _data(document) , _thumbw(1) -, _thumbh(1) { +, _thumbh(1) +, _gif(0) { setLinks(new DocumentOpenLink(_data), new DocumentSaveLink(_data), new DocumentCancelLink(_data)); setStatusSize(FileStatusSizeReady); @@ -4580,9 +4581,9 @@ HistoryGif::HistoryGif(DocumentData *document) : HistoryFileMedia() void HistoryGif::initDimensions(const HistoryItem *parent) { bool bubble = parent->hasBubble(); int32 tw = 0, th = 0; - if (parent == animated.msg) { - tw = convertScale(animated.w / cIntRetinaFactor()); - th = convertScale(animated.h / cIntRetinaFactor()); + if (_gif && _gif->ready()) { + tw = convertScale(_gif->width()); + th = convertScale(_gif->height()); } else { tw = convertScale(_data->dimensions.width()), th = convertScale(_data->dimensions.height()); if (!tw || !th) { @@ -4603,20 +4604,17 @@ void HistoryGif::initDimensions(const HistoryItem *parent) { } _thumbw = tw; _thumbh = th; - if (parent == animated.msg) { - _maxw = qMax(tw, int32(st::minPhotoSize)); - _minh = qMax(th, int32(st::minPhotoSize)); - } else { - int32 minWidth = qMax(st::minPhotoSize, parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); - minWidth = qMax(minWidth, gifMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); - _maxw = qMax(tw, minWidth); - _minh = qMax(th, int32(st::minPhotoSize)); + _maxw = qMax(tw, int32(st::minPhotoSize)); + _minh = qMax(th, int32(st::minPhotoSize)); + if (!_gif || !_gif->ready()) { + _maxw = qMax(_maxw, parent->infoWidth() + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); + _maxw = qMax(_maxw, gifMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); } - w = _maxw; if (bubble) { _maxw += st::mediaPadding.left() + st::mediaPadding.right(); _minh += st::mediaPadding.top() + st::mediaPadding.bottom(); } + w = _maxw; _height = _minh; } @@ -4624,7 +4622,7 @@ void HistoryGif::draw(Painter &p, const HistoryItem *parent, const QRect &r, boo if (w < st::msgPadding.left() + st::msgPadding.right() + 1) return; int32 width = w, height = _height, skipx = 0, skipy = 0; - bool animating = (parent == animated.msg); + bool animating = (_gif && _gif->started()); bool bubble = parent->hasBubble(); bool fromChannel = parent->fromChannel(), out = parent->out(), outbg = out && !fromChannel; @@ -4653,7 +4651,7 @@ void HistoryGif::draw(Painter &p, const HistoryItem *parent, const QRect &r, boo QRect rthumb(rtlrect(skipx, skipy, width, height, w)); if (animating) { - p.drawPixmap(rthumb.topLeft(), animated.current(width, height, true)); + p.drawPixmap(rthumb.topLeft(), _gif->current(_thumbw, _thumbh, width, height)); } else { p.drawPixmap(rthumb.topLeft(), _data->thumb->pixBlurredSingle(_thumbw, _thumbh, width, height)); } @@ -4753,9 +4751,9 @@ int32 HistoryGif::resize(int32 width, const HistoryItem *parent) { bool bubble = parent->hasBubble(); int32 tw = 0, th = 0; - if (parent == animated.msg) { - tw = convertScale(animated.w / cIntRetinaFactor()); - th = convertScale(animated.h / cIntRetinaFactor()); + if (_gif && _gif->ready()) { + tw = convertScale(_gif->width()); + th = convertScale(_gif->height()); } else { tw = convertScale(_data->dimensions.width()), th = convertScale(_data->dimensions.height()); if (!tw || !th) { @@ -4782,20 +4780,24 @@ int32 HistoryGif::resize(int32 width, const HistoryItem *parent) { th = qRound((width / float64(tw)) * th); tw = width; } - w = _thumbw = tw; + _thumbw = tw; _thumbh = th; - if (parent != animated.msg) { - int32 minWidth = qMax(st::minPhotoSize, parent->infoWidth() + 2 * (st::msgDateImgDelta + st::msgDateImgPadding.x())); - minWidth = qMax(minWidth, gifMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); - w = qMax(w, minWidth); - } - + w = qMax(tw, int32(st::minPhotoSize)); _height = qMax(th, int32(st::minPhotoSize)); + if (_gif && _gif->ready()) { + if (!_gif->started()) { + _gif->start(_thumbw, _thumbh, w, _height, true); + } + } else { + w = qMax(w, parent->infoWidth() + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); + w = qMax(w, gifMaxStatusWidth(_data) + 2 * int32(st::msgDateImgDelta + st::msgDateImgPadding.x())); + } if (bubble) { w += st::mediaPadding.left() + st::mediaPadding.right(); _height += st::mediaPadding.top() + st::mediaPadding.bottom(); } + return _height; } @@ -4821,9 +4823,9 @@ int32 HistoryGif::countHeight(const HistoryItem *parent, int32 width) const { bool bubble = parent->hasBubble(); int32 tw = 0, th = 0; - if (parent == animated.msg) { - tw = convertScale(animated.w / cIntRetinaFactor()); - th = convertScale(animated.h / cIntRetinaFactor()); + if (_gif && _gif->started()) { + tw = convertScale(_gif->width()); + th = convertScale(_gif->height()); } else { tw = convertScale(_data->dimensions.width()), th = convertScale(_data->dimensions.height()); if (!tw || !th) { @@ -4873,7 +4875,7 @@ void HistoryGif::getState(TextLinkPtr &lnk, HistoryCursorState &state, int32 x, height -= skipy + st::mediaPadding.bottom(); } if (x >= skipx && y >= skipy && x < skipx + width && y < skipy + height) { - if (parent == animated.msg) { + if (_gif && _gif->started()) { lnk = _openl; } else { lnk = _data->already().isEmpty() ? (_data->loader ? _cancell : _savel) : _openl; @@ -4896,6 +4898,25 @@ ImagePtr HistoryGif::replyPreview() { return _data->makeReplyPreview(); } +void HistoryGif::play(HistoryItem *parent) { + if (_gif) { + App::unregGifItem(_gif); + delete _gif; + _gif = 0; + } else { + _gif = new ClipReader(_data->location(), _data->data); + App::regGifItem(_gif, parent); + } +} + +HistoryGif::~HistoryGif() { + if (_gif) { + App::unregGifItem(_gif); + delete _gif; + setBadPointer(_gif); + } +} + HistorySticker::HistorySticker(DocumentData *document) : HistoryMedia() , pixw(1), pixh(1), data(document), lastw(0) { diff --git a/Telegram/SourceFiles/history.h b/Telegram/SourceFiles/history.h index 0b98b362c..c4f6a0aae 100644 --- a/Telegram/SourceFiles/history.h +++ b/Telegram/SourceFiles/history.h @@ -1592,9 +1592,6 @@ public: } ImagePtr replyPreview(); - void drawInPlaylist(Painter &p, const HistoryItem *parent, bool selected, bool over, int32 width) const; - TextLinkPtr linkInPlaylist(); - bool needsBubble(const HistoryItem *parent) const { return parent->toHistoryReply(); } @@ -1608,6 +1605,9 @@ public: return true; } + void play(HistoryItem *parent); + ~HistoryGif(); + protected: float64 dataProgress() const { @@ -1624,6 +1624,7 @@ private: DocumentData *_data; int32 _thumbw, _thumbh; + ClipReader *_gif; void setStatusSize(int32 newSize) const; void updateStatusText(const HistoryItem *parent) const; diff --git a/Telegram/SourceFiles/mainwidget.cpp b/Telegram/SourceFiles/mainwidget.cpp index 7ed9b96ac..a67beecb7 100644 --- a/Telegram/SourceFiles/mainwidget.cpp +++ b/Telegram/SourceFiles/mainwidget.cpp @@ -437,7 +437,6 @@ MainWidget::MainWidget(Window *window) : TWidget(window) connect(&_topBar, SIGNAL(clicked()), this, SLOT(onTopBarClick())); connect(&history, SIGNAL(historyShown(History*,MsgId)), this, SLOT(onHistoryShown(History*,MsgId))); connect(&updateNotifySettingTimer, SIGNAL(timeout()), this, SLOT(onUpdateNotifySettings())); - connect(this, SIGNAL(showPeerAsync(quint64,qint32)), this, SLOT(showPeerHistory(quint64,qint32)), Qt::QueuedConnection); if (audioPlayer()) { connect(audioPlayer(), SIGNAL(updated(const AudioMsgId&)), this, SLOT(audioPlayProgress(const AudioMsgId&))); connect(audioPlayer(), SIGNAL(stopped(const AudioMsgId&)), this, SLOT(audioPlayProgress(const AudioMsgId&))); @@ -1886,17 +1885,15 @@ void MainWidget::documentLoadProgress(mtpFileLoader *loader) { } else if (document->openOnSave > 0 && document->size < MediaViewImageSizeLimit) { const FileLocation &location(document->location(true)); if (location.accessEnable()) { - QImageReader reader(location.name()); - if (reader.canRead()) { - if (reader.supportsAnimation() && reader.imageCount() > 1 && item && item->getMedia() && item->getMedia()->type() == MediaTypeGif) { - startGif(item, location); - } else if (item) { + if (item && item->getMedia() && item->getMedia()->type() == MediaTypeGif) { + static_cast(item->getMedia())->play(item); + } else { + QImageReader reader(location.name()); + if (reader.canRead() && item) { App::wnd()->showDocument(document, item); } else { psOpenFile(already); } - } else { - psOpenFile(already); } location.accessDisable(); } else { diff --git a/Telegram/SourceFiles/structs.cpp b/Telegram/SourceFiles/structs.cpp index eff73cd29..6dda51a59 100644 --- a/Telegram/SourceFiles/structs.cpp +++ b/Telegram/SourceFiles/structs.cpp @@ -886,17 +886,15 @@ void DocumentOpenLink::doOpen(DocumentData *data) { if (App::main()) App::main()->documentPlayProgress(song); } } else if (data->size < MediaViewImageSizeLimit && location.accessEnable()) { - QImageReader reader(location.name()); - if (reader.canRead()) { - if (reader.supportsAnimation() && reader.imageCount() > 1 && App::hoveredLinkItem() && App::hoveredLinkItem()->getMedia() && App::hoveredLinkItem()->getMedia()->type() == MediaTypeGif) { - startGif(App::hoveredLinkItem(), location); - } else if (App::hoveredLinkItem() || App::contextItem()) { + if (App::hoveredLinkItem() && App::hoveredLinkItem()->getMedia() && App::hoveredLinkItem()->getMedia()->type() == MediaTypeGif) { + static_cast(App::hoveredLinkItem()->getMedia())->play(App::hoveredLinkItem()); + } else { + QImageReader reader(location.name()); + if (reader.canRead() && (App::hoveredLinkItem() || App::contextItem())) { App::wnd()->showDocument(data, App::hoveredLinkItem() ? App::hoveredLinkItem() : App::contextItem()); } else { psOpenFile(location.name()); } - } else { - psOpenFile(location.name()); } location.accessDisable(); } else {