As of October 1, 2023, LINE has been rebranded as LY Corporation. Visit the new blog of LY Corporation here: LY Corporation Tech Blog

Blog


LINE Animation Sticker Checker on Web browser

Great to meet you all. This is ha1f from LINE Fukuoka, I develop iOS apps at LINE. Back when I was as a part timer at LINE, I was involved in making in-house tools for LINE. To share a little bit of my background, I was hired as a part timer with a confirmed offer to a full time position. Anyway, today, I'd like to show you a tool I developed during my time as a part timer, the Animation Sticker Checker.

What is APNG?

Before we get into the details of the tool, let's discover what APNG is. APNG (Animated Portable Network Graphics) is a specification for animated image, consisting of images each numbered with a sequence number. LINE animation stickers use APNG. Unlike GIF, APNG supports full color, alpha channel and has a high compression rate. APNG is compatible with PNG, and is displayed as a still image for image viewers that do not support APNG. If you have the right tool, you can convert a sequence of PNG files into an APNG file.

apng

Animation sticker checker

LINE animation stickers must satisfy the set criteria. If you are interested, check out the Creation Guidelines on the LINE Creators Market for more guide.

  • Resolution
    • Width: 320px or less
    • Height: 270px or less
    • Either the width or the height shall be greater than 270px
  • Playback time: Maximum four seconds per sticker
  • Number of frames: 5–20 frames
  • Color mode: RGB
  • File size: 300KB or less per file

The Animation Sticker Checker* is a supplementary tool for you to check if your sticker satisfies the criteria, and is for internal use only. Here, you can see a glimpse of the checker. I've hidden the actual images of the sticker for this post. The real checker would be showing the actual images composing a sticker.

ASC

Simply drag the folder containing the animation sticker into the checker, then the images will be loaded recursively, and the image information–resolution, size, color mode–will be displayed on the screen for you. If there is something wrong with the sticker, an error message will be displayed. To support both Mac and Windows, I've chosen to implement the tool as an SPA (Single Page Application) to run on web browsers.

Playing APNG on web browsers

To make APNG-compatible web browsers to automatically play your APNG file, simply specify the src attribute of an <img> tag. Previously, only Firefox supported APNG, but Safari joined in 2014, and Chrome has also started supporting APNG from June 2017. To play an APNG on web browsers that do not support APNG, you can use a library like davidmz/apng-canvas which reads an image's binary, analyzes it and loads a frame each in a canvas.

Getting the basic information of APNG files

To determine whether an animation sticker has been made according to the criteria, we first need to get the information of the images in the sticker. Let's start with image resolution. Image resolution can be obtained with the following code, which is a common way used in obtaining image resolution. However, file's size or its color type cannot be. We need to write additional code to acquire these.

var img = $("<img />", {
    alt: foldername + "/" + filename
});
img.bind('load', function() {
    const dom = img.get(0);
    const width = dom.naturalWidth;
    const height = dom.naturalHeight;
}
img.attr({ src: e.target.result });
 

Getting more information with File API

So, how can we obtain information other than the resolution of an APNG file? We will use the File API, one of the useful Web APIs, which enables you to load local files onto a web browser, using JavaScript. In other words, we can see and analyze local images on our web browsers, without uploading them on a server. Let's look into the details.

apng-canvas uses XMLHttpRequest.

FileReader, File, FileList

As long as you have an <input type="file"> element in your HTML code, you can use the File API. As shown below, if you add the directory attributes in the element, you will be able to select file directories with your web app. Note that this hasn't become a standard yet, so it works on only a few web browsers, like Chrome.

<input type="file" webkitdirectory directory>

Once a user selects a directory containing the image to check, a FileList object is passed to the event handler. We run a loop, retrieving a File object from the list and analyzing the object using the FileReader interface, at each turn. The File object inherits from the Blob object, enabling us to retrieve file name, file size and MIME type. With the information retrieved, we can narrow down the scope for a web browser to process. Look at the code below, the code obtains the file size of each image in the list.

handleDirectorySelected(e) {
    Array.from(e.target.files).forEach((file, index, array) => {
        const fileSize = file.size;
        // If required, narrow down the scope of elements to load,
        // using information such as file name or MIME type.
        const reader = new FileReader();
        reader.onload = (e) => { /* some process */  }
        reader.readAsArrayBuffer(file);
    });
}

Reading a file with ArrayBuffer & DataView

You can specify the type of an image file read by the FileReader interface as to text or Data URI. However, our checker uses the ArrayBuffer type, convenient for binary analysis. ArrayBuffer is used to contain binary with fixed size, and you cannot directly access or manipulate the data in it. So that's why DataView comes into the picture; we read the data in the buffer with DataView and do something about it.

const fileReader = new FileReader();
fileReader.onload = function(e) {
    const arrayBuffer = e.target.result;
    const dataView = new DataView(arrayBuffer, 0);
    // Read the file with DataView
}
fileReader.readAsDataURL(file);

Displaying the image in ArrayBuffer

To display sticker images on our checker, we need to display on a web browser the image binary contained in the ArrayBuffer. We can do so by converting the binary into a Data URI in a common way as shown below.

img.attr({ src: `data:image/png;base64,${btoa(Array.from(new Uint8Array(this.arrayBuffer), e => String.fromCharCode(e)).join(''))}` });

Using the structures of PNG & APNG for reading image information

PNG is specified by ISO/IEC 15948:2003, and you can access the specification from Portable Network Graphics(PNG) Specification(Second Edition). A PNG binary starts with a 8-byte long string, 89 50 4E 47 0D 0A 1A 0A, followed by data blocks called chunks. There are many types of chunks to contain certain type of data. For example, the IHDR chunk contains the image type and color type, and the IDAT chunk contains the actual image data.

Chunk Name Chunk size (Bytes)
PNG signature 8
IHDR chunk 25
... ...
IDAT chunk Dynamic
... ...
IEND chunk 12

Each chunk is structured as defined below. If the value of the "chunk size" field is 0, then the "chunk data" field will not be present.

Chunk field Chunk field size (Bytes)
Chunk size 4
Chunk type 4
Chunk data Identical to the value of the "Chunk size" field.
CRC 4

The following code will sequentially read the chunks of an image file, leaving you an array of chunks. The value of "chunk type" field—mentioned in the table above—is defined only with the ISO 646 characters, as specified in Chunk layout, and is saved as a string. For your information, the readChunk() method called reads a given chunk from the given position.

getChunks() {
    const chunks = [];
    let chunk = { size: 0, type: '', crc: 0, dataOffset: 0x00, endOffset: 0x00 };
    while (chunk.type !== 'IEND') {
        chunk = this.readChunk(chunk.endOffset);
        chunks.push(chunk);
    }
    return chunks;
}

Finding the color type with IHDR chunk

Once you acquire an array of chunks, you can start off getting information you need by analyzing each chunk. Let's see the IHDR chunk as an example to get an image's color type. As you can see from the structure of the IHDR chunk below, you can get the image's color mode using the color type field.

Chunk field Chunk field size (Bytes)
Width 4
Height 4
Bit depth 1
Color type 1
Compression method 1
Filter method 1
Interlace method 1

Let's grab the IHDR chunk from the chunk array we obtained from the previous step.

const ihdrChunk = chunks.find(function(chunk) {
    return chunk.type === 'IHDR'
})

Now, we read in the chunk information. The information read by the following code is the fields of the IHDR chunk we just looked at. To see more information on the specification of the color type, please check Table 11.1 of the PNG specification. With the code below, we can check whether the color mode of a sticker image is RGB or not.

const offset = ihdrChunk.dataOffset;
return {
    width: dataView.getUint32(offset),
    height: dataView.getUint32(offset + 4),
    bitDepth: dataView.getUint8(offset + 8),
    colorType: dataView.getUint8(offset + 9),
    compression: dataView.getUint8(offset + 10),
    filter: dataView.getUint8(offset + 11),
    interlace: dataView.getUint8(offset + 12),
    crc: dataView.getUint32(offset + 13),
};

APNG Extension

APNG is an extension of PNG, with additional chunks like 'fcTL', 'fdAT', and 'acTL'. There is only a single 'acTL' chunk, and it contains information such as the number of frames and the number of times to play the animation. There can be multiple 'fcTL' chunks and 'fdAT' chunks for information such as time to display a frame, associated image data and display location. I am not going to go through the details of it, but will leave it to you to read the documentation on Animated PNG graphics by Mozilla.

Playing images with sequence number

Some of you may know that we can use the <canvas> element to play a series of PNG images with a sequence number. However, our checker simply uses the <img> element instead, and plays the animation by moving the position of the element. By the way, as you can see from the following animation, colored background is provided for each image for users to easily check the transparency of images.

ASC
We've deliberately hidden the actual images just for this post.

Closing notes

I mainly use Swift when I code, so I find it easier to make tools for Mac. A nice option for creating cross-platform tools can be Electron, but if you make a web-based tool then you can make it run not only on Mac or Windows but on iOS and Android as well. Also, if you need to update your tool due to various reasons such as fixing bugs, you don't have to ask your users to download and install the update. Instead, you simply update the files on a server, and you are done.

With HTML5, there are so many features you can make to run on web browsers. Hope you could take the advantage of those awesome features for your projects too.


* In Japan, stickers are called stamps. The original name for the tool introduced on this post is, Animation Stamp Checker.