After the
post about Apple's approach to encoding video to JPEG, I decided to talk about my similar “bike” under Android.
In our mobile project, we decided to make thumbnails of weapons not videos, but videos. It was meant that the artists would draw beautiful animations, maybe even in 3D, but something didn’t work out and we were given the simplest looped 1-1.5 second videos in the resolution of 256x256. In the iOS version, they built in remarkably, but in Android I had to make war with the MediaPlayer and SurfaceView, but all the same, there were some “clumsiness” - the contents of SurfaceView did not move after the parent View, there was a noticeable pause during playback, etc.
A reasonable solution would be to break the animation into frames and arrange it in xml for AnimationDrawable, but for 15 types of weapons this would mean a trashcan of 5000+ frames of 10-15 kb each. Therefore, its own implementation of AnimationDrawable, working with the sprite sheet and a relatively fast method of converting video into such a format, was made.
Sprite sheet
Map sprites without further ado decided to do horizontal, without a description file. This is not ideal practical, but for 1-2 second animations is not critical.
The original video is divided into sprites via ffmpeg:
')
ffmpeg -i gun.avi -f image2 gun-%02d.png
If more than 32 frames are obtained, then the -r 25 or -r 20 parameter is added to reduce fps. The limit of 32 frames is taken from the maximum reasonable image size horizontally at 8192 pixels. This can be bypassed by a more complex arrangement of sprites, but for a sequence of 1-1.5 seconds, this is enough.
It turns out such a scattering of files:

I use
Zwoptex to build a sprite sheet, but any similar tool, or even a self-written script, will do.

In the example with the gun turned out png file size 257kb and a resolution of 8192x256. After trimming to 7424x256 and processing through the
TinyPNG site
, it was reduced to 101kb without loss of quality. If desired, you can also save it in JPG with a small loss of quality and reduce to 50-70kb. For comparison, the original video in .MP4 with high quality takes the same 100kb. For more complex animations, PNGs can turn out to be 2-3 times larger than the original video, which is actually not critical either.

Own AnimationDrawable
In the original version of the bet was made that
Bitmap.createBitmap does not create a new image, but a subset of the existing one in accordance with the description:
Returns an immutable bitmap from the source bitmap.
The designer loads the image, splits it into frames and adds them to the AnimationDrawable. In our case, animations are stored in assets to gain access by name, but the code is very easily adapted to work with R.drawable. *
public class AssetAnimationDrawable extends AnimationDrawable { public AssetAnimationDrawable(Context context, String asset, int frames, int fps) throws IOException { BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Config.RGB_565;
The class is used, as usual AnimationDrawable:
AnimationDrawable animation = new AssetAnimationDrawable(getContext(), "movies/gun.jpg", 28, 25); animation.setOneShot(false); previewImage.setImageDrawable(animation); animation.start();
Unfortunately, experiments have shown that it is not a immutable link to the original that is created, but a new image for each frame, because this solution turned out to be quite resource intensive, although it works fine for many situations.
The next option is already much more complicated and is inherited directly from Drawable. In the constructor, the sprite sheet is loaded into a member of the class, and in the draw method, the current frame is drawn. The class also implements the Runnable interface by analogy with the original AnimationDrawable for animation.
@Override public void draw(Canvas canvas) { canvas.drawBitmap(m_bitmap, m_frameRect, copyBounds(), m_bitmapPaint); } @Override public void run() { long tick = SystemClock.uptimeMillis(); if (tick - m_lastUpdate >= m_duration) { m_frame = (int) (m_frame + (tick - m_lastUpdate) / m_duration) % m_frames; m_lastUpdate = tick;
In the run () method, the current frame is calculated and the task is queued. The accuracy of the above code will not be perfect, because the fractional frame time is not taken into account (for example, when tick - m_lastUpdate is 1ms less than the duration), but in our problem it was not relevant, and those who want can modify the class on their own.
Full paste2 code:
paste2.org/p/2240487I want to draw attention to the recycle () method, which clears m_bitmap. In most cases, it is not needed, but we can quickly click through purchases in the store, because of which several AssetAnimationDrawables are created and the memory may run out, so when creating a new animation, we clear the old resources.
Advantages and disadvantages
Of course, the approach is far from ideal and is not suitable for large animations or significantly different frames, but for our task it came up perfectly, without a noticeable increase in the project and visual bugs.
Minuses:
- inheriting from Drawable we lose some AnimationDrawable features like setOneShot
- image 8192x256x32bpp takes up 8MB of memory
- it is necessary to store somewhere the number of frames and fps for each animation
- own code for standard solutions worsens the readability of the program and complicates its support
- compressing the sprites with jpg we get the worst quality, at the same size as the original video. Squeezing in png we get the same quality, at 1-3 times the size
Pros:
- no bugs with SurfaceView, MediaPlayer, braking when loading
- in the RGB_565 mode, the picture 8192x256 takes 4MB of memory, and if needed, you can put options.inSampleSize = 2 or more in the constructor to reduce the size and memory (at a value of 2, 0.5MB of memory and a resolution of 4096x128 are obtained)
- You can scale the sprite sheet in your favorite editor to any size. the main rule is that the width remains a multiple of frames
- You can adjust the playback speed through fps without any problems without changing the finished files
- it is quite possible to play the animation with transparency in the modes ARGB_8888 or ARGB_4444
- at any time you can stop the animation and free up resources
PS
If it will be interesting to someone, I can separately tell you about the experience of integrating small videos in the GUI into MonoTouch for an iOS project. Documentation on Mono is relatively small, and there are enough pitfalls there.