/* * Copyright (C) 2000-2005 the xine project * * This file is part of xine, a free video player. * * xine 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. * * xine 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA * * OSD stuff (text and graphic primitives) */ #define __OSD_C__ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #ifdef HAVE_DIRENT_H #include #endif #include #ifdef HAVE_ICONV # include #endif #define LOG_MODULE "osd" #define LOG_VERBOSE /* #define LOG */ #define XINE_ENGINE_INTERNAL #include "xine_internal.h" #include "xine-engine/bswap.h" #include "xineutils.h" #include "video_out.h" #include "osd.h" #ifdef HAVE_FT2 #include #include FT_FREETYPE_H # ifdef HAVE_FONTCONFIG # include # endif #endif #define FONT_VERSION 2 #define BINARY_SEARCH 1 /* unicode value of alias character, * used if conversion fails */ #define ALIAS_CHARACTER_CONV '#' /* unicode value of alias character, * used if character isn't in the font */ #define ALIAS_CHARACTER_FONT '_' /* we want UCS-2 encoding in the machine endian */ #ifdef WORDS_BIGENDIAN # define UCS2_ENCODING "UCS-2BE" #else # define UCS2_ENCODING "UCS-2LE" #endif #ifdef MAX #undef MAX #endif #define MAX(a,b) ( (a) > (b) ) ? (a) : (b) #ifdef MIN #undef MIN #endif #define MIN(a,b) ( (a) < (b) ) ? (a) : (b) #if (FREETYPE_MAJOR > 2) || \ (FREETYPE_MAJOR == 2 && FREETYPE_MINOR > 1) || \ (FREETYPE_MAJOR == 2 && FREETYPE_MINOR == 1 && FREETYPE_PATCH >= 3) # define KERNING_DEFAULT FT_KERNING_DEFAULT #else # define KERNING_DEFAULT ft_kerning_default #endif #ifdef ENABLE_ANTIALIASING # define FT_LOAD_FLAGS FT_LOAD_DEFAULT #else # define FT_LOAD_FLAGS (FT_LOAD_DEFAULT | FT_LOAD_NO_HINTING) #endif typedef struct osd_fontchar_s { uint8_t *bmp; uint16_t code; uint16_t width; uint16_t height; } osd_fontchar_t; struct osd_font_s { char name[40]; char *filename; osd_fontchar_t *fontchar; osd_font_t *next; uint16_t version; uint16_t size; uint16_t num_fontchars; uint16_t loaded; }; #ifdef HAVE_FT2 struct osd_ft2context_s { FT_Library library; FT_Face face; int size; }; #endif /* * open a new osd object. this will allocated an empty (all zero) drawing * area where graphic primitives may be used. * It is ok to specify big width and height values. The render will keep * track of the smallest changed area to not generate too big overlays. * A default palette is initialized (i sugest keeping color 0 as transparent * for the sake of simplicity) */ static osd_object_t *osd_new_object (osd_renderer_t *this, int width, int height) { osd_object_t *osd; pthread_mutex_lock (&this->osd_mutex); osd = xine_xmalloc( sizeof(osd_object_t) ); osd->renderer = this; osd->next = this->osds; this->osds = osd; osd->width = width; osd->height = height; osd->area = xine_xmalloc( width * height ); osd->x1 = width; osd->y1 = height; osd->x2 = 0; osd->y2 = 0; memcpy(osd->color, textpalettes_color[0], sizeof(textpalettes_color[0])); memcpy(osd->trans, textpalettes_trans[0], sizeof(textpalettes_trans[0])); osd->handle = -1; #ifdef HAVE_ICONV osd->cd = (iconv_t)-1; osd->encoding = NULL; #endif pthread_mutex_unlock (&this->osd_mutex); lprintf("osd=%p size: %dx%d\n", osd, width, height); return osd; } /* #define DEBUG_RLE */ static int _osd_hide (osd_object_t *osd, int64_t vpts); /* * send the osd to be displayed at given pts (0=now) * the object is not changed. there may be subsequent drawing on it. */ static int _osd_show (osd_object_t *osd, int64_t vpts, int unscaled ) { osd_renderer_t *this = osd->renderer; video_overlay_manager_t *ovl_manager; rle_elem_t rle, *rle_p=0; int x, y, required; uint8_t *c; lprintf("osd=%p vpts=%"PRId64"\n", osd, vpts); this->stream->xine->port_ticket->acquire(this->stream->xine->port_ticket, 1); ovl_manager = this->stream->video_out->get_overlay_manager(this->stream->video_out); if( osd->handle < 0 ) { if( (osd->handle = ovl_manager->get_handle(ovl_manager, 0)) == -1 ) { this->stream->xine->port_ticket->release(this->stream->xine->port_ticket, 1); return 0; } } pthread_mutex_lock (&this->osd_mutex); /* clip update area to allowed range */ if(osd->x1 > osd->width) osd->x1 = osd->width; if(osd->x2 > osd->width) osd->x2 = osd->width; if(osd->y1 > osd->height) osd->y1 = osd->height; if(osd->y2 > osd->height) osd->y2 = osd->height; if(osd->x1 < 0) osd->x1 = 0; if(osd->x2 < 0) osd->x2 = 0; if(osd->y1 < 0) osd->y1 = 0; if(osd->y2 < 0) osd->y2 = 0; #ifdef DEBUG_RLE lprintf("osd_show %p rle starts\n", osd); #endif /* check if osd is valid (something drawn on it) */ if( osd->x2 > osd->x1 && osd->y2 > osd->y1 ) { this->event.object.handle = osd->handle; memset( this->event.object.overlay, 0, sizeof(*this->event.object.overlay) ); this->event.object.overlay->unscaled = unscaled; this->event.object.overlay->x = osd->display_x + osd->x1; this->event.object.overlay->y = osd->display_y + osd->y1; this->event.object.overlay->width = osd->x2 - osd->x1; this->event.object.overlay->height = osd->y2 - osd->y1; this->event.object.overlay->hili_top = 0; this->event.object.overlay->hili_bottom = this->event.object.overlay->height; this->event.object.overlay->hili_left = 0; this->event.object.overlay->hili_right = this->event.object.overlay->width; /* there will be at least that many rle objects (one for each row) */ required = osd->y2 - osd->y1; this->event.object.overlay->num_rle = 0; this->event.object.overlay->data_size = 1024; while (required > this->event.object.overlay->data_size) this->event.object.overlay->data_size += 1024; rle_p = this->event.object.overlay->rle = malloc(this->event.object.overlay->data_size * sizeof(rle_elem_t) ); for( y = osd->y1; y < osd->y2; y++ ) { #ifdef DEBUG_RLE lprintf("osd_show %p y = %d: ", osd, y); #endif c = osd->area + y * osd->width + osd->x1; /* initialize a rle object with the first pixel's color */ rle.len = 1; rle.color = *c++; /* loop over the remaining pixels in the row */ for( x = osd->x1 + rle.len; x < osd->x2; x++, c++ ) { if( rle.color != *c ) { if( (this->event.object.overlay->num_rle + required) > this->event.object.overlay->data_size ) { this->event.object.overlay->data_size += 1024; rle_p = this->event.object.overlay->rle = realloc( this->event.object.overlay->rle, this->event.object.overlay->data_size * sizeof(rle_elem_t) ); rle_p += this->event.object.overlay->num_rle; } #ifdef DEBUG_RLE lprintf("(%d, %d), ", rle.len, rle.color); #endif *rle_p++ = rle; this->event.object.overlay->num_rle++; rle.color = *c; rle.len = 1; } else { rle.len++; } } #ifdef DEBUG_RLE lprintf("(%d, %d)\n", rle.len, rle.color); #endif *rle_p++ = rle; this->event.object.overlay->num_rle++; /* another row done */ required--; } #ifdef DEBUG_RLE lprintf("osd_show %p rle ends\n", osd); #endif lprintf("num_rle = %d\n", this->event.object.overlay->num_rle); memcpy(this->event.object.overlay->hili_color, osd->color, sizeof(osd->color)); memcpy(this->event.object.overlay->hili_trans, osd->trans, sizeof(osd->trans)); memcpy(this->event.object.overlay->color, osd->color, sizeof(osd->color)); memcpy(this->event.object.overlay->trans, osd->trans, sizeof(osd->trans)); this->event.event_type = OVERLAY_EVENT_SHOW; this->event.vpts = vpts; ovl_manager->add_event(ovl_manager, (void *)&this->event); } else { /* osd empty - hide it */ _osd_hide(osd, vpts); } pthread_mutex_unlock (&this->osd_mutex); this->stream->xine->port_ticket->release(this->stream->xine->port_ticket, 1); return 1; } /* normal OSD show * overlay is blended and scaled together with the stream. */ static int osd_show_scaled (osd_object_t *osd, int64_t vpts) { return _osd_show(osd, vpts, 0); } /* unscaled OSD show * overlay is blended at output (screen) resolution. */ static int osd_show_unscaled (osd_object_t *osd, int64_t vpts) { return _osd_show(osd, vpts, 1); } /* * send event to hide osd at given pts (0=now) * the object is not changed. there may be subsequent drawing on it. */ static int _osd_hide (osd_object_t *osd, int64_t vpts) { osd_renderer_t *this = osd->renderer; video_overlay_manager_t *ovl_manager; lprintf("osd=%p vpts=%"PRId64"\n",osd, vpts); if( osd->handle < 0 ) return 0; this->event.object.handle = osd->handle; /* not really needed this, but good pratice to clean it up */ memset( this->event.object.overlay, 0, sizeof(this->event.object.overlay) ); this->event.event_type = OVERLAY_EVENT_HIDE; this->event.vpts = vpts; ovl_manager = this->stream->video_out->get_overlay_manager(this->stream->video_out); ovl_manager->add_event(ovl_manager, (void *)&this->event); return 1; } static int osd_hide (osd_object_t *osd, int64_t vpts) { osd_renderer_t *this = osd->renderer; int ret; this->stream->xine->port_ticket->acquire(this->stream->xine->port_ticket, 1); pthread_mutex_lock (&this->osd_mutex); ret = _osd_hide(osd, vpts); pthread_mutex_unlock (&this->osd_mutex); this->stream->xine->port_ticket->release(this->stream->xine->port_ticket, 1); return 1; } /* * clear an osd object, so that it can be used for rendering a new image */ static void osd_clear (osd_object_t *osd) { lprintf("osd=%p\n",osd); memset(osd->area, 0, osd->width * osd->height); osd->x1 = osd->width; osd->y1 = osd->height; osd->x2 = 0; osd->y2 = 0; } /* * Draw a point. */ static void osd_point (osd_object_t *osd, int x, int y, int color) { uint8_t *c; lprintf("osd=%p (%d x %d)\n", osd, x, y); if (x < 0 || x >= osd->width) return; if (y < 0 || y >= osd->height) return; /* update clipping area */ osd->x1 = MIN(osd->x1, x); osd->x2 = MAX(osd->x2, (x + 1)); osd->y1 = MIN(osd->y1, y); osd->y2 = MAX(osd->y2, (y + 1)); c = osd->area + y * osd->width + x; *c = color; } /* * Bresenham line implementation on osd object */ static void osd_line (osd_object_t *osd, int x1, int y1, int x2, int y2, int color) { uint8_t *c; int dx, dy, t, inc, d, inc1, inc2; int swap_x = 0; int swap_y = 0; lprintf("osd=%p (%d,%d)-(%d,%d)\n",osd, x1,y1, x2,y2 ); /* sort line */ if (x2 < x1) { t = x1; x1 = x2; x2 = t; swap_x = 1; } if (y2 < y1) { t = y1; y1 = y2; y2 = t; swap_y = 1; } /* clip line */ if (x1 < 0) { y1 = y1 + (y2-y1) * -x1 / (x2-x1); x1 = 0; } if (y1 < 0) { x1 = x1 + (x2-x1) * -y1 / (y2-y1); y1 = 0; } if (x2 > osd->width) { y2 = y1 + (y2-y1) * (osd->width-x1) / (x2-x1); x2 = osd->width; } if (y2 > osd->height) { x2 = x1 + (x2-x1) * (osd->height-y1) / (y2-y1); y2 = osd->height; } if (x1 >= osd->width || y1 >= osd->height) return; /* update clipping area */ osd->x1 = MIN( osd->x1, x1 ); osd->x2 = MAX( osd->x2, x2 ); osd->y1 = MIN( osd->y1, y1 ); osd->y2 = MAX( osd->y2, y2 ); dx = x2 - x1; dy = y2 - y1; /* unsort line */ if (swap_x) { t = x1; x1 = x2; x2 = t; } if (swap_y) { t = y1; y1 = y2; y2 = t; } if( dx>=dy ) { if( x1>x2 ) { t = x2; x2 = x1; x1 = t; t = y2; y2 = y1; y1 = t; } if( y2 > y1 ) inc = 1; else inc = -1; inc1 = 2*dy; d = inc1 - dx; inc2 = 2*(dy-dx); c = osd->area + y1 * osd->width + x1; while(x1area + y1 * osd->width + x1; } } } else { if( y1>y2 ) { t = x2; x2 = x1; x1 = t; t = y2; y2 = y1; y1 = t; } if( x2 > x1 ) inc = 1; else inc = -1; inc1 = 2*dx; d = inc1-dy; inc2 = 2*(dx-dy); c = osd->area + y1 * osd->width + x1; while(y1width; y1++; if( d<0 ) { d+=inc1; } else { x1+=inc; d+=inc2; c = osd->area + y1 * osd->width + x1; } } } } /* * filled retangle */ static void osd_filled_rect (osd_object_t *osd, int x1, int y1, int x2, int y2, int color) { int x, y, dx, dy; lprintf("osd=%p (%d,%d)-(%d,%d)\n",osd, x1,y1, x2,y2 ); /* sort rectangle */ x = MIN( x1, x2 ); dx = MAX( x1, x2 ); y = MIN( y1, y2 ); dy = MAX( y1, y2 ); /* clip rectangle */ if (x >= osd->width || y >= osd->height) return; if (x < 0) { dx += x; x = 0; } if (y < 0) { dy += y; y = 0; } dx = MIN( dx, osd->width ); dy = MIN( dy, osd->height ); /* update clipping area */ osd->x1 = MIN( osd->x1, x ); osd->x2 = MAX( osd->x2, dx ); osd->y1 = MIN( osd->y1, y ); osd->y2 = MAX( osd->y2, dy ); dx -= x; dy -= y; for( ; dy--; y++ ) { memset(osd->area + y * osd->width + x,color,dx); } } /* * set palette (color and transparency) */ static void osd_set_palette(osd_object_t *osd, const uint32_t *color, const uint8_t *trans ) { memcpy(osd->color, color, sizeof(osd->color)); memcpy(osd->trans, trans, sizeof(osd->trans)); } /* * set on existing text palette * (-1 to set user specified palette) */ static void osd_set_text_palette(osd_object_t *osd, int palette_number, int color_base) { if( palette_number < 0 ) palette_number = osd->renderer->textpalette; /* some sanity checks for the color indices */ if( color_base < 0 ) color_base = 0; else if( color_base > OVL_PALETTE_SIZE - TEXT_PALETTE_SIZE ) color_base = OVL_PALETTE_SIZE - TEXT_PALETTE_SIZE; memcpy(&osd->color[color_base], textpalettes_color[palette_number], sizeof(textpalettes_color[palette_number])); memcpy(&osd->trans[color_base], textpalettes_trans[palette_number], sizeof(textpalettes_trans[palette_number])); } /* * get palette (color and transparency) */ static void osd_get_palette (osd_object_t *osd, uint32_t *color, uint8_t *trans) { memcpy(color, osd->color, sizeof(osd->color)); memcpy(trans, osd->trans, sizeof(osd->trans)); } /* * set position were overlay will be blended */ static void osd_set_position (osd_object_t *osd, int x, int y) { if( x < 0 || x > 0x10000 ) x = 0; if( y < 0 || y > 0x10000 ) y = 0; osd->display_x = x; osd->display_y = y; } static uint16_t gzread_i16(gzFile *fp) { uint16_t ret; ret = gzgetc(fp); ret |= (gzgetc(fp)<<8); return ret; } /* load bitmap font into osd engine */ static int osd_renderer_load_font(osd_renderer_t *this, char *filename) { gzFile *fp; osd_font_t *font = NULL; int i, ret = 0; lprintf("name=%s\n", filename ); /* load quick & dirt font format */ /* fixme: check for all read errors... */ if( (fp = gzopen(filename,"rb")) != NULL ) { font = xine_xmalloc( sizeof(osd_font_t) ); gzread(fp, font->name, sizeof(font->name) ); font->version = gzread_i16(fp); if( font->version == FONT_VERSION ) { font->size = gzread_i16(fp); font->num_fontchars = gzread_i16(fp); font->loaded = 1; font->fontchar = malloc( sizeof(osd_fontchar_t) * font->num_fontchars ); lprintf("font '%s' chars=%d\n", font->name, font->num_fontchars); /* load all characters */ for( i = 0; i < font->num_fontchars; i++ ) { font->fontchar[i].code = gzread_i16(fp); font->fontchar[i].width = gzread_i16(fp); font->fontchar[i].height = gzread_i16(fp); font->fontchar[i].bmp = malloc(font->fontchar[i].width*font->fontchar[i].height); if( gzread(fp, font->fontchar[i].bmp, font->fontchar[i].width*font->fontchar[i].height) <= 0 ) break; } /* check if all expected characters were loaded */ if( i == font->num_fontchars ) { osd_font_t *known_font; ret = 1; lprintf("font '%s' loaded\n",font->name); /* check if font is already known to us */ known_font = this->fonts; while( known_font ) { if( !strcasecmp(known_font->name,font->name) && known_font->size == font->size ) break; known_font = known_font->next; } if( !known_font ) { /* new font, add it to list */ font->filename = strdup(filename); font->next = this->fonts; this->fonts = font; } else { if( !known_font->loaded ) { /* the font was preloaded before. * add loaded characters to the existing entry. */ known_font->version = font->version; known_font->num_fontchars = font->num_fontchars; known_font->loaded = 1; known_font->fontchar = font->fontchar; free(font); } else { xprintf(this->stream->xine, XINE_VERBOSITY_LOG, _("font '%s-%d' already loaded, weird.\n"), font->name, font->size); while( --i >= 0 ) { free(font->fontchar[i].bmp); } free(font->fontchar); free(font); } } } else { xprintf(this->stream->xine, XINE_VERBOSITY_LOG, _("font '%s' loading failed (%d < %d)\n") ,font->name, i, font->num_fontchars); while( --i >= 0 ) { free(font->fontchar[i].bmp); } free(font->fontchar); free(font); } } else { xprintf(this->stream->xine, XINE_VERBOSITY_LOG, _("wrong version for font '%s'. expected %d found %d.\n"), font->name, font->version, FONT_VERSION); free(font); } gzclose(fp); } return ret; } /* * unload font */ static int osd_renderer_unload_font(osd_renderer_t *this, char *fontname ) { osd_font_t *font, *last; osd_object_t *osd; int i, ret = 0; lprintf("font '%s'\n", fontname); pthread_mutex_lock (&this->osd_mutex); osd = this->osds; while( osd ) { if( !strcasecmp(osd->font->name, fontname) ) osd->font = NULL; osd = osd->next; } last = NULL; font = this->fonts; while( font ) { if ( !strcasecmp(font->name,fontname) ) { free( font->filename ); if( font->loaded ) { for( i = 0; i < font->num_fontchars; i++ ) { free( font->fontchar[i].bmp ); } free( font->fontchar ); } if( last ) last->next = font->next; else this->fonts = font->next; free( font ); ret = 1; break; } last = font; font = font->next; } pthread_mutex_unlock (&this->osd_mutex); return ret; } #ifdef HAVE_FT2 static int osd_set_font_freetype2( osd_object_t *osd, const char *fontname, int size ) { if (!osd->ft2) { osd->ft2 = xine_xmalloc(sizeof(osd_ft2context_t)); if(FT_Init_FreeType( &osd->ft2->library )) { xprintf(osd->renderer->stream->xine, XINE_VERBOSITY_LOG, _("osd: cannot initialize ft2 library\n")); free(osd->ft2); osd->ft2 = NULL; return 0; } } #ifdef HAVE_FONTCONFIG do { FcPattern *pat = NULL, *match = NULL; FcFontSet *fs = FcFontSetCreate(); FcResult result; pat = FcPatternBuild(NULL, FC_FAMILY, FcTypeString, fontname, FC_SIZE, FcTypeDouble, (double)size, NULL); FcConfigSubstitute(NULL, pat, FcMatchPattern); FcDefaultSubstitute(pat); match = FcFontMatch(NULL, pat, &result); FcPatternDestroy(pat); if ( ! match ) { FcFontSetDestroy(fs); xprintf(osd->renderer->stream->xine, XINE_VERBOSITY_LOG, _("osd: error matching font %s with FontConfig"), fontname); break; } FcFontSetAdd(fs, match); if ( fs->nfont != 0 ) { FcChar8 *filename = NULL; FcPatternGetString(fs->fonts[0], FC_FILE, 0, &filename); if ( ! FT_New_Face(osd->ft2->library, (const char*)filename, 0, &osd->ft2->face) ) { FcFontSetDestroy(fs); goto end; } xprintf(osd->renderer->stream->xine, XINE_VERBOSITY_LOG, _("osd: error loading font %s with FontConfig"), fontname); } else { xprintf(osd->renderer->stream->xine, XINE_VERBOSITY_LOG, _("osd: error looking up font %s with FontConfig"), fontname); } } while(0); #endif { char pathname[1024]; /* try load font from current directory */ if ( !FT_New_Face(osd->ft2->library, fontname, 0, &osd->ft2->face) ) goto end; /* try load font from home directory */ snprintf(pathname, 1024, "%s/.xine/fonts/%s", xine_get_homedir(), fontname); if ( !FT_New_Face(osd->ft2->library, pathname, 0, &osd->ft2->face) ) goto end; /* try load font from xine font directory */ snprintf(pathname, 1024, "%s/%s", XINE_FONTDIR, fontname); if ( !FT_New_Face(osd->ft2->library, pathname, 0, &osd->ft2->face) ) goto end; xprintf(osd->renderer->stream->xine, XINE_VERBOSITY_LOG, _("osd: error loading font %s with ft2\n"), fontname); } free(osd->ft2); osd->ft2 = NULL; return 0; end: if (FT_Set_Pixel_Sizes(osd->ft2->face, 0, size)) { xprintf(osd->renderer->stream->xine, XINE_VERBOSITY_LOG, _("osd: error setting font size (no scalable font?)\n")); free(osd->ft2); osd->ft2 = NULL; return 0; } osd->ft2->size = size; return 1; } #endif /* set the font of osd object */ static int osd_set_font( osd_object_t *osd, const char *fontname, int size) { int ret = 1; lprintf("osd=%p font '%s'\n", osd, fontname); pthread_mutex_lock (&osd->renderer->osd_mutex); #ifdef HAVE_FT2 if ( ! osd_set_font_freetype2(osd, fontname, size) ) #endif { /* If the FreeType2 loading failed */ osd_font_t *font; int best = 0; osd->font = NULL; ret = 0; font = osd->renderer->fonts; while( font ) { if( !strcasecmp(font->name, fontname) && (size>=font->size) && (bestsize)) { ret = 1; osd->font = font; best = font->size; lprintf ("best: font->name=%s, size=%d\n", font->name, font->size); } font = font->next; } if( ret ) { /* load font if needed */ if( !osd->font->loaded ) ret = osd_renderer_load_font(osd->renderer, osd->font->filename); if(!ret) osd->font = NULL; } } pthread_mutex_unlock (&osd->renderer->osd_mutex); return ret; } /* * search the character in the sorted array, * * returns ALIAS_CHARACTER_FONT if character 'code' isn't found, * returns 'n' on error */ static int osd_search(osd_fontchar_t *array, size_t n, uint16_t code) { #ifdef BINARY_SEARCH size_t i, left, right; if (!n) return 0; left = 0; right = n - 1; while (left < right) { i = (left + right) >> 1; if (code <= array[i].code) right = i; else left = i + 1; } if (array[right].code == code) return right; else return ALIAS_CHARACTER_FONT < n ? ALIAS_CHARACTER_FONT : n; #else size_t i; for( i = 0; i < n; i++ ) { if( font->fontchar[i].code == unicode ) break; } if (i < n) return i; else return ALIAS_CHARACTER_FONT < n ? ALIAS_CHARACTER_FONT : n; #endif } #ifdef HAVE_ICONV /* * get next unicode value */ static uint16_t osd_iconv_getunicode(xine_t *xine, iconv_t *cd, const char *encoding, ICONV_CONST char **inbuf, size_t *inbytesleft) { uint16_t unicode; char *outbuf = (char*)&unicode; size_t outbytesleft = 2; size_t count; if (cd != (iconv_t)-1) { /* get unicode value from iconv */ count = iconv(cd, inbuf, inbytesleft, &outbuf, &outbytesleft); if (count == (size_t)-1 && errno != E2BIG) { /* unknown character or character wider than 16 bits, try skip one byte */ xprintf(xine, XINE_VERBOSITY_LOG, _("osd: unknown sequence starting with byte 0x%02X in encoding \"%s\", skipping\n"), (*inbuf)[0] & 0xFF, encoding); if (*inbytesleft) { (*inbytesleft)--; (*inbuf)++; } return ALIAS_CHARACTER_CONV; } } else { /* direct mapping without iconv */ unicode = (unsigned char)(*inbuf)[0]; (*inbuf)++; (*inbytesleft)--; } return unicode; } #endif /* * free iconv encoding */ static void osd_free_encoding(osd_object_t *osd) { #ifdef HAVE_ICONV if (osd->cd != (iconv_t)-1) { iconv_close(osd->cd); osd->cd = (iconv_t)-1; } if (osd->encoding) { free(osd->encoding); osd->encoding = NULL; } #endif } /* * set encoding of text * * NULL ... no conversion (iso-8859-1) * "" ... locale encoding */ static int osd_set_encoding (osd_object_t *osd, const char *encoding) { #ifdef HAVE_ICONV char *enc; osd_free_encoding(osd); lprintf("osd=%p, encoding=%s\n", osd, encoding ? (encoding[0] ? encoding : "locale") : "no conversion"); /* no conversion, use latin1 */ if (!encoding) return 1; /* get encoding from system */ if (!encoding[0]) { if ((enc = xine_get_system_encoding()) == NULL) { xprintf(osd->renderer->stream->xine, XINE_VERBOSITY_LOG, _("osd: can't find out current locale character set\n")); return 0; } lprintf("locale encoding='%s'\n", enc); } else enc = strdup(encoding); /* prepare conversion to UCS-2 */ if ((osd->cd = iconv_open(UCS2_ENCODING, enc)) == (iconv_t)-1) { xprintf(osd->renderer->stream->xine, XINE_VERBOSITY_LOG, _("osd: unsupported conversion %s -> %s, no conversion performed\n"), enc, UCS2_ENCODING); free(enc); return 0; } osd->encoding = enc; return 1; #else return encoding == NULL; #endif /* HAVE_ICONV */ } #define FONT_OVERLAP 1/10 /* overlap between consecutive characters */ /* * render text in current encoding on x,y position * no \n yet */ static int osd_render_text (osd_object_t *osd, int x1, int y1, const char *text, int color_base) { osd_renderer_t *this = osd->renderer; osd_font_t *font; int i, y; uint8_t *dst, *src; const char *inbuf; uint16_t unicode; size_t inbytesleft; #ifdef HAVE_FT2 FT_UInt previous = 0; FT_Bool use_kerning = osd->ft2 && FT_HAS_KERNING(osd->ft2->face); int first = 1; #endif lprintf("osd=%p (%d,%d) \"%s\"\n", osd, x1, y1, text); /* some sanity checks for the color indices */ if( color_base < 0 ) color_base = 0; else if( color_base > OVL_PALETTE_SIZE - TEXT_PALETTE_SIZE ) color_base = OVL_PALETTE_SIZE - TEXT_PALETTE_SIZE; pthread_mutex_lock (&this->osd_mutex); { int proceed = 0; if ((font = osd->font)) proceed = 1; #ifdef HAVE_FT2 if (osd->ft2) proceed = 1; #endif if (proceed == 0) { xprintf(this->stream->xine, XINE_VERBOSITY_LOG, _("osd: font isn't defined\n")); pthread_mutex_unlock(&this->osd_mutex); return 0; } } if( x1 < osd->x1 ) osd->x1 = x1; if( y1 < osd->y1 ) osd->y1 = y1; inbuf = text; inbytesleft = strlen(text); while( inbytesleft ) { #ifdef HAVE_ICONV unicode = osd_iconv_getunicode(this->stream->xine, osd->cd, osd->encoding, (ICONV_CONST char **)&inbuf, &inbytesleft); #else unicode = inbuf[0]; inbuf++; inbytesleft--; #endif #ifdef HAVE_FT2 if (osd->ft2) { FT_GlyphSlot slot = osd->ft2->face->glyph; i = FT_Get_Char_Index( osd->ft2->face, unicode ); /* add kerning relative to the previous letter */ if (use_kerning && previous && i) { FT_Vector delta; FT_Get_Kerning(osd->ft2->face, previous, i, KERNING_DEFAULT, &delta); x1 += delta.x / 64; } previous = i; if (FT_Load_Glyph(osd->ft2->face, i, FT_LOAD_FLAGS)) { xprintf(this->stream->xine, XINE_VERBOSITY_LOG, _("osd: error loading glyph\n")); continue; } if (slot->format != ft_glyph_format_bitmap) { if (FT_Render_Glyph(slot, ft_render_mode_normal)) xprintf(this->stream->xine, XINE_VERBOSITY_LOG, _("osd: error in rendering glyph\n")); } /* if the first letter has a bearing not on the basepoint shift, the * whole output to be sure that we are inside the bounding box */ if (first) x1 -= slot->bitmap_left; first = 0; /* we shift the whole glyph down by it's ascender so that the specified * coordinate is the top left corner which is much more practical than * the baseline as the user normally has no idea where the baseline is */ dst = osd->area + (y1 + osd->ft2->face->size->metrics.ascender/64 - slot->bitmap_top) * osd->width; src = (uint8_t*) slot->bitmap.buffer; for (y = 0; y < slot->bitmap.rows; y++) { uint8_t *s = src; uint8_t *d = dst + x1 + slot->bitmap_left; if (d >= osd->area + osd->width*osd->height) break; if (dst > osd->area) while (s < src + slot->bitmap.width) { if ((d >= dst) && (d < dst + osd->width) && *s) *d = (uint8_t)(*s/25) + (uint8_t) color_base; d++; s++; } src += slot->bitmap.pitch; dst += osd->width; } x1 += slot->advance.x / 64; if( x1 > osd->x2 ) osd->x2 = x1; if( y1 + osd->ft2->face->size->metrics.height/64 > osd->y2 ) osd->y2 = y1 + osd->ft2->face->size->metrics.height/64; } else { #endif i = osd_search(font->fontchar, font->num_fontchars, unicode); lprintf("font '%s' [%d, U+%04X == U+%04X] %dx%d -> %d,%d\n", font->name, i, unicode, font->fontchar[i].code, font->fontchar[i].width, font->fontchar[i].height, x1, y1); if ( i != font->num_fontchars ) { dst = osd->area + y1 * osd->width; src = font->fontchar[i].bmp; for( y = 0; y < font->fontchar[i].height; y++ ) { uint8_t *s = src; uint8_t *d = dst + x1; if (d >= osd->area + osd->width*osd->height) break; if (dst >= osd->area) while (s < src + font->fontchar[i].width) { if((d >= dst) && (d < dst + osd->width) && (*s > 1)) /* skip drawing transparency */ *d = *s + (uint8_t) color_base; d++; s++; } src += font->fontchar[i].width; dst += osd->width; } x1 += font->fontchar[i].width - (font->fontchar[i].width * FONT_OVERLAP); if( x1 > osd->x2 ) osd->x2 = x1; if( y1 + font->fontchar[i].height > osd->y2 ) osd->y2 = y1 + font->fontchar[i].height; } #ifdef HAVE_FT2 } /* !(osd->ft2) */ #endif } pthread_mutex_unlock (&this->osd_mutex); return 1; } /* get width and height of how text will be renderized */ static int osd_get_text_size(osd_object_t *osd, const char *text, int *width, int *height) { osd_renderer_t *this = osd->renderer; osd_font_t *font; int i; const char *inbuf; uint16_t unicode; size_t inbytesleft; #ifdef HAVE_FT2 /* not all free type fonts provide kerning */ FT_Bool use_kerning = osd->ft2 && FT_HAS_KERNING(osd->ft2->face); FT_UInt previous = 0; int first_glyph = 1; #endif lprintf("osd=%p \"%s\"\n", osd, text); pthread_mutex_lock (&this->osd_mutex); { int proceed = 0; if ((font = osd->font)) proceed = 1; #ifdef HAVE_FT2 if (osd->ft2) proceed = 1; #endif if (proceed == 0) { xprintf(this->stream->xine, XINE_VERBOSITY_LOG, _("osd: font isn't defined\n")); pthread_mutex_unlock(&this->osd_mutex); return 0; } } *width = 0; *height = 0; inbuf = text; inbytesleft = strlen(text); while( inbytesleft ) { #ifdef HAVE_ICONV unicode = osd_iconv_getunicode(this->stream->xine, osd->cd, osd->encoding, (ICONV_CONST char **)&inbuf, &inbytesleft); #else unicode = inbuf[0]; inbuf++; inbytesleft--; #endif #ifdef HAVE_FT2 if (osd->ft2) { FT_GlyphSlot slot = osd->ft2->face->glyph; i = FT_Get_Char_Index( osd->ft2->face, unicode); /* kerning add the relative to the previous letter */ if (use_kerning && previous && i) { FT_Vector delta; FT_Get_Kerning(osd->ft2->face, previous, i, KERNING_DEFAULT, &delta); *width += delta.x / 64; } previous = i; if (FT_Load_Glyph(osd->ft2->face, i, FT_LOAD_DEFAULT | FT_LOAD_NO_HINTING)) { xprintf(this->stream->xine, XINE_VERBOSITY_LOG, _("osd: error loading glyph %i\n"), i); text++; continue; } if (slot->format != ft_glyph_format_bitmap) { if (FT_Render_Glyph(osd->ft2->face->glyph, ft_render_mode_normal)) xprintf(this->stream->xine, XINE_VERBOSITY_LOG, _("osd: error in rendering\n")); } /* left shows the left edge relative to the base point. A positive value means the * letter is shifted right, so we need to subtract the value from the width */ if (first_glyph) *width -= slot->bitmap_left; first_glyph = 0; *width += slot->advance.x / 64; text++; } else { #endif i = osd_search(font->fontchar, font->num_fontchars, unicode); if ( i != font->num_fontchars ) { if( font->fontchar[i].height > *height ) *height = font->fontchar[i].height; *width += font->fontchar[i].width - (font->fontchar[i].width * FONT_OVERLAP); } #ifdef HAVE_FT2 } /* !(osd->ft2) */ #endif } #ifdef HAVE_FT2 if (osd->ft2) { /* if we have a true type font we need to do some corrections for the last * letter. As this one is still in the gylph slot we can still work with * it. For the last letter be must not use advance and width but the real * width of the bitmap. We're right from the base point so we subtract the * advance value that was added in the for-loop and add the width. We have * to also add the left bearing because the letter might be shifted left or * right and then the right edge is also shifted */ if (osd->ft2->face->glyph->bitmap.width) *width -= osd->ft2->face->glyph->advance.x / 64; *width += osd->ft2->face->glyph->bitmap.width; *width += osd->ft2->face->glyph->bitmap_left; *height = osd->ft2->face->size->metrics.height / 64; } #endif pthread_mutex_unlock (&this->osd_mutex); return 1; } static void osd_preload_fonts (osd_renderer_t *this, char *path) { DIR *dir; char *s, *p; lprintf ("path='%s'\n", path); dir = opendir (path); if (dir) { struct dirent *entry; while ((entry = readdir (dir)) != NULL) { int len; len = strlen (entry->d_name); if ( (len > 12) && !strncmp (&entry->d_name[len-12], ".xinefont.gz", 12)) { s = strdup(entry->d_name); p = strchr(s, '-'); if( p ) { osd_font_t *font; char *pathname; *p++ = '\0'; font = xine_xmalloc( sizeof(osd_font_t) ); strncpy(font->name, s, sizeof(font->name)); font->size = atoi(p); lprintf("font '%s' size %d is preloaded\n", font->name, font->size); pathname = (char *) xine_xmalloc(strlen(path) + strlen(entry->d_name) + 2); sprintf (pathname, "%s/%s", path, entry->d_name); font->filename = pathname; font->next = this->fonts; this->fonts = font; } free(s); } } closedir (dir); } } /* * free osd object */ static void osd_free_object (osd_object_t *osd_to_close) { osd_renderer_t *this = osd_to_close->renderer; video_overlay_manager_t *ovl_manager; osd_object_t *osd, *last; if( osd_to_close->handle >= 0 ) { osd_hide(osd_to_close,0); this->event.object.handle = osd_to_close->handle; /* not really needed this, but good pratice to clean it up */ memset( this->event.object.overlay, 0, sizeof(this->event.object.overlay) ); this->event.event_type = OVERLAY_EVENT_FREE_HANDLE; this->event.vpts = 0; this->stream->xine->port_ticket->acquire(this->stream->xine->port_ticket, 1); ovl_manager = this->stream->video_out->get_overlay_manager(this->stream->video_out); ovl_manager->add_event(ovl_manager, (void *)&this->event); this->stream->xine->port_ticket->release(this->stream->xine->port_ticket, 1); osd_to_close->handle = -1; /* handle will be freed */ } pthread_mutex_lock (&this->osd_mutex); last = NULL; osd = this->osds; while( osd ) { if ( osd == osd_to_close ) { free( osd->area ); #ifdef HAVE_FT2 if( osd->ft2 ) { if ( osd->ft2->library ) FT_Done_FreeType(osd->ft2->library); free( osd->ft2 ); } #endif osd_free_encoding(osd); if( last ) last->next = osd->next; else this->osds = osd->next; free( osd ); break; } last = osd; osd = osd->next; } pthread_mutex_unlock (&this->osd_mutex); } static void osd_renderer_close (osd_renderer_t *this) { while( this->osds ) osd_free_object ( this->osds ); while( this->fonts ) osd_renderer_unload_font( this, this->fonts->name ); pthread_mutex_destroy (&this->osd_mutex); free(this->event.object.overlay); free(this); } static void update_text_palette(void *this_gen, xine_cfg_entry_t *entry) { osd_renderer_t *this = (osd_renderer_t *)this_gen; this->textpalette = entry->num_value; lprintf("palette will be '%s'\n", textpalettes_str[this->textpalette] ); } static void osd_draw_bitmap(osd_object_t *osd, uint8_t *bitmap, int x1, int y1, int width, int height, uint8_t *palette_map) { int y, x; lprintf("osd=%p at (%d,%d) %dx%d\n",osd, x1,y1, width,height ); /* update clipping area */ osd->x1 = MIN( osd->x1, x1 ); osd->x2 = MAX( osd->x2, x1+width ); osd->y1 = MIN( osd->y1, y1 ); osd->y2 = MAX( osd->y2, y1+height ); for( y=0; ywidth + x1; /* Slow copy with palette translation, the map describes how to convert color indexes in the source bitmap to indexes in the osd palette */ for ( x=0; xarea[dst_offset+x] = palette_map[bitmap[src_offset+x]]; } } else { /* Fast copy with direct mapping */ memcpy(osd->area + (y1+y) * osd->width + x1, bitmap + y * width, width); } } } static uint32_t osd_get_capabilities (osd_object_t *osd) { osd_renderer_t *this = osd->renderer; uint32_t capabilities = 0; #ifdef HAVE_FT2 capabilities |= XINE_OSD_CAP_FREETYPE2; #endif this->stream->xine->port_ticket->acquire(this->stream->xine->port_ticket, 1); if( this->stream->video_out->get_capabilities(this->stream->video_out) & VO_CAP_UNSCALED_OVERLAY) capabilities |= XINE_OSD_CAP_UNSCALED; this->stream->xine->port_ticket->release(this->stream->xine->port_ticket, 1); return capabilities; } /* * initialize the osd rendering engine */ osd_renderer_t *_x_osd_renderer_init( xine_stream_t *stream ) { osd_renderer_t *this; char str[1024]; this = xine_xmalloc(sizeof(osd_renderer_t)); this->stream = stream; this->event.object.overlay = xine_xmalloc( sizeof(vo_overlay_t) ); pthread_mutex_init (&this->osd_mutex, NULL); /* * load available fonts */ osd_preload_fonts (this, XINE_FONTDIR); snprintf (str, 1024, "%s/.xine/fonts", xine_get_homedir ()); osd_preload_fonts (this, str); this->textpalette = this->stream->xine->config->register_enum (this->stream->xine->config, "ui.osd.text_palette", 0, textpalettes_str, _("palette (foreground-border-background) to use for subtitles and OSD"), _("The palette for on-screen-display and some subtitle formats that do " "not specify any colouring themselves. The palettes are listed in the " "form: foreground-border-background."), 10, update_text_palette, this); /* * set up function pointer */ this->new_object = osd_new_object; this->free_object = osd_free_object; this->show = osd_show_scaled; this->hide = osd_hide; this->set_palette = osd_set_palette; this->set_text_palette = osd_set_text_palette; this->get_palette = osd_get_palette; this->set_position = osd_set_position; this->set_font = osd_set_font; this->clear = osd_clear; this->point = osd_point; this->line = osd_line; this->filled_rect = osd_filled_rect; this->set_encoding = osd_set_encoding; this->render_text = osd_render_text; this->get_text_size = osd_get_text_size; this->close = osd_renderer_close; this->draw_bitmap = osd_draw_bitmap; this->show_unscaled = osd_show_unscaled; this->get_capabilities = osd_get_capabilities; return this; }