screen: Add svg shapes pre-computation [O. Galibert]

This commit is contained in:
Olivier Galibert 2016-04-27 15:28:37 +02:00
parent fe6b9390c7
commit 7d635eec4b

View File

@ -15,7 +15,7 @@
#include <nanosvg/src/nanosvg.h>
#include <nanosvg/src/nanosvgrast.h>
#include <set>
//**************************************************************************
// DEBUGGING
@ -50,12 +50,43 @@ public:
static void output_notifier(const char *outname, INT32 value, void *param);
private:
struct paired_entry {
int key;
int cache_entry;
paired_entry(int k, int c) { key = k; cache_entry = c; }
};
struct cached_bitmap {
int x, y, sx, sy;
std::vector<UINT32> image;
std::vector<paired_entry> pairs;
};
struct bbox {
int x0, y0, x1, y1;
};
NSVGimage *m_image;
NSVGrasterizer *m_rasterizer;
std::vector<bool> m_key_state;
std::vector<std::list<NSVGshape *>> m_keyed_shapes;
std::unordered_map<std::string, int> m_key_ids;
int m_key_count;
std::unordered_map<std::string, std::list<NSVGshape *>> m_keyed_shapes;
int m_sx, m_sy;
double m_scale;
std::vector<UINT32> m_background;
std::vector<cached_bitmap> m_cache;
void output_change(const char *outname, INT32 value);
void render_state(std::vector<UINT32> &dest, const std::vector<bool> &state);
void compute_initial_bboxes(std::vector<bbox> &bboxes);
bool compute_mask_intersection_bbox(int key1, int key2, bbox &bb) const;
void compute_diff_image(const std::vector<UINT32> &rend, const bbox &bb, cached_bitmap &dest) const;
void compute_dual_diff_image(const std::vector<UINT32> &rend, const bbox &bb, const cached_bitmap &src1, const cached_bitmap &src2, cached_bitmap &dest) const;
void rebuild_cache();
void blit(bitmap_rgb32 &bitmap, const cached_bitmap &src) const;
};
screen_device_svg_renderer::screen_device_svg_renderer(memory_region *region)
@ -67,11 +98,28 @@ screen_device_svg_renderer::screen_device_svg_renderer(memory_region *region)
delete[] s;
m_rasterizer = nsvgCreateRasterizer();
m_key_count = 0;
for (NSVGshape *shape = m_image->shapes; shape != nullptr; shape = shape->next)
if(shape->title[0]) {
shape->flags &= ~NSVG_FLAGS_VISIBLE;
m_keyed_shapes[shape->title].push_back(shape);
auto it = m_key_ids.find(shape->title);
if(it != m_key_ids.end())
m_keyed_shapes[it->second].push_back(shape);
else {
int id = m_key_count;
m_key_count++;
m_keyed_shapes.resize(m_key_count);
m_keyed_shapes[id].push_back(shape);
m_key_ids[shape->title] = id;
}
}
m_key_state.resize(m_key_count);
// Don't memset a vector<bool>, they're special, and not in a good way
for(int i=0; i != m_key_count; i++)
m_key_state[i] = false;
m_sx = m_sy = 0;
m_scale = 1.0;
}
screen_device_svg_renderer::~screen_device_svg_renderer()
@ -90,33 +138,83 @@ int screen_device_svg_renderer::height() const
return int(m_image->height + 0.5);
}
void screen_device_svg_renderer::render_state(std::vector<UINT32> &dest, const std::vector<bool> &state)
{
for(int key = 0; key != m_key_count; key++) {
if (state[key])
for(auto s : m_keyed_shapes[key])
s->flags |= NSVG_FLAGS_VISIBLE;
else
for(auto s : m_keyed_shapes[key])
s->flags &= ~NSVG_FLAGS_VISIBLE;
}
nsvgRasterize(m_rasterizer, m_image, 0, 0, m_scale, (unsigned char *)&dest[0], m_sx, m_sy, m_sx*4);
// Nanosvg generates non-premultiplied alpha, so remultiply by
// alpha to "blend" against a black background. Plus align the
// channel order to what we do.
UINT8 *image = (UINT8 *)&dest[0];
for(unsigned int pixel=0; pixel != m_sy*m_sx; pixel++) {
UINT8 r = image[0];
UINT8 g = image[1];
UINT8 b = image[2];
UINT8 a = image[3];
if(a != 0xff) {
r = r*a/255;
g = g*a/255;
b = b*a/255;
}
UINT32 color = 0xff000000 | (r << 16) | (g << 8) | (b << 0);
*(UINT32 *)image = color;
image += 4;
}
}
void screen_device_svg_renderer::blit(bitmap_rgb32 &bitmap, const cached_bitmap &src) const
{
const UINT32 *s = &src.image[0];
for(int y=0; y<src.sy; y++) {
UINT32 *d = &bitmap.pix(y + src.y, src.x);
for(int x=0; x<src.sx; x++) {
UINT32 c = *s++;
if(c)
*d = c;
d++;
}
}
}
int screen_device_svg_renderer::render(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect)
{
double sx = double(bitmap.width())/m_image->width;
double sy = double(bitmap.height())/m_image->height;
double sz = sx > sy ? sy : sx;
nsvgRasterize(m_rasterizer, m_image, 0, 0, sz, (unsigned char *)bitmap.raw_pixptr(0, 0), bitmap.width(), bitmap.height(), bitmap.rowbytes());
int nsx = bitmap.width();
int nsy = bitmap.height();
// Annoyingly, nanosvg doesn't use the same byte order than our bitmaps
if(nsx != m_sx || nsy != m_sy) {
m_sx = nsx;
m_sy = nsy;
double sx = double(m_sx)/m_image->width;
double sy = double(m_sy)/m_image->height;
m_scale = sx > sy ? sy : sx;
m_background.resize(m_sx * m_sy);
rebuild_cache();
}
// It generates non-premultiplied alpha anyway, so remultiply by
// alpha to "blend" against a black background.
for(unsigned int y = 0; y < m_sy; y++)
memcpy(bitmap.raw_pixptr(y, 0), &m_background[y * m_sx], m_sx * 4);
for(unsigned int y=0; y<bitmap.height(); y++) {
UINT8 *image = (UINT8 *)bitmap.raw_pixptr(y, 0);
for(unsigned int x=0; x<bitmap.width(); x++) {
UINT8 r = image[0];
UINT8 g = image[1];
UINT8 b = image[2];
UINT8 a = image[3];
if(a != 0xff) {
r = r*a/255;
g = g*a/255;
b = b*a/255;
}
UINT32 color = 0xff000000 | (r << 16) | (g << 8) | (b << 0);
*(UINT32 *)image = color;
image += 4;
std::list<int> to_draw;
for(int key = 0; key != m_key_count; key++)
if(m_key_state[key])
to_draw.push_back(key);
while(!to_draw.empty()) {
int key = to_draw.front();
to_draw.pop_front();
blit(bitmap, m_cache[key]);
for(auto p : m_cache[key].pairs) {
if(m_key_state[p.key])
to_draw.push_back(p.cache_entry);
}
}
@ -130,15 +228,297 @@ void screen_device_svg_renderer::output_notifier(const char *outname, INT32 valu
void screen_device_svg_renderer::output_change(const char *outname, INT32 value)
{
auto l = m_keyed_shapes.find(outname);
if (l == m_keyed_shapes.end())
auto l = m_key_ids.find(outname);
if (l == m_key_ids.end())
return;
if (value)
for(auto s : l->second)
s->flags |= NSVG_FLAGS_VISIBLE;
else
for(auto s : l->second)
s->flags &= ~NSVG_FLAGS_VISIBLE;
m_key_state[l->second] = value;
}
void screen_device_svg_renderer::compute_initial_bboxes(std::vector<bbox> &bboxes)
{
bboxes.resize(m_key_count);
for(int key = 0; key != m_key_count; key++) {
int x0, y0, x1, y1;
x0 = y0 = x1 = y1 = -1;
for(auto s : m_keyed_shapes[key]) {
int xx0 = int(floor(s->bounds[0]*m_scale));
int yy0 = int(floor(s->bounds[1]*m_scale));
int xx1 = int(ceil (s->bounds[2]*m_scale)) + 1;
int yy1 = int(ceil (s->bounds[3]*m_scale)) + 1;
if(xx0 < 0)
xx0 = 0;
if(xx0 >= m_sx)
xx0 = m_sx - 1;
if(xx1 < 0)
xx1 = 0;
if(xx1 >= m_sx)
xx1 = m_sx - 1;
if(yy0 < 0)
yy0 = 0;
if(yy0 >= m_sy)
yy0 = m_sy - 1;
if(yy1 < 0)
yy1 = 0;
if(yy1 >= m_sy)
yy1 = m_sy - 1;
if(x0 == -1) {
x0 = xx0;
y0 = yy0;
x1 = xx1;
y1 = yy1;
} else {
if(xx0 < x0)
x0 = xx0;
if(yy0 < y0)
y0 = yy0;
if(xx1 > x1)
x1 = xx1;
if(yy1 > y1)
y1 = yy1;
}
}
bboxes[key].x0 = x0;
bboxes[key].y0 = y0;
bboxes[key].x1 = x1;
bboxes[key].y1 = y1;
}
}
void screen_device_svg_renderer::compute_diff_image(const std::vector<UINT32> &rend, const bbox &bb, cached_bitmap &dest) const
{
int x0, y0, x1, y1;
x0 = y0 = x1 = y1 = -1;
for(int y = bb.y0; y != bb.y1; y++) {
const UINT32 *src1 = &m_background[bb.x0 + y * m_sx];
const UINT32 *src2 = &rend[bb.x0 + y * m_sx];
for(int x = bb.x0; x != bb.x1; x++) {
if(*src1 != *src2) {
if(x0 == -1) {
x0 = x1 = x;
y0 = y1 = y;
} else {
if(x < x0)
x0 = x;
if(y < y0)
y0 = y;
if(x > x1)
x1 = x;
if(y > y1)
y1 = y;
}
}
src1++;
src2++;
}
}
if(x0 == -1) {
dest.x = dest.y = dest.sx = dest.sy = 0;
return;
}
dest.x = x0;
dest.y = y0;
dest.sx = x1+1-x0;
dest.sy = y1+1-y0;
dest.image.resize(dest.sx * dest.sy);
UINT32 *dst = &dest.image[0];
for(int y = 0; y != dest.sy; y++) {
const UINT32 *src1 = &m_background[dest.x + (y + dest.y) * m_sx];
const UINT32 *src2 = &rend[dest.x + (y + dest.y) * m_sx];
for(int x = 0; x != dest.sx; x++) {
if(*src1 != *src2)
*dst = *src2;
else
*dst = 0x00000000;
src1++;
src2++;
dst++;
}
}
}
bool screen_device_svg_renderer::compute_mask_intersection_bbox(int key1, int key2, bbox &bb) const
{
const cached_bitmap &c1 = m_cache[key1];
const cached_bitmap &c2 = m_cache[key2];
if(c1.x >= c2.x + c2.sx ||
c1.x + c1.sx <= c2.x ||
c1.y >= c2.y + c2.sy ||
c1.y + c1.sy <= c2.y)
return false;
int cx0 = c1.x > c2.x ? c1.x : c2.x;
int cy0 = c1.y > c2.y ? c1.y : c2.y;
int cx1 = c1.x + c1.sx < c2.x + c2.sx ? c1.x + c1.sx : c2.x + c2.sx;
int cy1 = c1.y + c1.sy < c2.y + c2.sy ? c1.y + c1.sy : c2.y + c2.sy;
int x0, y0, x1, y1;
x0 = y0 = x1 = y1 = -1;
for(int y = cy0; y < cy1; y++) {
const UINT32 *src1 = &c1.image[(cx0 - c1.x) + c1.sx * (y - c1.y)];
const UINT32 *src2 = &c2.image[(cx0 - c2.x) + c2.sx * (y - c2.y)];
for(int x = cx0; x < cx1; x++) {
if(*src1 && *src2 && *src1 != *src2) {
if(x0 == -1) {
x0 = x1 = x;
y0 = y1 = y;
} else {
if(x < x0)
x0 = x;
if(y < y0)
y0 = y;
if(x > x1)
x1 = x;
if(y > y1)
y1 = y;
}
}
src1++;
src2++;
}
}
if(x0 == -1)
return false;
bb.x0 = x0;
bb.x1 = x1;
bb.y0 = y0;
bb.y1 = y1;
return true;
}
void screen_device_svg_renderer::compute_dual_diff_image(const std::vector<UINT32> &rend, const bbox &bb, const cached_bitmap &src1, const cached_bitmap &src2, cached_bitmap &dest) const
{
dest.x = bb.x0;
dest.y = bb.y0;
dest.sx = bb.x1 - bb.x0 + 1;
dest.sy = bb.y1 - bb.y0 + 1;
dest.image.resize(dest.sx*dest.sy);
for(int y = 0; y != dest.sy; y++) {
const UINT32 *psrc1 = &src1.image[(dest.x - src1.x) + src1.sx * (y + dest.y - src1.y)];
const UINT32 *psrc2 = &src2.image[(dest.x - src2.x) + src2.sx * (y + dest.y - src2.y)];
const UINT32 *psrcr = &rend [ dest.x + m_sx * (y + dest.y )];
UINT32 *pdest = &dest.image[ dest.sx * y ];
for(int x = 0; x != dest.sx; x++) {
if(*psrc1 && *psrc2 && *psrc1 != *psrc2)
*pdest = *psrcr;
psrc1++;
psrc2++;
psrcr++;
pdest++;
}
}
}
void screen_device_svg_renderer::rebuild_cache()
{
m_cache.clear();
std::vector<UINT32> rend(m_sx*m_sy);
// Render the background, e.g. with everything off
std::vector<bool> state(m_key_count);
for(int key=0; key != m_key_count; key++)
state[key] = false;
render_state(m_background, state);
// Render each individual element independently. Try to reduce
// the actual number of render passes with a greedy algorithm
// using the element bounding boxes.
std::vector<bbox> bboxes;
compute_initial_bboxes(bboxes);
m_cache.resize(m_key_count);
std::set<int> to_do;
for(int key=0; key != m_key_count; key++)
to_do.insert(key);
while(!to_do.empty()) {
std::list<int> doing;
for(int key : to_do) {
for(int okey : doing) {
// The bounding boxes include x1/y1, so the comparisons must be strict
if(!(bboxes[key].x0 > bboxes[okey].x1 ||
bboxes[key].x1 < bboxes[okey].x0 ||
bboxes[key].y0 > bboxes[okey].y1 ||
bboxes[key].y1 < bboxes[okey].y0))
goto conflict;
}
doing.push_back(key);
conflict:
;
}
for(int key : doing)
state[key] = true;
render_state(rend, state);
for(int key : doing) {
state[key] = false;
to_do.erase(key);
}
for(int key : doing)
compute_diff_image(rend, bboxes[key], m_cache[key]);
}
// Then it's time to pick up the interactions.
int spos = 0;
int epos = m_key_count;
std::vector<std::list<int> > keys(m_key_count);
std::vector<int> previous;
for(int key = 0; key != m_key_count; key++)
keys[key].push_back(key);
int ckey = m_key_count;
while(spos != epos) {
for(int key = spos; key < epos-1; key++) {
for(int key2 = keys[key].back()+1; key2 < m_key_count; key2++) {
bbox bb;
if(compute_mask_intersection_bbox(key, key2, bb)) {
previous.resize(ckey+1);
previous[ckey] = key;
m_cache[key].pairs.push_back(paired_entry(key2, ckey));
keys.push_back(keys[key]);
keys.back().push_back(key2);
bboxes.push_back(bb);
ckey++;
}
}
}
m_cache.resize(ckey);
std::set<int> to_do;
for(int key = epos; key != ckey; key++)
to_do.insert(key);
while(!to_do.empty()) {
std::list<int> doing;
for(int key : to_do) {
for(int okey : doing) {
// The bounding boxes include x1/y1, so the comparisons must be strict
if(!(bboxes[key].x0 > bboxes[okey].x1 ||
bboxes[key].x1 < bboxes[okey].x0 ||
bboxes[key].y0 > bboxes[okey].y1 ||
bboxes[key].y1 < bboxes[okey].y0))
goto conflict2;
}
doing.push_back(key);
conflict2:
;
}
for(int key : doing)
for(int akey : keys[key])
state[akey] = true;
render_state(rend, state);
for(int key : doing) {
for(int akey : keys[key])
state[akey] = false;
to_do.erase(key);
}
for(int key : doing)
compute_dual_diff_image(rend, bboxes[key], m_cache[previous[key]], m_cache[keys[key].back()], m_cache[key]);
}
spos = epos;
epos = ckey;
}
}
//**************************************************************************