new gif playing from separate threads

This commit is contained in:
John Preston 2015-12-15 17:50:51 +03:00
parent a66c051eb5
commit 5e0e0d2391
9 changed files with 669 additions and 72 deletions

View File

@ -74,6 +74,7 @@ namespace {
DocumentItems documentItems;
WebPageItems webPageItems;
SharedContactItems sharedContactItems;
GifItems gifItems;
typedef QMap<HistoryItem*, QMap<HistoryReply*, bool> > 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()) {

View File

@ -39,6 +39,7 @@ typedef QMap<AudioData*, HistoryItemsMap> AudioItems;
typedef QMap<DocumentData*, HistoryItemsMap> DocumentItems;
typedef QMap<WebPageData*, HistoryItemsMap> WebPageItems;
typedef QMap<int32, HistoryItemsMap> SharedContactItems;
typedef QMap<ClipReader*, HistoryItem*> 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();

View File

@ -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

View File

@ -27,6 +27,8 @@ Copyright (c) 2014-2015 John Preston, https://desktop.telegram.org
namespace {
AnimationManager *_manager = 0;
QVector<QThread*> _clipThreads;
QVector<ClipReadManager*> _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;
}

View File

@ -335,6 +335,8 @@ AnimationCallbacks *animation(Param param, Type *obj, typename AnimationCallback
return new AnimationCallbacksAbsoluteWithParam<Type, Param>(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<Animation*, NullType> 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 &current(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<ClipReader*, ClipReaderPrivate*> 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<ClipReaderPrivate*, uint64> Readers;
Readers _readers;
QTimer _timer;
QThread *_processingInThread;
};

View File

@ -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)
{

View File

@ -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;

View File

@ -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<HistoryGif*>(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 {

View File

@ -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<HistoryGif*>(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 {