avs2bdnxml/auto_split.c
2013-07-23 12:04:20 +02:00

564 lines
12 KiB
C

/*----------------------------------------------------------------------------
* avs2bdnxml - Generates BluRay subtitle stuff from RGBA AviSynth scripts
* Copyright (C) 2008-2013 Arne Bochem <avs2bdnxml at ps-auxw de>
*
* This program 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 3 of the License, or
* (at your option) any later version.
*
* This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*----------------------------------------------------------------------------*/
#include <stdint.h>
#include <string.h>
#include <limits.h>
#include "auto_split.h"
#include "abstract_lists.h"
#include "sort.h"
/* Transparent pixels are assumed to be set to zero */
void auto_crop (pic_t p, crop_t *c)
{
uint32_t *b = (uint32_t *)p.b;
int min_x = INT_MAX, max_x = INT_MIN, min_y = INT_MAX, max_y = INT_MIN;
int seen_pixel = 0;
int x, y;
for (y = c->y; y < c->y + c->h && y < p.h; y++)
for (x = c->x; x < c->x + c->w && x < p.w; x++)
if (b[x + p.s * y])
{
seen_pixel = 1;
if (x < min_x)
min_x = x;
if (x > max_x)
max_x = x;
if (y < min_y)
min_y = y;
if (y > max_y)
max_y = y;
}
if (!seen_pixel)
{
c->w = 0;
c->h = 0;
}
else
{
c->x = min_x;
c->y = min_y;
c->w = max_x - min_x + 1;
c->h = max_y - min_y + 1;
}
/* Ensure no forbidden/tiny results are produced */
if (c->w < 8)
{
if (c->x + 8 > p.w)
c->x -= c->x + 8 - p.w;
c->w = 8;
}
if (c->h < 8)
{
if (c->y + 8 > p.h)
c->y -= c->y + 8 - p.h;
c->h = 8;
}
}
static int block_state (pic_t p, crop_t c)
{
uint32_t *b = (uint32_t *)p.b;
int x, y;
for (y = c.y; y < c.y + c.h && y < p.h; y++)
for (x = c.x; x < c.x + c.w && x < p.w; x++)
if (b[x + p.s * y])
return -1;
return 0;
}
#define GRID_BLOCKS 24 /* GCD of 480, 576, 720, 1080 */
static int line_ok (int grid[GRID_BLOCKS + 1][GRID_BLOCKS + 1], int x, int y, int w)
{
int i;
if (grid[y][MAX(0, x - 1)] == -1)
return 0;
for (i = 0; i < w; i++)
if (grid[y][x + i] != -1)
return 0;
if (grid[y][MIN(GRID_BLOCKS, x + i)] == -1)
return 0;
return 1;
}
static void set_line (int grid[GRID_BLOCKS + 1][GRID_BLOCKS + 1], int x, int y, int w, int n)
{
int i;
for (i = 0; i < w; i++)
grid[y][x + i] = n;
}
static rect_t make_rect (int grid[GRID_BLOCKS + 1][GRID_BLOCKS + 1], int x, int y, int n)
{
rect_t r = {x, y, 1, 1};
int line_length = 1;
/* Get length of first rectangle line, and assign rect number */
grid[y][x] = n;
while (line_length + x < GRID_BLOCKS + 1 && grid[y][x + line_length] == -1)
{
grid[y][x + line_length] = n;
line_length++;
}
r.w = line_length;
/* Add lines while available */
while (line_ok(grid, x, r.y + r.h, r.w))
set_line(grid, x, r.y + r.h++, r.w, n);
return r;
}
rect_t merge_rects (rect_t r1, rect_t r2)
{
rect_t r;
/* Set rectangle that covers both input rectangles */
r.x = MIN(r1.x, r2.x);
r.y = MIN(r1.y, r2.y);
r.w = MAX(r1.x + r1.w, r2.x + r2.w) - r.x;
r.h = MAX(r1.y + r1.h, r2.y + r2.h) - r.y;
return r;
}
int score_rect (rect_t r)
{
return r.w * r.h;
}
static int check_close (rect_t r1, rect_t r2, int distance)
{
rect_t a, b;
if (r1.x <= r2.x)
{
a = r1;
b = r2;
}
else
{
a = r2;
b = r1;
}
/* The rectangles are not too close, if the left border of of the right-most
* rectangle has a distance of at least distance to the right border of the
* left-most rectangle.
*/
if (b.x > a.x + a.w + distance)
return 0;
if (r1.y <= r2.y)
{
a = r1;
b = r2;
}
else
{
a = r2;
b = r1;
}
/* Same as before, except vertically */
if (b.y > a.y + a.h + distance)
return 0;
/* Too close! */
return 1;
}
/* crop_t *c - Array of length 2 */
int auto_split (pic_t p, crop_t *c, int ugly, int even_y)
{
crop_t c1 = {0, 0, 0, 0};
crop_t c2 = {0, 0, 0, 0};
crop_t null = {0, 0, 0, 0};
crop_t t;
rect_t rects[(GRID_BLOCKS + 1) * (GRID_BLOCKS + 1)];
rect_t r1 = {0};
rect_t r2 = {0};
rect_t rt1, rt2;
int grid[GRID_BLOCKS + 1][GRID_BLOCKS + 1];
int score_t1, score_t2, score_r1 = 0, score_r2 = 0;
int n_rect = 0;
int score = 0;
int bw, bh;
int x, y;
int i, j;
int n_res;
/* Initialize grid */
memset(grid, 0, sizeof(int) * (GRID_BLOCKS + 1) * (GRID_BLOCKS + 1));
bw = p.w / GRID_BLOCKS;
bh = p.h / GRID_BLOCKS;
/* Ensure block height is even, if even_y is enabled */
if (even_y && (bh % 2))
bh--;
/* Ensure block dimensions are not zero */
if (!bw)
bw = 1;
if (!bh && even_y)
bh = 2;
else if (!bh)
bh = 1;
/* Determine state of blocks */
t.w = bw;
t.h = bh;
for (y = 0; (t.y = y * bh) < p.h; y++)
for (x = 0; (t.x = x * bw) < p.w; x++)
grid[y][x] = block_state(p, t);
/* Create rectangles */
for (y = 0; y < GRID_BLOCKS + 1; y++)
for (x = 0; x < GRID_BLOCKS + 1; x++)
if (grid[y][x] == -1)
{
rects[n_rect] = make_rect(grid, x, y, n_rect);
n_rect++;
}
/* Shouldn't happen, empty frame */
if (!n_rect)
{
c[0] = c1;
c[1] = c2;
return 0;
}
/* Single rectangle */
if (n_rect == 1)
{
c1.x = rects[0].x * bw;
c1.y = rects[0].y * bh;
c1.w = rects[0].w * bw;
c1.h = rects[0].h * bh;
auto_crop(p, &c1);
c[0] = c1;
c[1] = c2;
return 1;
}
/* Two rectangles */
n_res = 2;
/* Any other number of rectangles, first find most "distant" ones */
for (i = 0; i < n_rect; i++)
for (j = 0; j < n_rect; j++)
if (i == j)
continue;
else
{
rt1 = merge_rects(rects[i], rects[j]);
score_t1 = score_rect(rt1);
if (score <= score_t1)
{
score = score_t1;
r1 = rects[i];
r2 = rects[j];
score_r1 = score_rect(r1);
score_r2 = score_rect(r2);
}
}
/* Merge all other rectangles with the "nearest" one */
for (i = 0; i < n_rect; i++)
{
rt1 = merge_rects(r1, rects[i]);
rt2 = merge_rects(r2, rects[i]);
score_t1 = score_rect(rt1);
score_t2 = score_rect(rt2);
if (score_t1 - score_r1 < score_t2 - score_r2)
{
r1 = rt1;
score_r1 = score_t1;
}
else
{
r2 = rt2;
score_r2 = score_t2;
}
}
/* Turn rectangles into crops */
c1.x = r1.x * bw;
c1.y = r1.y * bh;
c1.w = r1.w * bw;
c1.h = r1.h * bh;
c2.x = r2.x * bw;
c2.y = r2.y * bh;
c2.w = r2.w * bw;
c2.h = r2.h * bh;
/* Minimize surfaces and return them */
auto_crop(p, &c1);
auto_crop(p, &c2);
/* Merge in rare cases of closeness or overlap */
if ((!ugly && check_close(c1, c2, 0)) || (ugly && check_close(c1, c2, -1)))
{
c1 = merge_rects(c1, c2);
c2 = null;
auto_crop(p, &c1);
n_res = 1;
}
else if (!ugly)
{
/* Check whether split is ugly due to small gains */
rt1 = merge_rects(c1, c2);
score_t1 = score_rect(rt1);
score_t2 = score_rect(c1) + score_rect(c2);
/* Merge if area taken by the merged rectangle is less than 1.5 * sum of
* split rectangles and the difference is below a hard limit.
*/
if ((score_t1 < 3 * score_t2 / 2) && (score_t1 - score_t2 < 500 * 300))
{
c1 = merge_rects(c1, c2);
c2 = null;
auto_crop(p, &c1);
n_res = 1;
}
}
c[0] = c1;
c[1] = c2;
return n_res;
}
/* Find two minimal non-overlapping windows covering the given rectangles. */
typedef struct window_s
{
rect_t r; /* Position and size. */
int s; /* Score. */
} window_t;
typedef struct interval_s
{
rect_t r;
int start, end;
} interval_t;
STATIC_LIST(interval, interval_t)
int cmp_rect_x (rect_t *a, rect_t *b)
{
return a->x > b->x;
}
int cmp_rect_y (rect_t *a, rect_t *b)
{
return a->y > b->y;
}
void rect_bounds_x (rect_t *c, int *x, int *w)
{
*x = c->x;
*w = c->w;
}
void rect_bounds_y (rect_t *c, int *y, int *h)
{
*y = c->y;
*h = c->h;
}
/* The windows argument must point to 2 * sizeof(rect_t) allocated memory. */
int find_windows (rect_t *rects, int n_rects, rect_t *windows)
{
void (*rect_bounds)(rect_t *, int *, int *);
rect_t **sorted = malloc(sizeof(void *) * n_rects);
void *work[2][2] = { {rect_bounds_x, cmp_rect_x}
, {rect_bounds_y, cmp_rect_y}
};
interval_list_t *segs;
interval_t *iv = NULL;
window_t *fwd, *bwd;
rect_t best[2], tmp;
int i, dir, edge, a, b, n_ivs, found;
int score = -1;
if (!n_rects)
{
free(sorted);
return 0;
}
memset(best, 0, 2 * sizeof(rect_t));
found = 0;
for (dir = 0; dir < 2; dir++)
{
/* Sort rectangles from left/top to right/bottom. */
for (i = 0; i < n_rects; i++)
sorted[i] = &(rects[i]);
sort((sort_func_t)work[dir][1], (void **)sorted, n_rects);
/* Group overlapping rectangles. */
segs = interval_list_new();
n_ivs = 0;
edge = -1;
rect_bounds = work[dir][0];
for (i = 0; i < n_rects; i++)
{
rect_bounds(sorted[i], &a, &b);
if (a < edge)
{
iv->end = MAX(iv->end, a + b);
iv->r = merge_rects(iv->r, *(sorted[i]));
edge = iv->end;
}
else
{
iv = malloc(sizeof(interval_t));
iv->start = a;
iv->end = a + b;
iv->r = *(sorted[i]);
interval_list_insert_after(segs, iv);
n_ivs++;
edge = iv->end;
}
}
if (!n_ivs)
{
interval_list_destroy_deep(segs);
continue;
}
/* Sum up scores for interval 0-n and vice versa. */
fwd = malloc(sizeof(window_t) * n_ivs);
bwd = malloc(sizeof(window_t) * n_ivs);
iv = interval_list_first(segs);
/* Initialize first candidate. */
fwd[0].r = iv->r;
fwd[0].s = score_rect(fwd[0].r);
/* Merge/sum up the rest. */
for (i = 1; i < n_ivs && iv != NULL; i++)
{
iv = interval_list_next(segs);
fwd[i].r = merge_rects(fwd[i - 1].r, iv->r);
fwd[i].s = score_rect(fwd[i].r);
}
iv = interval_list_last(segs);
/* Corresponding to a "merged all" forward window is a null window. */
memset(&(bwd[n_ivs - 1]), 0, sizeof(window_t));
/* Initialize first real candidate if it exists. */
if (n_ivs > 1)
{
bwd[n_ivs - 2].r = iv->r;
bwd[n_ivs - 2].s = score_rect(bwd[n_ivs - 2].r);
}
/* Merge/sum up the rest. */
for (i = n_ivs - 3; i >= 0 && iv != NULL; i--)
{
iv = interval_list_prev(segs);
bwd[i].r = merge_rects(bwd[i + 1].r, iv->r);
bwd[i].s = score_rect(bwd[i].r);
}
/* Find best pair of two windows. */
for (i = 0; i < n_ivs; i++)
if (fwd[i].s + bwd[i].s < score || score == -1)
{
score = fwd[i].s + bwd[i].s;
best[0] = fwd[i].r;
best[1] = bwd[i].r;
}
/* Cleanup. */
free(bwd);
free(fwd);
interval_list_destroy_deep(segs);
}
/* Is any of the best rectangles not null? */
if ((best[0].w != 0 && best[0].h != 0) || (best[1].w != 0 && best[1].h != 0))
{
/* We have at least one. */
found = 1;
/* If the first one is null, set it to the rect of the second and null that one. */
if (best[0].w == 0 || best[0].h == 0)
{
best[0] = best[1];
memset(&(best[1]), 0, sizeof(rect_t));
}
/* If the second rect is still not null... */
if (best[1].w != 0 && best[1].h != 0)
{
/* We found two. */
found = 2;
/* Let's order them, so the one closer to 0/0 is the second. */
if (best[1].y > best[0].y || (best[1].y == best[0].y && best[1].x > best[0].x))
{
tmp = best[0];
best[0] = best[1];
best[1] = tmp;
}
}
memcpy(windows, best, 2 * sizeof(rect_t));
}
/* Cleanup. */
free(sorted);
return found;
}
void enforce_even_y (crop_t *c, int n)
{
int mod;
if (!n)
return;
/* Since block borders used to auto split always lie on even rows, y will
* only be odd when it was shrunk to below a block border, meaning expanding it
* back up by one row should be harmless and never lead to overlap.
*/
mod = c[0].y % 2;
c[0].y -= mod;
c[0].h += mod;
if (n > 1)
{
mod = c[1].y % 2;
c[1].y -= mod;
c[1].h += mod;
}
}