The article was first published on a personal blog: http://heavenru.com
In the recent project, we need to implement a sprite animation. The material side only provides a short video material. So before we implement the sprite animation, we first introduce two tools to help us better meet the requirements. In this article, two command-line tools are introduced to convert a short video file into a sprite image and how to use canvas to draw wizard animation.
The official addresses of the two tools are as follows:
1. ffmpeg Video Transfer Tool
ffmpeg is "a complete cross-platform solution for recording, converting and streaming audio and video transmission tools." Its role is not limited to what is described in this article. Interested students can go to the official website to learn more by themselves.
Converting Video to Picture Output
Basic Usage
./ffmpeg -i jellyfish.mp4 -vf scale=138:-1 -r 8 %04d.png
- i Video Stream Input URL
- vf creates filters specified by filters and uses them to filter streams, which are the descriptions of filters to be applied to streams, and must have a single input and a single output of the same type of stream. The corresponding filter parameters must follow this or they will not work.
Scale video scaling, scale=width:height where, if height=-1, the adaptive height, according to the video output aspect ratio, followed by the scale=width:height,setar=16:9 can specify the output aspect ratio.
- R video output fps value, the greater the value, the higher the fps slice video, alias-framerate, for example, we want to use-r 60 to clip the video to export pictures.
- Aspect video output aspect ratio, such as 4:3 and 16:9, are standard parameter usage.
- The start position of SS clipping is a very useful parameter, which means that the video is clipped at a certain time. The parameter is placed in front of - i, and the parameter format hh:mm:ss is time-seconds.
- t duration, which means the length of video that needs to be clipped, can be used with-ss to clip the content of any video time period. For example, we need to clip 5-10 seconds of video export. We can use ffmgeg-ss 00:00:05-t 00:10 in this way.
- V frames sets the number of output video frames, which is an alias for - frames:v
- qscale:v 2 specifies the quality of the output picture. The range of values is 2-31. The larger the value, the worse the quality. It is recommended that the value be 2-5.
Comprehensive application:
// Intercept a picture at 60 seconds ffmpeg -ss 60 -i input.mp4 -qscale:v 2 -vframes 1 output.jpg // Export all pictures at 60 FPS speed ffmpeg -i input.mp4 -r 60 %04d.png
2. Merge multiple pictures into one picture montage
With the tools described above, we can easily convert a video into a series of image files, and then we can use them. montage Tool combines n previously exported pictures into one picture
Basic usage:
montage -border 0 -geometry 138x -tile 89x -quality 100% *.png myvideo.jpg
- tile represents the number of rows that need to be merged, and when that number is exceeded, line breaks are merged.
- Quality represents the quality of composite images, ranging from 0% to 100%.
3. Drawing canvas Elf Animation
Before we start editing the code, let's sort out the requirements:
Animation needs to be able to play in circles
Animation needs to be able to specify a frame to start rendering
Specifies how many frames of animation to render
Animation needs to be able to control the rendering frame rate
When the wizard image is not a single line, it should be able to achieve automatic Line-Changing rendering.
OK, after understanding our needs, we started to write code. Let's start with a suggested parameter merging tool approach
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { // Traversing the attributes of the incoming object if (Object.prototype.hasOwnProperty.call(source, key)) { // Only the attributes and methods on the instance are manipulated to avoid circular prototypes target[key] = source[key]; } } } return target; }
Next is our canvas wizard object
function Sprite(canvas, opts) { var defaults = { loop: false, // Whether to Play Loop or not frameIndex: 0, // What frame is the current frame? startFrameIndex: 0, // Actually rendering location tickCount: 0, // Counters in each time period ticksPerFrame: 1, // The number of frames per rendering period, which controls the rendering speed of the animation numberOfFrames: 1, // Number of total animation frames numberOfPerLine: undefined, // Number of frames per action width: 0, // Canvas width height: 0, // Screen Height sprite: undefined // image object }; var params = opts || {}; this.canvas = canvas; this.ctx = canvas.getContext('2d'); this.options = _extends({}, defaults, params); if (this.image) throw new Error('Please pass in the picture object'); // The reason for taking Math.min() here is that under safari, if the size of the picture exceeds the size of the canvas, no image will be rendered. // So here we go to the little ones in the canvas and pictures. this.options.width = Math.min(this.canvas.width, this.options.sprite.width); this.options.height = Math.min(this.canvas.height, this.options.sprite.height); if (!this.options.numberOfPerLine) { this.options.numberOfPerLine = this.options.numberOfFrames || 9999; } } Sprite.prototype.render = function () { this.ctx.clearRect(0, 0, this.options.width, this.options.height); // The core drawing code mainly uses canvas.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight) API. // This. options. frameIndex%. This. options. numberOfPerLine calculates the remainder each time to determine whether to change lines or not. // Math.floor(this.options.frameIndex / this.options.numberOfPerLine) this.ctx.drawImage(this.options.sprite, this.options.width * (this.options.frameIndex % this.options.numberOfPerLine), this.options.height * Math.floor(this.options.frameIndex / this.options.numberOfPerLine), this.options.width, this.options.height, 0, 0, this.options.width, this.options.height); } Sprite.prototype.update = function () { this.options.tickCount++; // The core part of frame rate control is to determine whether the current counter is larger than the value we pass in at each drawing time point. if (this.options.tickCount > this.options.ticksPerFrame) { this.options.tickCount = 0; // Animation Cycle Judgment if (this.options.frameIndex < this.options.numberOfFrames - 1) { this.options.frameIndex++; } else if (this.options.loop) { // Each loop starts with a given startFrameIndex this.options.frameIndex = this.options.startFrameIndex; } } }
Now that our Wizard class is basically complete, let's look at how it can be used in business code.
var spriteCanvas = document.getElementById('spriteCanvas'); spriteCanvas.width = 138; spriteCanvas.height = 308; var isSpriteLoaded = false; var spriteImage = new Image(); var sprite; // Here is a BUG under IE, if our sprite is not loaded in the image, it will be executed. // Then a DOM Exception will be thrown under IE // So we put Sprite initialization in the image.onlaod callback function to execute sprite.onload = function () { sprite = new Sprite(spriteCanvas, { sprite: spriteImage, loop: true, numberOfFrames: 92, ticksPerFrame: 3 }); spriteAnimate(); } sprite.src = 'xxxxx/sprite.jpg'; function spriteAnimate() { requestAnimationFrame(spriteAnimate); sprite.render(); sprite.update(); }
The article is basically completed here. If you want to see the specific effect, you can check it here.
Portal: Jellyfish Animation, Hummingbird animation
Reference material
https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/drawImage
http://www.williammalone.com/articles/create-html5-canvas-javascript-sprite-animation/