RichEdit Animated GIFs

The post RichEdit 8.0 Image Support describes how RichEdit supports popular image formats, such as jpeg’s, png’s and GIF’s. RichEdit 8.1 added direct support for jpeg’s and png’s in the Rich Text Format (RTF) instead of using RichEdit’s proprietary blob format. Even so, GIFs were treated as second-class images in two ways. First, they were converted to png’s when persisted in RTF. Since png’s are limited to a single frame, only the initial frame of a multiframe GIF was saved. Second, only the initial frame was displayed, so the animated GIFs that are seen frequently on the web and in texting programs weren’t animated. This post describes how the current RichEdit image facility fixes both problems.

Persisting multiframe GIFs in RTF

The RTF specification doesn’t have a control word for GIF images, although it has \jpegblib for jpeg’s and \pngblip for png’s. One way to persist GIFs would be to define a \gifblip. But this approach has the disadvantages that RTF readers that don’t recognize \gifblip would either discard the image altogether or fall back to an alternate format if one is included in the RTF stream. RichEdit’s earlier choice of converting the GIF to a png saves only the initial frame. A simple observation reveals how to solve both problems: the RTF reader isn’t, and furthermore shouldn’t be, responsible for the image format. Instead the program that interprets the image binary data should recognize the image format and validate the data. This is essential for security reasons. Accordingly, RichEdit just uses the \pngblip control word for a GIF even though the binary data defines a GIF! This interoperates with Word, older RichEdit clients, etc. Human readers of RTF can know the difference if they want to by looking at the image data, which starts with “PNG” for a real png and “GIF” for a GIF. The decoding of the images on Microsoft and Android operating systems is performed by the Windows Imaging Component (WIC), which also validates the images. Currently the RichEdit image facility isn’t supported on Apple platforms, but that may change.

When reading a GIF from RTF or via one of the APIs, RichEdit calls WIC to convert the image to a WIC bitmap. RichEdit saves memory space by not storing the original image data, since that data can be faithfully recovered from the WIC bitmap including all metadata.

Animated GIFs vary appreciably in size, ranging from 50 kilobytes for simple cartoon animation like

to several megabytes, such as this 1-megabyte GIF from the movie Singin’ in the Rain

When exported in RTF files, the size doubles since each binary byte is represented by two hexadecimal bytes. So, it’s wise not to insert animated GIFs excessively.

Multiframe GIFs

The GIF format is described very well on the web, but here are a few quick thoughts before explaining how GIF animation works in RichEdit. First, GIF metadata is essential for animation. It controls the frame rate on a per-frame basis, the color palette(s) and how the current full image is used in composing the next image. In fact, several partial frames along with the current full image may be combined to create the next displayed frame. Such intermediate frames have “0-delay”, that is, they are included in the composition without being displayed. Some programs display them anyway (see the Windows photo viewer and the animated GIF sample), but I, at least ☺, think that’s misunderstanding the purpose of 0-delay intermediate frames. In fact as discussed further below, some multiframe GIFs use the extra frames purely for refining the image colors, since a single frame is limited to 8-bit color and one color palette.

RichEdit implementation

Each animated GIF gets its own timer in this model since use of a shared timer ends up slowing down the animations. The time delays between frames may be the same or they can vary substantially, such as in the GIF

Animation is enabled/disabled via calling ITextDocument2::SetProperty(tomAnimateImages, Value), where tomAnimateImages is given by 0x95 and Value = tomTrue/tomFalse, respectively. A client that enables animation guarantees that the ID2D1RenderTarget supplied in the call to ITextServices2::TxDrawD2D() remain valid between calls.  By default, animations are disabled since previous clients don’t know about this requirement.

Animations only work in Direct2D/DirectWrite mode. Initial attempts to get them running in GDI/Uniscribe mode display the animated images correctly except with a black background. This could be fixed if there is a need for animations in GDI mode.

Animations are disabled for selected images. This allows the user to recognize when images are selected. They are also disabled when the RichEdit control loses the focus. If a GIF has a maximum loop count, the animation stops after reaching that count.

Getting images from RichEdit

Clients can get an image IStream by calling ITextRange::GetEmbeddedObject() to get the image’s IUnknown interface, then calling IUnknown::QueryInterface for an IServiceProvider interface and finally calling IServiceProvider::QueryService() to get the image IStream. This works for all kinds of images, not just GIFs.

Stationary multiframe GIFs

GIFs with frames that all have zero frame delays display only the composite final frame. The multiple frames are typically used to get a composite image that has higher-resolution color than possible with a single 8-bit color palette. The Windows photo viewer nevertheless tries to animate such GIFs so the user sees fluctuating colors. PowerPoint presentation mode displays the correct composite image after one animation sequence, but Word only shows the first frame. See High-Color GIF Images for a detailed discussion of the following stationary multiframe GIF with high-resolution color.