You can see the rendered result here: https://www.dropbox.com/s/81muv6rri8delox/btn-anime.webm
Last active
August 29, 2015 14:04
-
-
Save cjxgm/05d71ad8f69ec91fabb6 to your computer and use it in GitHub Desktop.
a simple animation renderer written in c++
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <array> | |
#include <vector> | |
#include <string> | |
#include <sstream> | |
#include <functional> | |
#include <cmath> | |
using namespace std; | |
namespace pro | |
{ | |
struct ColorARGB | |
{ | |
using Type = ColorARGB; | |
using Component = float; | |
using Array = array<Component, 4>; | |
using Raw = unsigned int; // 0xAARRGGBB | |
using Index = int; | |
ColorARGB() : ColorARGB{0xFFFFFFFF} | |
{ | |
} | |
ColorARGB(Raw color) | |
: ColorARGB{ | |
((color >> 24) & 0xFF) / 255.0f, | |
((color >> 16) & 0xFF) / 255.0f, | |
((color >> 8) & 0xFF) / 255.0f, | |
((color >> 0) & 0xFF) / 255.0f} | |
{ | |
clamp(); | |
} | |
ColorARGB(Component a, Component r, Component g, Component b) | |
: array{{a, r, g, b}} | |
{ | |
} | |
const Component& operator[](Index idx) const { return array[idx]; } | |
Component& operator[](Index idx) { return array[idx]; } | |
operator Raw() { return raw(); } | |
Raw raw() const | |
{ | |
return (raw(0)<<24) | (raw(1)<<16) | (raw(2)<<8) | raw(3); | |
} | |
Raw raw(Index idx) const | |
{ | |
int c = clamp(idx) * 0xFF; | |
if (c < 0x00) c = 0x00; | |
if (c > 0xFF) c = 0xFF; | |
return static_cast<Raw>(c); | |
} | |
Component clamp(Index idx) const | |
{ | |
auto c = array[idx]; | |
if (c < 0.0f) c = 0.0f; | |
if (c > 1.0f) c = 1.0f; | |
return c; | |
} | |
void clamp() | |
{ | |
array[0] = clamp(0); | |
array[1] = clamp(1); | |
array[2] = clamp(2); | |
array[3] = clamp(3); | |
} | |
void value(float v) | |
{ | |
array[1] *= v; | |
array[2] *= v; | |
array[3] *= v; | |
} | |
void blend(const Type& color) | |
{ | |
blend(color, color[0]); | |
} | |
void blend(const Type& color, float a) | |
{ | |
auto c = 1.0f - a; | |
array[0] = array[0]*c + color[0]*a; | |
array[1] = array[1]*c + color[1]*a; | |
array[2] = array[2]*c + color[2]*a; | |
array[3] = array[3]*c + color[3]*a; | |
} | |
void alpha(float v) | |
{ | |
array[0] *= v; | |
} | |
private: | |
Array array; | |
}; | |
struct Pixmap | |
{ | |
using Color = ColorARGB; | |
using Array = vector<Color>; | |
using Coord = int; | |
using Index = Coord; | |
using Size = Coord; | |
using SubCoord = float; // subpixel coordinate | |
template <class Iterable> | |
Pixmap(const Iterable& iterable, Size w) : | |
Pixmap(begin(iterable), end(iterable), w) | |
{ | |
} | |
template <class IterIn> | |
Pixmap(IterIn begin, IterIn end, Size w) : | |
array{begin, end}, | |
_w{w}, | |
_h{static_cast<Size>(array.size())/w} | |
{ | |
struct bad_size {}; | |
if (array.size() % w) throw bad_size{}; | |
} | |
Pixmap(Size w, Size h) : | |
array(w*h), | |
_w{w}, | |
_h{h} | |
{ | |
} | |
Size w() const { return _w; } | |
Size h() const { return _h; } | |
Index index(Coord x, Coord y) const | |
{ | |
if (x < 0) x = x%w() + w(); | |
if (y < 0) y = y%h() + h(); | |
if (x >= w()) x %= w(); | |
if (y >= h()) y %= h(); | |
return y*w() + x; | |
} | |
const Color& at(Index idx) const { return array[idx]; } | |
Color& at(Index idx) { return array[idx]; } | |
const Color& at(int x, int y) const { return at(index(x, y)); } | |
Color& at(int x, int y) { return at(index(x, y)); } | |
operator string() const { return ppm(); } | |
using Transformer = function<void(Color&)>; | |
void transform(Transformer f) | |
{ | |
for (auto& color: array) | |
f(color); | |
} | |
void alpha(float a) | |
{ | |
transform([a](Color& color) { color.alpha(a); }); | |
} | |
void ppm(ostream& o) const | |
{ | |
o << dec; | |
o << "P3" << endl; | |
o << w() << " " << h() << " 255" << endl; | |
for (Coord y=0; y<h(); y++) { | |
for (Coord x=0; x<w(); x++) { | |
auto c = at(x, y); | |
//c.value(c[0]); | |
o << c.raw(1) << " " << c.raw(2) << " " << c.raw(3); | |
if (x != w()-1) o << " "; | |
} | |
o << endl; | |
} | |
} | |
string ppm() const | |
{ | |
stringstream ss; | |
ppm(ss); | |
return ss.str(); | |
} | |
Color xget(SubCoord sx, Coord y) const | |
{ | |
Coord x = sx; | |
auto color = at(x, y); | |
color.blend(at(x+1, y), sx-x); | |
return color; | |
} | |
Color yget(Coord x, SubCoord sy) const | |
{ | |
Coord y = sy; | |
auto color = at(x, y); | |
color.blend(at(x, y+1), sy-y); | |
return color; | |
} | |
Pixmap xscale(Size w) const | |
{ | |
Pixmap result{w, h()}; | |
for (Coord y=0; y<result.h(); y++) | |
for (Coord x=0; x<result.w(); x++) | |
result.at(x, y) = xget( | |
lirp( | |
x, | |
0, result.w()-1, | |
0, this->w()-1), | |
y); | |
return result; | |
} | |
Pixmap yscale(Size h) const | |
{ | |
Pixmap result{w(), h}; | |
for (Coord y=0; y<result.h(); y++) | |
for (Coord x=0; x<result.w(); x++) | |
result.at(x, y) = yget( | |
x, | |
lirp( | |
y, | |
0, result.h()-1, | |
0, this->h()-1)); | |
return result; | |
} | |
Pixmap scale(Size w, Size h) const | |
{ | |
return xscale(w).yscale(h); | |
} | |
Pixmap crop(Coord x, Coord y, Size w, Size h) const | |
{ | |
Pixmap result{w, h}; | |
result.blit(0, 0, *this, x, y, w, h); | |
return result; | |
} | |
void blit(Coord x, Coord y, const Pixmap& src) | |
{ | |
blit(x, y, src, 0, 0, src.w(), src.h()); | |
} | |
void blit(Coord x, Coord y, const Pixmap& src, | |
Coord sx, Coord sy, Size sw, Size sh) | |
{ | |
for (Coord iy=sy, ry=y; iy<sy+sh; iy++, ry++) | |
for (Coord ix=sx, rx=x; ix<sx+sw; ix++, rx++) | |
at(rx, ry).blend(src.at(ix, iy)); | |
} | |
Pixmap border_scale(Size w, Size h, | |
Size nsize, Size ssize, Size wsize, Size esize) | |
{ | |
Pixmap result{w, h}; | |
// corners | |
result.blit( 0, 0, *this, 0, 0, wsize, nsize); // nw | |
result.blit(w-esize, 0, *this, this->w()-esize, 0, esize, nsize); // ne | |
result.blit( 0, h-ssize, *this, 0, this->h()-ssize, wsize, ssize); // sw | |
result.blit(w-esize, h-ssize, *this, this->w()-esize, this->h()-ssize, esize, ssize); // se | |
// edges | |
result.blit( wsize, 0, crop( wsize, 0, this->w()-wsize-esize, nsize).xscale(w-wsize-esize)); // n | |
result.blit( 0, nsize, crop( 0, nsize, wsize, this->h()-nsize-ssize).yscale(h-nsize-ssize)); // w | |
result.blit( wsize, h-ssize, crop( wsize, this->h()-ssize, this->w()-wsize-esize, ssize).xscale(w-wsize-esize)); // s | |
result.blit(w-esize, nsize, crop(this->w()-esize, nsize, esize, this->h()-nsize-ssize).yscale(h-nsize-ssize)); // e | |
// center | |
result.blit(wsize, nsize, crop(wsize, nsize, this->w()-wsize-esize, this->h()-nsize-ssize).scale(w-wsize-esize, h-nsize-ssize)); | |
return result; | |
} | |
static float lirp(float s, float sf, float st, float df, float dt) | |
{ | |
return (s-sf) / (st-sf) * (dt-df) + df; | |
} | |
private: | |
Array array; | |
const Size _w; | |
const Size _h; | |
}; | |
}; | |
using namespace pro; | |
int main(int argc, char* argv[]) | |
{ | |
if (argc != 2) return 1; | |
int frame = stoi(string{argv[1]}); | |
using RawColor = ColorARGB::Raw; | |
RawColor checker_raw[] = { | |
0xFF888888, 0xFFAAAAAA, 0xFF888888, | |
0xFFAAAAAA, 0xFFFFFFFF, 0xFFAAAAAA, | |
0xFF888888, 0xFFAAAAAA, 0xFF888888, | |
}; | |
Pixmap checker{checker_raw, 3}; | |
checker.at(2, 2)[3] += sin(frame/10.3f) * 0.47; | |
checker.at(1, 0)[2] += sin(frame/9.1f+1.1f) * 0.31; | |
if (frame < 30) { | |
float x = Pixmap::lirp(frame, 0, 29, 0, 10); | |
float y = Pixmap::lirp(frame, 0, 29, 30, 10); | |
float w = Pixmap::lirp(frame, 0, 29, 20, 580); | |
float h = Pixmap::lirp(frame, 0, 29, 100, 580); | |
float a = Pixmap::lirp(frame, 0, 19, 0, 1); | |
if (a > 1) a = 1; | |
Pixmap result{600, 600}; | |
auto m = checker.scale(30, 30).border_scale(w, h, 10, 10, 10, 10); | |
m.alpha(a); | |
result.blit(x, y, m); | |
cout << string(result) << endl; | |
return 0; | |
} | |
if (frame < 60) { | |
float f = Pixmap::lirp(frame, 30, 59, 30, 300); | |
Pixmap result{600, 600}; | |
result.blit(10, 10, | |
checker | |
.scale(f, f) | |
.border_scale(580, 580, 10, 10, 10, 10)); | |
cout << string(result) << endl; | |
return 0; | |
} | |
if (frame < 90) { | |
float f = Pixmap::lirp(frame, 60, 89, 0, 140); | |
Pixmap result{600, 600}; | |
result.blit(10, 10, | |
checker | |
.scale(300, 300) | |
.border_scale(580, 580, f, f, f, f)); | |
cout << string(result) << endl; | |
return 0; | |
} | |
if (frame < 120) { | |
float f = Pixmap::lirp(frame, 90, 119, 140, 0); | |
float a = Pixmap::lirp(frame, 90, 119, 1, 0); | |
Pixmap result{600, 600}; | |
auto m = checker.scale(300, 300).border_scale(580, 580, f, f, f, f); | |
m.alpha(a); | |
result.blit(10, 10, m); | |
cout << string(result) << endl; | |
return 0; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/fish | |
set -l nframe 119 | |
set -l output "./rendered" | |
set -l renderer "./btn-anime" | |
mkdir -p $output | |
for frame in (seq 0 $nframe) | |
set -l index (printf "%.4d" $frame) | |
set -l file "$output/$index.png" | |
set -l cmd "$renderer $frame | convert - $file &" | |
echo -e "\e[0;32mrendering \e[1m$index\e[0m..." | |
eval $cmd | |
end | |
ffmpeg -i "$output/%04d.png" -b:v 1M $output.webm -y | |
rm -rf $output |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment