How the Telegram Desktop Media Preview Widget Handles Various File Types

The Window::MediaPreviewWidget in telegramdesktop/tdesktop detects whether the supplied media is a photo, static sticker, Lottie animation, WebM video, or GIF, then routes each type through specialized rendering pipelines—including PhotoMedia views, DocumentMedia stickers, Lottie::SinglePlayer for JSON animations, and Media::Clip::Reader for WebM/GIF clips—to produce a scaled, cached preview.

The media preview widget in Telegram Desktop provides a lightweight, transient overlay that appears when users hover over messages or drag files. As implemented in the telegramdesktop/tdesktop repository, this component must gracefully handle everything from static images to complex animated stickers. Understanding how the media preview widget handles various file types requires examining the type-detection logic in Telegram/SourceFiles/window/window_media_preview.cpp and the specialized media views that power each rendering path.

Type Dispatch and Core Logic

The widget determines what to display by inspecting the type of the supplied pointer passed to its showPreview() entry points. There are two primary overloads: one accepting PhotoData* and another accepting DocumentData*. According to the source code at lines 24‑35, the implementation stores the file origin, clears any previously held media pointer, creates a media view, and requests thumbnails.

Once the preview is activated, currentImage() serves as the dispatch hub for rendering. This method branches based on runtime type checks—distinguishing between photos, static stickers, Lottie animations, and WebM/GIF content—to determine which cache or player to initialize.

Photos

When showPreview(origin, photo) is invoked, the widget creates a PhotoMedia view via photo->createMediaView() (lines 24‑35). The view attempts to load the full-size image; if unavailable, it falls back to a thumbnail or blurred placeholder. The resulting image is stored in the internal _cache member and reused while the preview remains visible (lines 14‑21).

Static Stickers

Documents representing static stickers are identified through document->sticker(). If the sticker is not a Lottie or WebM variant, the widget queries _documentMedia->getStickerLarge() to retrieve the high-resolution image (lines 59‑70). This pixmap is then scaled to the dimensions returned by currentDimensions() and cached for painting.

Lottie Animations

Animated stickers using the Lottie (JSON) format are detected via sticker->isLottie(). The method createLottieIfReady() verifies that the document is fully loaded before invoking setupLottie() (lines 99‑106). The latter instantiates a Lottie::SinglePlayer (lines 14‑28) to render frames on-the-fly. An optional premium effect player may be attached to this pipeline for enhanced visual output.

WebM and GIF Videos

For WebM stickers (detected by sticker->isWebm()) and GIF/MP4 animations (isAnimation()), the widget uses the GIF-style rendering path via Media::Clip::Reader. The validateGifAnimation() method (lines 51‑73) creates either a full-size reader (_gif) or a thumbnail reader (_gifThumbnail). Frames are decoded on demand through gif->current() and drawn as QPixmap objects. The widget respects the global pause reason Window::GifPauseReason::MediaPreview to manage resource usage.

Premium Sticker Effects

Premium stickers receive special handling through size modifiers defined as kPremiumMultiplier, kPremiumShift, and kPremiumDownscale (lines 66‑73). The content is downscaled before being passed to the Lottie player, then drawn at double size with an effect overlay applied during the paint cycle (lines 17‑32).

Animation Lifecycle and Rendering

The rendering pipeline follows a strict lifecycle to ensure smooth animation and memory efficiency.

Sizing: currentDimensions() computes the target size based on original media dimensions, custom padding, UI scaling, and the premium downscale factor (lines 40‑88).

Frame Generation: In currentImage() (lines 56‑124), the widget selects between:

  • Static sticker images from _documentMedia
  • Photo images from _photoMedia
  • Lottie frames from _lottie
  • GIF/WebM frames from the clip reader

Painting: The paintEvent() method (lines 79‑66) draws the background using st::stickerPreviewBg, then renders the content. For Lottie, it displays _lottie->frameInfo; for GIFs, it uses the current frame from the clip reader. Emoji associated with stickers are rendered after the media layer completes.

Working with the MediaPreviewWidget API

The following examples demonstrate how to instantiate and configure the widget for different media types.

Displaying a Document or Sticker

// controller: pointer to the current SessionController
// doc: non-null DocumentData* representing the hovered item

auto *preview = new Window::MediaPreviewWidget(parentWidget, controller);
preview->setCustomPadding(QMargins(5, 5, 5, 5));
preview->showPreview(Data::FileOrigin::FromUser, doc);
preview->show();

Key implementation references: showPreview(origin, doc) at lines 24‑35; setCustomPadding at lines 2‑4.

Displaying a Photo

auto *preview = new Window::MediaPreviewWidget(parentWidget, controller);
preview->setContentShift(10);  // shift preview down by 10 pixels
preview->showPreview(Data::FileOrigin::FromUser, photo);
preview->show();

Key implementation reference: showPreview(origin, photo) at lines 24‑33.

Customizing Animation and Appearance

auto *preview = new Window::MediaPreviewWidget(parentWidget, controller);
preview->setCustomDuration(crl::time(200));  // 200 ms fade duration
preview->setCustomRadius(8);                 // 8 px corner radius
preview->showPreview(Data::FileOrigin::FromUser, doc);
preview->show();

Key implementation references: setCustomDuration at lines 18‑20; setCustomRadius at lines 13‑15.

Key Source Files

File Role
[window/window_media_preview.h](https://github.com/telegramdesktop/tdesktop/blob/dev/Telegram/SourceFiles/window/window_media_preview.h) Public interface of MediaPreviewWidget
[window/window_media_preview.cpp](https://github.com/telegramdesktop/tdesktop/blob/dev/Telegram/SourceFiles/window/window_media_preview.cpp) Full implementation: preview lifecycle, sizing, media loading, Lottie/GIF handling, painting
data/data_document_media.h / .cpp Provides the DocumentMedia view used for thumbnails, stickers, and animation bytes
data/data_photo_media.h / .cpp Handles loading of photo images at various resolutions
lottie/lottie_single_player.h / .cpp Renders Lottie (JSON-based) animations for stickers
media/clip/media_clip_reader.h / .cpp Decodes GIF/WebM animation frames for the preview widget
[ui/painter.h](https://github.com/telegramdesktop/tdesktop/blob/dev/Telegram/SourceFiles/ui/painter.h) Used in paintEvent to draw backgrounds, images, and emojis

Summary

  • The MediaPreviewWidget distinguishes between photos and documents through overloaded showPreview() methods, then routes content through type-specific rendering branches.
  • Static stickers use getStickerLarge() from DocumentMedia, while Lottie animations require a SinglePlayer instance created via setupLottie().
  • WebM and GIF content share a unified pipeline using Media::Clip::Reader managed by validateGifAnimation(), with automatic thumbnail fallback.
  • Premium stickers apply scaling modifiers (kPremiumDownscale) and optional effect overlays during the paint cycle.
  • All visual content is cached in _cache or live-rendered through animation players, with dimensions computed by currentDimensions() to respect UI scaling and padding settings.

Frequently Asked Questions

How does the widget choose between photo and document rendering?

The widget exposes two showPreview() overloads: one accepting PhotoData* and another accepting DocumentData*. When called, the method stores the appropriate media pointer and clears the opposite type, then creates a corresponding media view (PhotoMedia or DocumentMedia) to handle subsequent rendering decisions at lines 24‑35.

What is the difference between Lottie and WebM sticker handling in the preview?

Lottie stickers trigger the creation of a Lottie::SinglePlayer through setupLottie() to render JSON animation frames procedurally. WebM stickers, conversely, are treated as video content and routed through validateGifAnimation() to create a Media::Clip::Reader that decodes compressed video frames, similar to GIF handling (lines 51‑73).

How does the widget handle unloaded or unsupported media types?

When media cannot be loaded—for example, if the document is incomplete or the type is unrecognized—the _cache member holds a blank pixmap. The currentImage() method returns this empty cache, causing paintEvent() to render only the background without content (lines 42‑44).

How are premium sticker effects implemented differently from standard stickers?

Premium stickers apply geometric transformations using kPremiumMultiplier and kPremiumShift constants. The widget first downscales the content (kPremiumDownscale) before passing it to the Lottie renderer, then draws the result at double size with an additional effect layer overlay, as implemented in the size calculation logic at lines 66‑73.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →