mpv/libvo/bitmap_packer.c

207 lines
7.2 KiB
C
Raw Normal View History

/*
* Calculate how to pack bitmap rectangles into a larger surface
*
* Copyright 2009, 2012 Uoti Urpala
*
* This file is part of mplayer2.
*
* mplayer2 is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* mplayer2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with mplayer2. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <libavutil/common.h>
#include "talloc.h"
#include "bitmap_packer.h"
#include "mp_msg.h"
#include "mpcommon.h"
#include "sub/ass_mp.h"
#include "sub/dec_sub.h"
#define HEIGHT_SORT_BITS 4
static int size_index(int s)
{
int n = av_log2_16bit(s);
return (n << HEIGHT_SORT_BITS)
+ (- 1 - (s << HEIGHT_SORT_BITS >> n) & (1 << HEIGHT_SORT_BITS) - 1);
}
/* Pack the given rectangles into an area of size w * h.
* The size of each rectangle is read from in[i].x / in[i].y.
* The height of each rectangle must be less than 65536.
* 'scratch' must point to work memory for num_rects+16 ints.
* The packed position for rectangle number i is set in out[i].
* Return 0 on success, -1 if the rectangles did not fit in w*h.
*
* The rectangles are placed in rows in order approximately sorted by
* height (the approximate sorting is simpler than a full one would be,
* and allows the algorithm to work in linear time). Additionally, to
* reduce wasted space when there are a few tall rectangles, empty
* lower-right parts of rows are filled recursively when the size of
* rectangles in the row drops past a power-of-two threshold. So if a
* row starts with rectangles of size 3x50, 10x40 and 5x20 then the
* free rectangle with corners (13, 20)-(w, 50) is filled recursively.
*/
static int pack_rectangles(struct pos *in, struct pos *out, int num_rects,
int w, int h, int *scratch, int *used_width)
{
int bins[16 << HEIGHT_SORT_BITS];
int sizes[16 << HEIGHT_SORT_BITS] = { 0 };
for (int i = 0; i < num_rects; i++)
sizes[size_index(in[i].y)]++;
int idx = 0;
for (int i = 0; i < 16 << HEIGHT_SORT_BITS; i += 1 << HEIGHT_SORT_BITS) {
for (int j = 0; j < 1 << HEIGHT_SORT_BITS; j++) {
bins[i + j] = idx;
idx += sizes[i + j];
}
scratch[idx++] = -1;
}
for (int i = 0; i < num_rects; i++)
scratch[bins[size_index(in[i].y)]++] = i;
for (int i = 0; i < 16; i++)
bins[i] = bins[i << HEIGHT_SORT_BITS] - sizes[i << HEIGHT_SORT_BITS];
struct {
int size, x, bottom;
} stack[16] = {{15, 0, h}}, s = {};
int stackpos = 1;
int y;
while (stackpos) {
y = s.bottom;
s = stack[--stackpos];
s.size++;
while (s.size--) {
int maxy = -1;
int obj;
while ((obj = scratch[bins[s.size]]) >= 0) {
int bottom = y + in[obj].y;
if (bottom > s.bottom)
break;
int right = s.x + in[obj].x;
if (right > w)
break;
bins[s.size]++;
out[obj] = (struct pos){s.x, y};
num_rects--;
if (maxy < 0)
stack[stackpos++] = s;
s.x = right;
maxy = FFMAX(maxy, bottom);
}
*used_width = FFMAX(*used_width, s.x);
if (maxy > 0)
s.bottom = maxy;
}
}
return num_rects ? -1 : y;
}
int packer_pack(struct bitmap_packer *packer)
{
if (packer->count == 0)
return 0;
int w_orig = packer->w, h_orig = packer->h;
struct pos *in = packer->in;
int xmax = 0, ymax = 0;
for (int i = 0; i < packer->count; i++) {
if (in[i].x <= packer->padding || in[i].y <= packer->padding)
in[i] = (struct pos){0, 0};
if (in[i].x < 0 || in [i].x > 65535 || in[i].y < 0 || in[i].y > 65535) {
mp_msg(MSGT_VO, MSGL_FATAL, "Invalid OSD / subtitle bitmap size\n");
abort();
}
xmax = FFMAX(xmax, in[i].x);
ymax = FFMAX(ymax, in[i].y);
}
xmax = FFMAX(0, xmax - packer->padding);
ymax = FFMAX(0, ymax - packer->padding);
if (xmax > packer->w)
packer->w = 1 << av_log2(xmax - 1) + 1;
if (ymax > packer->h)
packer->h = 1 << av_log2(ymax - 1) + 1;
while (1) {
int used_width = 0;
int y = pack_rectangles(in, packer->result, packer->count,
packer->w + packer->padding,
packer->h + packer->padding,
packer->scratch, &used_width);
if (y >= 0) {
// No padding at edges
packer->used_width = FFMIN(used_width, packer->w);
packer->used_height = FFMIN(y, packer->h);
return packer->w != w_orig || packer->h != h_orig;
}
if (packer->w <= packer->h && packer->w != packer->w_max)
packer->w = FFMIN(packer->w * 2, packer->w_max);
else if (packer->h != packer->h_max)
packer->h = FFMIN(packer->h * 2, packer->h_max);
else {
packer->w = w_orig;
packer->h = h_orig;
return -1;
}
}
}
void packer_set_size(struct bitmap_packer *packer, int size)
{
packer->count = size;
if (size <= packer->asize)
return;
packer->asize = FFMAX(packer->asize * 2, size);
talloc_free(packer->result);
talloc_free(packer->scratch);
packer->in = talloc_realloc(packer, packer->in, struct pos, packer->asize);
packer->result = talloc_array_ptrtype(packer, packer->result,
packer->asize);
packer->scratch = talloc_array_ptrtype(packer, packer->scratch,
packer->asize + 16);
}
static int packer_pack_from_assimg(struct bitmap_packer *packer,
struct ass_image *imglist)
{
int count = 0;
struct ass_image *img = imglist;
while (img) {
if (count >= packer->asize)
packer_set_size(packer, FFMAX(packer->asize * 2, 32));
packer->in[count].x = img->w;
packer->in[count].y = img->h;
img = img->next;
count++;
}
packer->count = count;
return packer_pack(packer);
}
int packer_pack_from_subbitmaps(struct bitmap_packer *packer,
struct sub_bitmaps *b, int padding_pixels)
{
packer->padding = 0;
packer->count = 0;
if (b->type == SUBBITMAP_EMPTY)
return 0;
if (b->type == SUBBITMAP_LIBASS)
return packer_pack_from_assimg(packer, b->imgs);
packer->padding = padding_pixels;
packer_set_size(packer, b->part_count);
int a = packer->padding;
for (int i = 0; i < b->part_count; i++)
packer->in[i] = (struct pos){b->parts[i].w + a, b->parts[i].h + a};
return packer_pack(packer);
}