Implement downsampling of color emoji bitmaps

Still need to fix placement of the downsampled bitmaps in the cell
This commit is contained in:
Kovid Goyal 2017-12-08 00:16:47 +05:30
parent 8d7515bd9b
commit 03847b15f6
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C

View File

@ -317,6 +317,7 @@ typedef struct {
size_t start_x, width, stride;
size_t rows;
FT_Pixel_Mode pixel_mode;
bool needs_free;
} ProcessedBitmap;
@ -338,7 +339,7 @@ trim_borders(ProcessedBitmap *ans, size_t extra) {
static inline bool
render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int num_cells, bool bold, bool italic, bool rescale) {
render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, bool bold, bool italic, bool rescale) {
if (!load_glyph(self, glyph_id, FT_LOAD_RENDER)) return false;
unsigned int max_width = cell_width * num_cells;
FT_Bitmap *bitmap = &self->face->glyph->bitmap;
@ -355,7 +356,7 @@ render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_
FT_F26Dot6 char_width = self->char_width, char_height = self->char_height;
float ar = (float)max_width / (float)bitmap->width;
if (set_font_size(self, (FT_F26Dot6)((float)self->char_width * ar), (FT_F26Dot6)((float)self->char_height * ar), self->xdpi, self->ydpi, 0)) {
if (!render_bitmap(self, glyph_id, ans, cell_width, num_cells, bold, italic, false)) return false;
if (!render_bitmap(self, glyph_id, ans, cell_width, cell_height, num_cells, bold, italic, false)) return false;
if (!set_font_size(self, char_width, char_height, self->xdpi, self->ydpi, 0)) return false;
} else return false;
}
@ -363,9 +364,39 @@ render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_
return true;
}
static void
downsample_bitmap(ProcessedBitmap *bm, unsigned int width, unsigned int cell_height) {
// Downsample using a simple area averaging algorithm. Could probably do
// better with bi-cubic or lanczos, but at these small sizes I dont think
// it matters
float ratio = MAX((float)bm->width / width, (float)bm->rows / cell_height);
int factor = ceilf(ratio);
uint8_t *dest = calloc(4, width * cell_height);
if (dest == NULL) fatal("Out of memory");
uint8_t *d = dest;
for (unsigned int i = 0, sr = 0; i < cell_height; i++, sr += factor) {
for (unsigned int j = 0, sc = 0; j < width; j++, sc += factor, d += 4) {
// calculate area average
unsigned int r=0, g=0, b=0, a=0, count=0;
for (unsigned int y=sr; y < MIN(sr + factor, bm->rows); y++) {
uint8_t *p = bm->buf + (y * bm->stride) + sc * 4;
for (unsigned int x=sc; x < MIN(sc + factor, bm->width); x++, count++) {
b += *(p++); g += *(p++); r += *(p++); a += *(p++);
}
}
if (count) {
d[0] = b / count; d[1] = g / count; d[2] = r / count; d[3] = a / count;
}
}
}
bm->buf = dest; bm->needs_free = true; bm->stride = 4 * width; bm->width = width; bm->rows = cell_height;
}
static inline bool
render_color_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int num_cells, bool bold, bool italic) {
render_color_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, bool bold, bool italic) {
unsigned short best = 0, diff = USHRT_MAX;
for (short i = 0; i < self->face->num_fixed_sizes; i++) {
unsigned short w = self->face->available_sizes[i].width;
@ -378,13 +409,15 @@ render_color_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int
FT_Error error = FT_Select_Size(self->face, best);
if (error) { set_freetype_error("Failed to set char size for non-scalable font, with error:", error); return false; }
if (!load_glyph(self, glyph_id, FT_LOAD_COLOR)) return false;
FT_Set_Char_Size(self->face, 0, self->char_height, self->xdpi, self->ydpi);
FT_Bitmap *bitmap = &self->face->glyph->bitmap;
if (bitmap->pixel_mode != FT_PIXEL_MODE_BGRA) return render_bitmap(self, glyph_id, ans, cell_width, num_cells, bold, italic, true);
if (bitmap->pixel_mode != FT_PIXEL_MODE_BGRA) return render_bitmap(self, glyph_id, ans, cell_width, cell_height, num_cells, bold, italic, true);
ans->buf = bitmap->buffer;
ans->start_x = 0; ans->width = bitmap->width;
ans->stride = bitmap->pitch < 0 ? -bitmap->pitch : bitmap->pitch;
ans->rows = bitmap->rows;
ans->pixel_mode = bitmap->pixel_mode;
if (ans->width > num_cells * cell_width + 2) downsample_bitmap(ans, num_cells * cell_width, cell_height);
return true;
}
@ -451,23 +484,27 @@ right_shift_canvas(pixel *canvas, size_t width, size_t height, size_t amt) {
}
}
static const ProcessedBitmap EMPTY_PBM = {0};
bool
render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored) {
Face *self = (Face*)f;
bool is_emoji = *was_colored; *was_colored = is_emoji && self->face->num_fixed_sizes > 0 && self->has_color;
float x = 0.f, y = 0.f, x_offset = 0.f;
ProcessedBitmap bm;
ProcessedBitmap bm = {0};
unsigned int canvas_width = cell_width * num_cells;
for (unsigned int i = 0; i < num_glyphs; i++) {
if (*was_colored) {
if (!render_color_bitmap(self, info[i].codepoint, &bm, cell_width, num_cells, bold, italic)) return false;
if (!render_color_bitmap(self, info[i].codepoint, &bm, cell_width, cell_height, num_cells, bold, italic)) return false;
} else {
if (!render_bitmap(self, info[i].codepoint, &bm, cell_width, num_cells, bold, italic, true)) return false;
if (!render_bitmap(self, info[i].codepoint, &bm, cell_width, cell_height, num_cells, bold, italic, true)) return false;
}
x_offset = x + (float)positions[i].x_offset / 64.0f;
y = (float)positions[i].y_offset / 64.0f;
if (self->face->glyph->metrics.width > 0 && bm.width > 0) place_bitmap_in_canvas(canvas, &bm, canvas_width, cell_height, x_offset, y, &self->face->glyph->metrics, baseline);
x += (float)positions[i].x_advance / 64.0f;
if (bm.needs_free) free(bm.buf);
bm = EMPTY_PBM;
}
unsigned int right_edge = (unsigned int)x, delta;
if (num_cells > 1 && right_edge < canvas_width && (delta = (canvas_width - right_edge) / 2) && delta > 1) {