/* * libzvbi - Closed Caption and Teletext rendering * * Copyright (C) 2000, 2001, 2002 Michael H. Schimek * * Based on code from AleVT 1.5.1 * Copyright (C) 1998, 1999 Edgar Toernig * Copyright (C) 1999 Paul Ortyl * * 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 2 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, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ /* $Id: exp-gfx.c,v 1.11 2006/02/10 06:25:37 mschimek Exp $ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include "lang.h" #include "export.h" #include "exp-gfx.h" #include "vt.h" /* VBI_TRANSPARENT_BLACK */ #include "wstfont2.xbm" #include "ccfont2.xbm" /* Teletext character cell dimensions - hardcoded (DRCS) */ #define TCW 12 #define TCH 10 #define TCPL (wstfont2_width / TCW * wstfont2_height / TCH) /* Closed Caption character cell dimensions */ #define CCW 16 #define CCH 26 /* line doubled */ #define CCPL (ccfont2_width / CCW * ccfont2_height / CCH) static void init_gfx(void) __attribute__ ((constructor)); static void init_gfx(void) { uint8_t *t, *p; int i, j; /* de-interleave font image (puts all chars in row 0) */ if (!(t = malloc(wstfont2_width * wstfont2_height / 8))) exit(EXIT_FAILURE); for (p = t, i = 0; i < TCH; i++) for (j = 0; j < wstfont2_height; p += wstfont2_width / 8, j += TCH) memcpy(p, wstfont2_bits + (j + i) * wstfont2_width / 8, wstfont2_width / 8); memcpy(wstfont2_bits, t, wstfont2_width * wstfont2_height / 8); free (t); if (!(t = malloc(ccfont2_width * ccfont2_height / 8))) exit(EXIT_FAILURE); for (p = t, i = 0; i < CCH; i++) for (j = 0; j < ccfont2_height; p += ccfont2_width / 8, j += CCH) memcpy(p, ccfont2_bits + (j + i) * ccfont2_width / 8, ccfont2_width / 8); memcpy(ccfont2_bits, t, ccfont2_width * ccfont2_height / 8); free(t); } /** * @internal * @param c Unicode. * @param italic @c TRUE to switch to slanted character set (doesn't affect * Hebrew and Arabic). If this is a G1 block graphic character * switch to separated block mosaic set. * * Translate Unicode character to glyph number in wstfont2 image. * * @return * Glyph number. */ static unsigned int unicode_wstfont2(unsigned int c, int italic) { static const unsigned short specials[] = { 0x01B5, 0x2016, 0x01CD, 0x01CE, 0x0229, 0x0251, 0x02DD, 0x02C6, 0x02C7, 0x02C9, 0x02CA, 0x02CB, 0x02CD, 0x02CF, 0x02D8, 0x02D9, 0x02DA, 0x02DB, 0x02DC, 0x2014, 0x2018, 0x2019, 0x201C, 0x201D, 0x20A0, 0x2030, 0x20AA, 0x2122, 0x2126, 0x215B, 0x215C, 0x215D, 0x215E, 0x2190, 0x2191, 0x2192, 0x2193, 0x25A0, 0x266A, 0xE800, 0xE75F }; const unsigned int invalid = 357; unsigned int i; if (c < 0x0180) { if (c < 0x0080) { if (c < 0x0020) return invalid; else /* %3 Basic Latin (ASCII) 0x0020 ... 0x007F */ c = c - 0x0020 + 0 * 32; } else if (c < 0x00A0) return invalid; else /* %3 Latin-1 Supplement, Latin Extended-A 0x00A0 ... 0x017F */ c = c - 0x00A0 + 3 * 32; } else if (c < 0xEE00) { if (c < 0x0460) { if (c < 0x03D0) { if (c < 0x0370) goto special; else /* %5 Greek 0x0370 ... 0x03CF */ c = c - 0x0370 + 12 * 32; } else if (c < 0x0400) return invalid; else /* %5 Cyrillic 0x0400 ... 0x045F */ c = c - 0x0400 + 15 * 32; } else if (c < 0x0620) { if (c < 0x05F0) { if (c < 0x05D0) return invalid; else /* %6 Hebrew 0x05D0 ... 0x05EF */ return c - 0x05D0 + 18 * 32; } else if (c < 0x0600) return invalid; else /* %6 Arabic 0x0600 ... 0x061F */ return c - 0x0600 + 19 * 32; } else if (c >= 0xE600 && c < 0xE740) return c - 0xE600 + 19 * 32; /* %6 Arabic (TTX) */ else goto special; } else if (c < 0xEF00) { /* %3 G1 Graphics */ return (c ^ 0x20) - 0xEE00 + 23 * 32; } else if (c < 0xF000) { /* %4 G3 Graphics */ return c - 0xEF20 + 27 * 32; } else /* 0xF000 ... 0xF7FF reserved for DRCS */ return invalid; if (italic) return c + 31 * 32; else return c; special: for (i = 0; i < sizeof(specials) / sizeof(specials[0]); i++) if (specials[i] == c) { if (italic) return i + 41 * 32; else return i + 10 * 32; } return invalid; } /** * @internal * @param c Unicode. * @param italic @c TRUE to switch to slanted character set. * * Translate Unicode character to glyph number in ccfont2 image. * * @return * Glyph number. */ static unsigned int unicode_ccfont2(unsigned int c, int italic) { static const unsigned short specials[] = { 0x00E1, 0x00E9, 0x00ED, 0x00F3, 0x00FA, 0x00E7, 0x00F7, 0x00D1, 0x00F1, 0x25A0, 0x00AE, 0x00B0, 0x00BD, 0x00BF, 0x2122, 0x00A2, 0x00A3, 0x266A, 0x00E0, 0x0020, 0x00E8, 0x00E2, 0x00EA, 0x00EE, 0x00F4, 0x00FB }; unsigned int i; if (c < 0x0020) c = 15; /* invalid */ else if (c < 0x0080) c = c; else { for (i = 0; i < sizeof(specials) / sizeof(specials[0]); i++) if (specials[i] == c) { c = i + 6; goto slant; } c = 15; /* invalid */ } slant: if (italic) c += 4 * 32; return c; } /** * @internal * @param p Plane of @a canvas_type char, short, int. * @param i Index. * * @return * Pixel @a i in plane @a p. */ #define peek(p, i) \ ((canvas_type == sizeof(uint8_t)) ? ((uint8_t *)(p))[i] : \ ((canvas_type == sizeof(uint16_t)) ? ((uint16_t *)(p))[i] : \ ((uint32_t *)(p))[i])) /** * @internal * @param p Plane of @a canvas_type char, short, int. * @param i Index. * @param v Value. * * Set pixel @a i in plane @a p to value @a v. */ #define poke(p, i, v) \ ((canvas_type == sizeof(uint8_t)) ? (((uint8_t *)(p))[i] = (v)) : \ ((canvas_type == sizeof(uint16_t)) ? (((uint16_t *)(p))[i] = (v)) : \ (((uint32_t *)(p))[i] = (v)))) /** * @internal * @param canvas_type sizeof(char, short, int). * @param canvas Pointer to image plane where the character is to be drawed. * @param rowstride @a canvas byte distance from line to line. * @param pen Pointer to color palette of @a canvas_type (index 0 background * pixels, index 1 foreground pixels). * @param font Pointer to font image with width @a cpl x @a cw pixels, height * @a ch pixels, depth one bit, bit '1' is foreground. * @param cpl Chars per line (number of characters in @a font image). * @param cw Character cell width in pixels. * @param ch Character cell height in pixels. * @param glyph Glyph number in font image, 0 ... @a cpl - 1. * @param bold Draw character bold (font image | font image << 1). * @param underline Bit mask of character rows. For each bit * 1 << (n = 0 ... @a ch - 1) set all of character row n to * foreground color. * @param size Size of character, either NORMAL, DOUBLE_WIDTH (draws left * and right half), DOUBLE_HEIGHT (draws upper half only), * DOUBLE_SIZE (left and right upper half), DOUBLE_HEIGHT2 * (lower half), DOUBLE_SIZE2 (left and right lower half). * * Draw one character (function template - define a static version with * constant @a canvas_type, @a font, @a cpl, @a cw, @a ch). */ static inline void draw_char(int canvas_type, uint8_t *canvas, int rowstride, uint8_t *pen, uint8_t *font, int cpl, int cw, int ch, int glyph, int bold, unsigned int underline, vbi_size size) { uint8_t *src; int shift, x, y; bold = !!bold; assert(cw >= 8 && cw <= 16); assert(ch >= 1 && cw <= 31); x = glyph * cw; shift = x & 7; src = font + (x >> 3); switch (size) { case VBI_DOUBLE_HEIGHT2: case VBI_DOUBLE_SIZE2: src += cpl * cw / 8 * ch / 2; underline >>= ch / 2; case VBI_DOUBLE_HEIGHT: case VBI_DOUBLE_SIZE: ch >>= 1; default: break; } for (y = 0; y < ch; underline >>= 1, y++) { int bits = ~0; if (!(underline & 1)) { #if #cpu (i386) bits = (*((uint16_t *) src) >> shift); #else /* unaligned/little endian */ bits = ((src[1] * 256 + src[0]) >> shift); #endif bits |= bits << bold; } switch (size) { case VBI_NORMAL_SIZE: for (x = 0; x < cw; bits >>= 1, x++) poke(canvas, x, peek(pen, bits & 1)); canvas += rowstride; break; case VBI_DOUBLE_HEIGHT: case VBI_DOUBLE_HEIGHT2: for (x = 0; x < cw; bits >>= 1, x++) { unsigned int col = peek(pen, bits & 1); poke(canvas, x, col); poke(canvas, x + rowstride / canvas_type, col); } canvas += rowstride * 2; break; case VBI_DOUBLE_WIDTH: for (x = 0; x < cw * 2; bits >>= 1, x += 2) { unsigned int col = peek(pen, bits & 1); poke(canvas, x + 0, col); poke(canvas, x + 1, col); } canvas += rowstride; break; case VBI_DOUBLE_SIZE: case VBI_DOUBLE_SIZE2: for (x = 0; x < cw * 2; bits >>= 1, x += 2) { unsigned int col = peek(pen, bits & 1); poke(canvas, x + 0, col); poke(canvas, x + 1, col); poke(canvas, x + rowstride / canvas_type + 0, col); poke(canvas, x + rowstride / canvas_type + 1, col); } canvas += rowstride * 2; break; default: break; } src += cpl * cw / 8; } } /** * @internal * @param canvas_type sizeof(char, short, int). * @param canvas Pointer to image plane where the character is to be drawed. * @param rowstride @a canvas byte distance from line to line. * @param pen Pointer to color palette of @a canvas_type (index 0 ... 1 for * depth 1 DRCS, 0 ... 3 for depth 2, 0 ... 15 for depth 4). * @param color Offset into color palette. * @param font Pointer to DRCS image. Each pixel is coded in four bits, an * index into the color palette, and stored in LE order (i. e. first * pixel 0x0F, second pixel 0xF0). Character size is 12 x 10 pixels, * 60 bytes, without padding. * @param glyph Glyph number in font image, 0x00 ... 0x3F. * @param size Size of character, either NORMAL, DOUBLE_WIDTH (draws left * and right half), DOUBLE_HEIGHT (draws upper half only), * DOUBLE_SIZE (left and right upper half), DOUBLE_HEIGHT2 * (lower half), DOUBLE_SIZE2 (left and right lower half). * * Draw one Teletext Dynamically Redefinable Character (function template - * define a static version with constant @a canvas_type, @a font). */ static inline void draw_drcs(int canvas_type, uint8_t *canvas, unsigned int rowstride, uint8_t *pen, int color, uint8_t *font, int glyph, vbi_size size) { uint8_t *src; unsigned int col; int x, y; src = font + glyph * 60; pen = pen + color * canvas_type; switch (size) { case VBI_NORMAL_SIZE: for (y = 0; y < TCH; canvas += rowstride, y++) for (x = 0; x < 12; src++, x += 2) { poke(canvas, x + 0, peek(pen, *src & 15)); poke(canvas, x + 1, peek(pen, *src >> 4)); } break; case VBI_DOUBLE_HEIGHT2: src += 30; case VBI_DOUBLE_HEIGHT: for (y = 0; y < TCH / 2; canvas += rowstride * 2, y++) for (x = 0; x < 12; src++, x += 2) { col = peek(pen, *src & 15); poke(canvas, x + 0, col); poke(canvas, x + rowstride / canvas_type + 0, col); col = peek(pen, *src >> 4); poke(canvas, x + 1, col); poke(canvas, x + rowstride / canvas_type + 1, col); } break; case VBI_DOUBLE_WIDTH: for (y = 0; y < TCH; canvas += rowstride, y++) for (x = 0; x < 12 * 2; src++, x += 4) { col = peek(pen, *src & 15); poke(canvas, x + 0, col); poke(canvas, x + 1, col); col = peek(pen, *src >> 4); poke(canvas, x + 2, col); poke(canvas, x + 3, col); } break; case VBI_DOUBLE_SIZE2: src += 30; case VBI_DOUBLE_SIZE: for (y = 0; y < TCH / 2; canvas += rowstride * 2, y++) for (x = 0; x < 12 * 2; src++, x += 4) { col = peek(pen, *src & 15); poke(canvas, x + 0, col); poke(canvas, x + 1, col); poke(canvas, x + rowstride / canvas_type + 0, col); poke(canvas, x + rowstride / canvas_type + 1, col); col = peek(pen, *src >> 4); poke(canvas, x + 2, col); poke(canvas, x + 3, col); poke(canvas, x + rowstride / canvas_type + 2, col); poke(canvas, x + rowstride / canvas_type + 3, col); } break; default: break; } } /** * @internal * @param canvas_type sizeof(char, short, int). * @param canvas Pointer to image plane where the character is to be drawed. * @param rowstride @a canvas byte distance from line to line. * @param color Color value of @a canvas_type. * @param cw Character width in pixels. * @param ch Character height in pixels. * * Draw blank character. */ static inline void draw_blank(int canvas_type, uint8_t *canvas, unsigned int rowstride, unsigned int color, int cw, int ch) { int x, y; for (y = 0; y < ch; y++) { for (x = 0; x < cw; x++) poke(canvas, x, color); canvas += rowstride; } } /** * @param pg Source vbi_page, see vbi_fetch_cc_page(). * @param fmt Target format. For now only VBI_PIXFMT_RGBA32_LE (vbi_rgba) permitted. * @param canvas Pointer to destination image (currently an array of vbi_rgba), this * must be at least @a rowstride * @a height * 26 bytes large. * @param rowstride @a canvas byte distance from line to line. * If this is -1, pg->columns * 16 * sizeof(vbi_rgba) bytes will be assumed. * @param column First source column, 0 ... pg->columns - 1. * @param row First source row, 0 ... pg->rows - 1. * @param width Number of columns to draw, 1 ... pg->columns. * @param height Number of rows to draw, 1 ... pg->rows. * * Draw a subsection of a Closed Caption vbi_page. In this mode one * character occupies 16 x 26 pixels. */ void vbi_draw_cc_page_region(vbi_page *pg, vbi_pixfmt fmt, void *canvas, int rowstride, int column, int row, int width, int height) { vbi_rgba pen[2], *canvast = canvas; int count, row_adv; vbi_char *ac; if (fmt != VBI_PIXFMT_RGBA32_LE) return; if (0) { int i, j; for (i = 0; i < pg->rows; i++) { fprintf(stderr, "%2d: ", i); ac = &pg->text[i * pg->columns]; for (j = 0; j < pg->columns; j++) fprintf(stderr, "%d%d%02x ", ac[j].foreground, ac[j].background, ac[j].unicode & 0xFF); fprintf(stderr, "\n"); } } if (rowstride == -1) rowstride = pg->columns * CCW * sizeof(*canvast); row_adv = rowstride * CCH - width * CCW * sizeof(*canvast); for (; height > 0; height--, row++) { ac = &pg->text[row * pg->columns + column]; for (count = width; count > 0; count--, ac++) { pen[0] = pg->color_map[ac->background]; pen[1] = pg->color_map[ac->foreground]; draw_char (sizeof (*canvast), (uint8_t *) canvast, rowstride, (uint8_t *) pen, (uint8_t *) ccfont2_bits, CCPL, CCW, CCH, unicode_ccfont2 (ac->unicode, ac->italic), 0 /* bold */, (ac->underline * (3 << 24)) /* cell row 24, 25 */, VBI_NORMAL_SIZE); canvast += CCW; } canvast += row_adv / sizeof(*canvast); } } /** * @param pg Source page. * @param fmt Target format. For now only VBI_PIXFMT_RGBA32_LE (vbi_rgba) permitted. * @param canvas Pointer to destination image (currently an array of vbi_rgba), this * must be at least @a rowstride * @a height * 10 bytes large. * @param rowstride @a canvas byte distance from line to line. * If this is -1, pg->columns * 12 * sizeof(vbi_rgba) bytes will be assumed. * @param column First source column, 0 ... pg->columns - 1. * @param row First source row, 0 ... pg->rows - 1. * @param width Number of columns to draw, 1 ... pg->columns. * @param height Number of rows to draw, 1 ... pg->rows. * @param reveal If FALSE, draw characters flagged 'concealed' (see vbi_char) as * space (U+0020). * @param flash_on If FALSE, draw characters flagged 'blink' (see vbi_char) as * space (U+0020). * * Draw a subsection of a Teletext vbi_page. In this mode one * character occupies 12 x 10 pixels. */ void vbi_draw_vt_page_region(vbi_page *pg, vbi_pixfmt fmt, void *canvas, int rowstride, int column, int row, int width, int height, int reveal, int flash_on) { vbi_rgba pen[64], *canvast = canvas; int count, row_adv; int conceal, off, unicode; vbi_char *ac; int i; if (fmt != VBI_PIXFMT_RGBA32_LE) return; if (0) { int i, j; for (i = 0; i < pg->rows; i++) { fprintf(stderr, "%2d: ", i); ac = &pg->text[i * pg->columns]; for (j = 0; j < pg->columns; j++) fprintf(stderr, "%04x ", ac[j].unicode); fprintf(stderr, "\n"); } } if (rowstride == -1) rowstride = pg->columns * 12 * sizeof(*canvast); row_adv = rowstride * 10 - width * 12 * sizeof(*canvast); conceal = !reveal; off = !flash_on; if (pg->drcs_clut) for (i = 2; i < 2 + 8 + 32; i++) pen[i] = pg->color_map[pg->drcs_clut[i]]; for (; height > 0; height--, row++) { ac = &pg->text[row * pg->columns + column]; for (count = width; count > 0; count--, ac++) { if ((ac->conceal & conceal) || (ac->flash & off)) unicode = 0x0020; else unicode = ac->unicode; pen[0] = pg->color_map[ac->background]; pen[1] = pg->color_map[ac->foreground]; switch (ac->size) { case VBI_OVER_TOP: case VBI_OVER_BOTTOM: break; default: if (vbi_is_drcs(unicode)) { uint8_t *font = pg->drcs[(unicode >> 6) & 0x1F]; if (font) draw_drcs(sizeof(*canvast), (uint8_t *) canvast, rowstride, (uint8_t *) pen, ac->drcs_clut_offs, font, unicode & 0x3F, ac->size); else /* shouldn't happen */ draw_blank(sizeof(*canvast), (uint8_t *) canvast, rowstride, pen[0], TCW, TCH); } else { draw_char (sizeof (*canvast), (uint8_t *) canvast, rowstride, (uint8_t *) pen, (uint8_t *) wstfont2_bits, TCPL, TCW, TCH, unicode_wstfont2 (unicode, ac->italic), ac->bold, ac->underline << 9 /* cell row 9 */, ac->size); } } canvast += TCW; } canvast += row_adv / sizeof(*canvast); } } /* * This won't scale with proportional spacing or custom fonts, * to be removed. */ /** * @param w * @param h * * @deprecated * Character cells are 12 x 10 for Teletext and 16 x 26 for Caption. * Page size is in vbi_page. */ void vbi_get_max_rendered_size(int *w, int *h) { if (w) *w = 41 * TCW; if (h) *h = 25 * TCH; } /** * @param w * @param h * * @deprecated * Character cells are 12 x 10 for Teletext and 16 x 26 for Caption. */ void vbi_get_vt_cell_size(int *w, int *h) { if (w) *w = TCW; if (h) *h = TCH; } /* * Shared export options */ typedef struct gfx_instance { vbi_export export; /* Options */ unsigned double_height : 1; /* * The raw image contains the same information a real TV * would show, however a TV overlays the image on both fields. * So raw pixel aspect is 2:1, and this option will double * lines adding redundant information. The resulting images * with pixel aspect 2:2 are still too narrow compared to a * real TV closer to 4:3 (11 MHz TXT pixel clock), but I * think one should export raw, not scaled data (which is * still possible in Zapping using the screenshot plugin). */ } gfx_instance; static vbi_export * gfx_new(void) { gfx_instance *gfx; if (!(gfx = calloc(1, sizeof(*gfx)))) return NULL; return &gfx->export; } static void gfx_delete(vbi_export *e) { free(PARENT(e, gfx_instance, export)); } static vbi_option_info gfx_options[] = { VBI_OPTION_BOOL_INITIALIZER ("aspect", N_("Correct aspect ratio"), TRUE, N_("Approach an image aspect ratio similar to " "a real TV. This will double the image size.")) }; #define elements(array) (sizeof(array) / sizeof(array[0])) static vbi_option_info * option_enum(vbi_export *e, int index) { e = e; if (index < 0 || index >= (int) elements(gfx_options)) return NULL; else return gfx_options + index; } static vbi_bool option_get(vbi_export *e, const char *keyword, vbi_option_value *value) { gfx_instance *gfx = PARENT(e, gfx_instance, export); if (strcmp(keyword, "aspect") == 0) { value->num = gfx->double_height; } else { vbi_export_unknown_option(e, keyword); return FALSE; } return TRUE; } static vbi_bool option_set(vbi_export *e, const char *keyword, va_list args) { gfx_instance *gfx = PARENT(e, gfx_instance, export); if (strcmp(keyword, "aspect") == 0) { gfx->double_height = !!va_arg(args, int); } else { vbi_export_unknown_option(e, keyword); return FALSE; } return TRUE; } /* * PPM - Portable Pixmap File (raw) */ static vbi_bool ppm_export(vbi_export *e, FILE *fp, vbi_page *pg) { gfx_instance *gfx = PARENT(e, gfx_instance, export); int cw, ch, ww, size, scale, row; vbi_rgba *image; uint8_t *body; int i; if (pg->columns < 40) /* caption */ { cw = CCW; ch = CCH; /* characters already line-doubled */ scale = !!gfx->double_height; } else { cw = TCW; ch = TCH; scale = 1 + !!gfx->double_height; } ww = cw * pg->columns; size = ww * ch * 1; /* one character row */ if (!(image = malloc(size * sizeof(*image)))) { vbi_export_error_printf(e, _("Unable to allocate %d KB image buffer."), size * sizeof(*image) / 1024); return FALSE; } fprintf(fp, "P6 %d %d 255\n", cw * pg->columns, (ch * pg->rows / 2) << scale); if (ferror(fp)) goto write_error; for (row = 0; row < pg->rows; row++) { if (pg->columns < 40) vbi_draw_cc_page_region(pg, VBI_PIXFMT_RGBA32_LE, image, -1, 0, row, pg->columns, 1 /* rows */); else vbi_draw_vt_page_region(pg, VBI_PIXFMT_RGBA32_LE, image, -1, 0, row, pg->columns, 1 /* rows */, !e->reveal, 1 /* flash_on */); body = (uint8_t *) image; if (scale == 0) for (i = 0; i < size; body += 3, i++) { body[0] = ((image[i] & 0xFF) + (image[i + ww] & 0xFF) + 0x01) >> 1; body[1] = ((image[i] & 0xFF00) + (image[i + ww] & 0xFF00) + 0x0100) >> 9; body[2] = ((image[i] & 0xFF0000) + (image[i + ww] & 0xFF0000) + 0x010000) >> 17; } else for (i = 0; i < size; body += 3, i++) { unsigned int n = image[i]; body[0] = n; body[1] = n >> 8; body[2] = n >> 16; } switch (scale) { int rows, stride; case 0: body = (uint8_t *) image; rows = ch / 2; stride = ww * 3; for (i = 0; i < rows; i++, body += stride * 2) if (!fwrite(body, stride, 1, fp)) goto write_error; break; case 1: if (!fwrite(image, size * 3, 1, fp)) goto write_error; break; case 2: body = (uint8_t *) image; stride = cw * pg->columns * 3; for (i = 0; i < ch; body += stride, i++) { if (!fwrite(body, stride, 1, fp)) goto write_error; if (!fwrite(body, stride, 1, fp)) goto write_error; } break; } } free(image); image = NULL; return TRUE; write_error: vbi_export_write_error(e); if (image) free(image); return FALSE; } static vbi_export_info info_ppm = { .keyword = "ppm", .label = N_("PPM"), .tooltip = N_("Export this page as raw PPM image"), .mime_type = "image/x-portable-pixmap", .extension = "ppm", }; vbi_export_class vbi_export_class_ppm = { ._public = &info_ppm, ._new = gfx_new, ._delete = gfx_delete, .option_enum = option_enum, .option_get = option_get, .option_set = option_set, .export = ppm_export }; VBI_AUTOREG_EXPORT_MODULE(vbi_export_class_ppm) /* * PNG - Portable Network Graphics File */ #ifdef HAVE_LIBPNG #include "png.h" #include "setjmp.h" static void draw_char_cc_indexed(png_bytep canvas, int rowstride, png_bytep pen, int unicode, vbi_char *ac) { draw_char(sizeof(png_byte), (uint8_t *) canvas, rowstride, (uint8_t *) pen, (uint8_t *) ccfont2_bits, CCPL, CCW, CCH, unicode_ccfont2(unicode, ac->italic), 0 /* bold */, ac->underline * (3 << 24) /* cell row 24, 25 */, VBI_NORMAL_SIZE); } static void draw_char_vt_indexed(png_bytep canvas, int rowstride, png_bytep pen, int unicode, vbi_char *ac) { draw_char(sizeof(png_byte), (uint8_t *) canvas, rowstride, (uint8_t *) pen, (uint8_t *) wstfont2_bits, TCPL, TCW, TCH, unicode_wstfont2(unicode, ac->italic), ac->bold, ac->underline << 9 /* cell row 9 */, ac->size); } static void draw_drcs_indexed(png_bytep canvas, int rowstride, png_bytep pen, uint8_t *font, int glyph, vbi_size size) { draw_drcs(sizeof(png_byte), (uint8_t *) canvas, rowstride, (uint8_t *) pen, 0, font, glyph, size); } static vbi_bool png_export(vbi_export *e, FILE *fp, vbi_page *pg) { gfx_instance *gfx = PARENT(e, gfx_instance, export); png_structp png_ptr; png_infop info_ptr; png_color palette[80]; png_byte alpha[80]; png_text text[4]; char title[80]; png_bytep *row_pointer; png_bytep image; int cw, ch, ww, wh, rowstride, scale; void (* draw_char_indexed)(png_bytep, int, png_bytep, int, vbi_char *); int i; if (pg->columns < 40) /* caption */ { draw_char_indexed = draw_char_cc_indexed; cw = CCW; ch = CCH; /* characters are already line-doubled */ scale = !!gfx->double_height; } else { draw_char_indexed = draw_char_vt_indexed; cw = TCW; ch = TCH; scale = 1 + !!gfx->double_height; } ww = cw * pg->columns; wh = ch * pg->rows; rowstride = ww * sizeof(*image); if (!(row_pointer = malloc(sizeof(*row_pointer) * wh * 2))) { vbi_export_error_printf(e, _("Unable to allocate %d byte buffer."), sizeof(*row_pointer) * wh * 2); return FALSE; } if ((image = malloc(wh * ww * sizeof(*image)))) { png_bytep canvas = image; png_byte pen[128]; int row, column; vbi_char *ac; int unicode, conceal = !e->reveal; int row_adv; row_adv = pg->columns * cw * (ch - 1); if (pg->drcs_clut) for (i = 2; i < 2 + 8 + 32; i++) { pen[i] = pg->drcs_clut[i]; /* opaque */ pen[i + 64] = pg->drcs_clut[i] + 40; /* translucent */ } for (row = 0; row < pg->rows; canvas += row_adv, row++) { for (column = 0; column < pg->columns ; canvas += cw, column++) { ac = &pg->text[row * pg->columns + column]; if (ac->size == VBI_OVER_TOP || ac->size == VBI_OVER_BOTTOM) continue; unicode = (ac->conceal & conceal) ? 0x0020u : ac->unicode; switch (ac->opacity) { case VBI_TRANSPARENT_SPACE: /* * Transparent foreground and background. */ draw_blank(sizeof(*canvas), (uint8_t *) canvas, rowstride, VBI_TRANSPARENT_BLACK, cw, ch); break; case VBI_TRANSPARENT_FULL: /* * Transparent background, opaque foreground. Currently not used. * Mind Teletext level 2.5 foreground and background transparency * by referencing colormap entry 8, VBI_TRANSPARENT_BLACK. * The background of multicolor DRCS is ambiguous, so we make * them opaque. */ if (vbi_is_drcs(unicode)) { uint8_t *font = pg->drcs[(unicode >> 6) & 0x1F]; pen[0] = VBI_TRANSPARENT_BLACK; pen[1] = ac->foreground; if (font && (draw_char_indexed == draw_char_vt_indexed)) draw_drcs_indexed(canvas, rowstride, pen, font, unicode & 0x3F, ac->size); else /* shouldn't happen */ draw_blank(sizeof(*canvas), (uint8_t *) canvas, rowstride, VBI_TRANSPARENT_BLACK, cw, ch); } else { pen[0] = VBI_TRANSPARENT_BLACK; pen[1] = ac->foreground; draw_char_indexed(canvas, rowstride, pen, unicode, ac); } break; case VBI_SEMI_TRANSPARENT: /* * Translucent background (for 'boxed' text), opaque foreground. * The background of multicolor DRCS is ambiguous, so we make * them completely translucent. */ if (vbi_is_drcs(unicode)) { uint8_t *font = pg->drcs[(unicode >> 6) & 0x1F]; pen[64] = ac->background + 40; pen[65] = ac->foreground; if (font && (draw_char_indexed == draw_char_vt_indexed)) draw_drcs_indexed(canvas, rowstride, (uint8_t *)(pen + 64), font, unicode & 0x3F, ac->size); else /* shouldn't happen */ draw_blank(sizeof(*canvas), (uint8_t *) canvas, rowstride, VBI_TRANSPARENT_BLACK, cw, ch); } else { pen[0] = ac->background + 40; /* translucent */ pen[1] = ac->foreground; draw_char_indexed(canvas, rowstride, pen, unicode, ac); } break; case VBI_OPAQUE: pen[0] = ac->background; pen[1] = ac->foreground; if (vbi_is_drcs(unicode)) { uint8_t *font = pg->drcs[(unicode >> 6) & 0x1F]; if (font && (draw_char_indexed == draw_char_vt_indexed)) draw_drcs_indexed(canvas, rowstride, pen, font, unicode & 0x3F, ac->size); else /* shouldn't happen */ draw_blank(sizeof(*canvas), (uint8_t *) canvas, rowstride, pen[0], cw, ch); } else draw_char_indexed(canvas, rowstride, pen, unicode, ac); break; } } } } else { vbi_export_error_printf(e, _("Unable to allocate %d KB image buffer."), wh * ww * sizeof(*image) / 1024); free(row_pointer); return FALSE; } /* Now save the image */ if (!(png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL))) goto unknown_error; if (!(info_ptr = png_create_info_struct(png_ptr))) { png_destroy_write_struct(&png_ptr, (png_infopp) NULL); goto unknown_error; } /* avoid possible longjmp breakage due to libpng ugliness */ /* XXX not portable, to be removed */ { int do_write() { if (setjmp(png_ptr->jmpbuf)) return 1; png_init_io(png_ptr, fp); png_set_IHDR(png_ptr, info_ptr, ww, (wh << scale) >> 1, 8 /* bit_depth */, PNG_COLOR_TYPE_PALETTE, (gfx->double_height) ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); /* Could be optimized (or does libpng?) */ for (i = 0; i < 40; i++) { /* opaque */ palette[i].red = pg->color_map[i] & 0xFF; palette[i].green = (pg->color_map[i] >> 8) & 0xFF; palette[i].blue = (pg->color_map[i] >> 16) & 0xFF; alpha[i] = 255; /* translucent */ palette[i + 40] = palette[i]; alpha[i + 40] = 128; } alpha[VBI_TRANSPARENT_BLACK] = 0; alpha[40 + VBI_TRANSPARENT_BLACK] = 0; png_set_PLTE(png_ptr, info_ptr, palette, 80); png_set_tRNS(png_ptr, info_ptr, alpha, 80, NULL); png_set_gAMA(png_ptr, info_ptr, 1.0 / 2.2); { int size = 0; if (e->network) size = snprintf(title, sizeof(title) - 1, "%s ", e->network); else title[0] = 0; /* * FIXME * ISO 8859-1 (Latin-1) character set required, * see png spec for other */ if (pg->pgno < 0x100) { size += snprintf(title + size, sizeof(title) - size - 1, "Closed Caption"); /* no i18n, proper name */ } else if (pg->subno != VBI_ANY_SUBNO) { size += snprintf(title + size, sizeof(title) - size - 1, _("Teletext Page %3x.%x"), pg->pgno, pg->subno); } else { size += snprintf(title + size, sizeof(title) - size - 1, _("Teletext Page %3x"), pg->pgno); } } memset(text, 0, sizeof(text)); text[0].key = "Title"; text[0].text = title; text[0].compression = PNG_TEXT_COMPRESSION_NONE; text[1].key = "Software"; text[1].text = e->creator; text[1].compression = PNG_TEXT_COMPRESSION_NONE; png_set_text(png_ptr, info_ptr, text, 2); png_write_info(png_ptr, info_ptr); switch (scale) { case 0: for (i = 0; i < wh / 2; i++) row_pointer[i] = image + i * 2 * ww; break; case 1: for (i = 0; i < wh; i++) row_pointer[i] = image + i * ww; break; case 2: for (i = 0; i < wh; i++) row_pointer[i * 2 + 0] = row_pointer[i * 2 + 1] = image + i * ww; break; } png_write_image(png_ptr, row_pointer); png_write_end(png_ptr, info_ptr); png_destroy_write_struct(&png_ptr, &info_ptr); return 0; } if (do_write()) goto write_error; } free(row_pointer); row_pointer = NULL; free(image); image = NULL; return TRUE; write_error: vbi_export_write_error(e); unknown_error: if (row_pointer) free(row_pointer); if (image) free(image); return FALSE; } static vbi_export_info info_png = { .keyword = "png", .label = N_("PNG"), .tooltip = N_("Export this page as PNG image"), .mime_type = "image/png", .extension = "png", }; vbi_export_class vbi_export_class_png = { ._public = &info_png, ._new = gfx_new, ._delete = gfx_delete, .option_enum = option_enum, .option_get = option_get, .option_set = option_set, .export = png_export }; VBI_AUTOREG_EXPORT_MODULE(vbi_export_class_png) #endif /* HAVE_LIBPNG */