summaryrefslogtreecommitdiff
path: root/src/exp-gfx.c (plain)
blob: c8ff3d5bf1d27eb8666d6e12bea8776604d83163
1/*
2 * libzvbi - Closed Caption and Teletext rendering
3 *
4 * Copyright (C) 2000, 2001, 2002, 2007 Michael H. Schimek
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public
17 * License along with this library; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301 USA.
20 */
21
22/* $Id: exp-gfx.c,v 1.16 2008/02/24 14:17:47 mschimek Exp $ */
23
24#ifdef HAVE_CONFIG_H
25# include "config.h"
26#endif
27
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31#include <assert.h>
32#include <errno.h>
33#include <sys/stat.h>
34#include <unistd.h>
35
36#include "lang.h"
37#include "export.h"
38#include "exp-gfx.h"
39#include "vt.h" /* VBI_TRANSPARENT_BLACK */
40
41#include "wstfont2.xbm"
42#include "ccfont2.xbm"
43
44#include <android/log.h>
45#define LOG_TAG "ZVBI"
46#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
47#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
48
49/* Teletext character cell dimensions - hardcoded (DRCS) */
50
51#define TCW 12
52#define TCH 10
53
54#define TCPL (wstfont2_width / TCW * wstfont2_height / TCH)
55
56/* Closed Caption character cell dimensions */
57
58#define CCW 16
59#define CCH 26 /* line doubled */
60
61#define CCPL (ccfont2_width / CCW * ccfont2_height / CCH)
62
63static void init_gfx(void) __attribute__ ((constructor));
64
65static void
66init_gfx(void)
67{
68 uint8_t *t, *p;
69 int i, j;
70
71 /* de-interleave font image (puts all chars in row 0) */
72
73 if (!(t = malloc(wstfont2_width * wstfont2_height / 8)))
74 exit(EXIT_FAILURE);
75
76 for (p = t, i = 0; i < TCH; i++)
77 for (j = 0; j < wstfont2_height; p += wstfont2_width / 8, j += TCH)
78 memcpy(p, wstfont2_bits + (j + i) * wstfont2_width / 8,
79 wstfont2_width / 8);
80
81 memcpy(wstfont2_bits, t, wstfont2_width * wstfont2_height / 8);
82 free (t);
83
84 if (!(t = malloc(ccfont2_width * ccfont2_height / 8)))
85 exit(EXIT_FAILURE);
86
87 for (p = t, i = 0; i < CCH; i++)
88 for (j = 0; j < ccfont2_height; p += ccfont2_width / 8, j += CCH)
89 memcpy(p, ccfont2_bits + (j + i) * ccfont2_width / 8,
90 ccfont2_width / 8);
91
92 memcpy(ccfont2_bits, t, ccfont2_width * ccfont2_height / 8);
93 free(t);
94}
95
96/**
97 * @internal
98 * @param c Unicode.
99 * @param italic @c TRUE to switch to slanted character set (doesn't affect
100 * Hebrew and Arabic). If this is a G1 block graphic character
101 * switch to separated block mosaic set.
102 *
103 * Translate Unicode character to glyph number in wstfont2 image.
104 *
105 * @return
106 * Glyph number.
107 */
108static unsigned int
109unicode_wstfont2(unsigned int c, int italic)
110{
111 static const unsigned short specials[] = {
112 0x01B5, 0x2016, 0x01CD, 0x01CE, 0x0229, 0x0251, 0x02DD, 0x02C6,
113 0x02C7, 0x02C9, 0x02CA, 0x02CB, 0x02CD, 0x02CF, 0x02D8, 0x02D9,
114 0x02DA, 0x02DB, 0x02DC, 0x2014, 0x2018, 0x2019, 0x201C, 0x201D,
115 0x20A0, 0x2030, 0x20AA, 0x2122, 0x2126, 0x215B, 0x215C, 0x215D,
116 0x215E, 0x2190, 0x2191, 0x2192, 0x2193, 0x25A0, 0x266A, 0xE800,
117 0xE75F };
118 const unsigned int invalid = 357;
119 unsigned int i;
120
121 if (c < 0x0180) {
122 if (c < 0x0080) {
123 if (c < 0x0020)
124 return invalid;
125 else /* %3 Basic Latin (ASCII) 0x0020 ... 0x007F */
126 c = c - 0x0020 + 0 * 32;
127 } else if (c < 0x00A0)
128 return invalid;
129 else /* %3 Latin-1 Supplement, Latin Extended-A 0x00A0 ... 0x017F */
130 c = c - 0x00A0 + 3 * 32;
131 } else if (c < 0xEE00) {
132 if (c < 0x0460) {
133 if (c < 0x03D0) {
134 if (c < 0x0370)
135 goto special;
136 else /* %5 Greek 0x0370 ... 0x03CF */
137 c = c - 0x0370 + 12 * 32;
138 } else if (c < 0x0400)
139 return invalid;
140 else /* %5 Cyrillic 0x0400 ... 0x045F */
141 c = c - 0x0400 + 15 * 32;
142 } else if (c < 0x0620) {
143 if (c < 0x05F0) {
144 if (c < 0x05D0)
145 return invalid;
146 else /* %6 Hebrew 0x05D0 ... 0x05EF */
147 return c - 0x05D0 + 18 * 32;
148 } else if (c < 0x0600)
149 return invalid;
150 else /* %6 Arabic 0x0600 ... 0x061F */
151 return c - 0x0600 + 19 * 32;
152 } else if (c >= 0xE600 && c < 0xE740)
153 return c - 0xE600 + 19 * 32; /* %6 Arabic (TTX) */
154 else
155 goto special;
156 } else if (c < 0xEF00) { /* %3 G1 Graphics */
157 return (c ^ 0x20) - 0xEE00 + 23 * 32;
158 } else if (c < 0xF000) { /* %4 G3 Graphics */
159 return c - 0xEF20 + 27 * 32;
160 } else /* 0xF000 ... 0xF7FF reserved for DRCS */
161 return invalid;
162
163 if (italic)
164 return c + 31 * 32;
165 else
166 return c;
167special:
168 for (i = 0; i < sizeof(specials) / sizeof(specials[0]); i++)
169 if (specials[i] == c) {
170 if (italic)
171 return i + 41 * 32;
172 else
173 return i + 10 * 32;
174 }
175
176 return invalid;
177}
178
179/**
180 * @internal
181 * @param c Unicode.
182 * @param italic @c TRUE to switch to slanted character set.
183 *
184 * Translate Unicode character to glyph number in ccfont2 image.
185 *
186 * @return
187 * Glyph number.
188 */
189static unsigned int
190unicode_ccfont2(unsigned int c, int italic)
191{
192 static const unsigned short specials[] = {
193 0x00E1, 0x00E9,
194 0x00ED, 0x00F3, 0x00FA, 0x00E7, 0x00F7, 0x00D1, 0x00F1, 0x25A0,
195 0x00AE, 0x00B0, 0x00BD, 0x00BF, 0x2122, 0x00A2, 0x00A3, 0x266A,
196 0x00E0, 0x0020, 0x00E8, 0x00E2, 0x00EA, 0x00EE, 0x00F4, 0x00FB };
197 unsigned int i;
198
199 if (c < 0x0020)
200 c = 15; /* invalid */
201 else if (c < 0x0080)
202 c = c;
203 else {
204 for (i = 0; i < sizeof(specials) / sizeof(specials[0]); i++)
205 if (specials[i] == c) {
206 c = i + 6;
207 goto slant;
208 }
209
210 c = 15; /* invalid */
211 }
212
213slant:
214 if (italic)
215 c += 4 * 32;
216
217 return c;
218}
219
220/**
221 * @internal
222 * @param p Plane of @a canvas_type char, short, int.
223 * @param i Index.
224 *
225 * @return
226 * Pixel @a i in plane @a p.
227 */
228#define peek(p, i) \
229((canvas_type == sizeof(uint8_t)) ? ((uint8_t *)(p))[i] : \
230 ((canvas_type == sizeof(uint16_t)) ? ((uint16_t *)(p))[i] : \
231 ((uint32_t *)(p))[i]))
232
233/**
234 * @internal
235 * @param p Plane of @a canvas_type char, short, int.
236 * @param i Index.
237 * @param v Value.
238 *
239 * Set pixel @a i in plane @a p to value @a v.
240 */
241#define poke(p, i, v) \
242((canvas_type == sizeof(uint8_t)) ? (((uint8_t *)(p))[i] = (v)) : \
243 ((canvas_type == sizeof(uint16_t)) ? (((uint16_t *)(p))[i] = (v)) : \
244 (((uint32_t *)(p))[i] = (v))))
245
246/**
247 * @internal
248 * @param canvas_type sizeof(char, short, int).
249 * @param canvas Pointer to image plane where the character is to be drawn.
250 * @param rowstride @a canvas <em>byte</em> distance from line to line.
251 * @param pen Pointer to color palette of @a canvas_type (index 0 background
252 * pixels, index 1 foreground pixels).
253 * @param font Pointer to font image with width @a cpl x @a cw pixels, height
254 * @a ch pixels, depth one bit, bit '1' is foreground.
255 * @param cpl Chars per line (number of characters in @a font image).
256 * @param cw Character cell width in pixels.
257 * @param ch Character cell height in pixels.
258 * @param glyph Glyph number in font image, 0 ... @a cpl - 1.
259 * @param bold Draw character bold (font image | font image << 1).
260 * @param underline Bit mask of character rows. For each bit
261 * 1 << (n = 0 ... @a ch - 1) set all of character row n to
262 * foreground color.
263 * @param size Size of character, either NORMAL, DOUBLE_WIDTH (draws left
264 * and right half), DOUBLE_HEIGHT (draws upper half only),
265 * DOUBLE_SIZE (left and right upper half), DOUBLE_HEIGHT2
266 * (lower half), DOUBLE_SIZE2 (left and right lower half).
267 *
268 * Draw one character (function template - define a static version with
269 * constant @a canvas_type, @a font, @a cpl, @a cw, @a ch).
270 */
271static inline void
272draw_char(int canvas_type, uint8_t *canvas, int rowstride,
273 uint8_t *pen, uint8_t *font, int cpl, int cw, int ch,
274 int glyph, int bold, unsigned int underline, vbi_size size)
275{
276 uint8_t *src;
277 int shift, x, y;
278
279 bold = !!bold;
280 assert(cw >= 8 && cw <= 16);
281 assert(ch >= 1 && cw <= 31);
282
283 x = glyph * cw;
284 shift = x & 7;
285 src = font + (x >> 3);
286
287 switch (size) {
288 case VBI_DOUBLE_HEIGHT2:
289 case VBI_DOUBLE_SIZE2:
290 src += cpl * cw / 8 * ch / 2;
291 underline >>= ch / 2;
292
293 case VBI_DOUBLE_HEIGHT:
294 case VBI_DOUBLE_SIZE:
295 ch >>= 1;
296
297 default:
298 break;
299 }
300
301 for (y = 0; y < ch; underline >>= 1, y++) {
302 int bits = ~0;
303
304 if (!(underline & 1)) {
305#ifdef __GNUC__
306#if defined (i386)
307 bits = (*((uint16_t *) src) >> shift);
308#else
309 /* unaligned/little endian */
310 bits = ((src[1] * 256 + src[0]) >> shift);
311#endif
312#else
313 bits = ((src[1] * 256 + src[0]) >> shift);
314#endif
315 bits |= bits << bold;
316 }
317
318 switch (size) {
319 case VBI_NORMAL_SIZE:
320 for (x = 0; x < cw; bits >>= 1, x++)
321 poke(canvas, x, peek(pen, bits & 1));
322
323 canvas += rowstride;
324
325 break;
326
327 case VBI_DOUBLE_HEIGHT:
328 case VBI_DOUBLE_HEIGHT2:
329 for (x = 0; x < cw; bits >>= 1, x++) {
330 unsigned int col = peek(pen, bits & 1);
331
332 poke(canvas, x, col);
333 poke(canvas, x + rowstride / canvas_type, col);
334 }
335
336 canvas += rowstride * 2;
337
338 break;
339
340 case VBI_DOUBLE_WIDTH:
341 for (x = 0; x < cw * 2; bits >>= 1, x += 2) {
342 unsigned int col = peek(pen, bits & 1);
343
344 poke(canvas, x + 0, col);
345 poke(canvas, x + 1, col);
346 }
347
348 canvas += rowstride;
349
350 break;
351
352 case VBI_DOUBLE_SIZE:
353 case VBI_DOUBLE_SIZE2:
354 for (x = 0; x < cw * 2; bits >>= 1, x += 2) {
355 unsigned int col = peek(pen, bits & 1);
356
357 poke(canvas, x + 0, col);
358 poke(canvas, x + 1, col);
359 poke(canvas, x + rowstride / canvas_type + 0, col);
360 poke(canvas, x + rowstride / canvas_type + 1, col);
361 }
362
363 canvas += rowstride * 2;
364
365 break;
366
367 default:
368 break;
369 }
370
371 src += cpl * cw / 8;
372 }
373}
374
375/**
376 * @internal
377 * @param canvas_type sizeof(char, short, int).
378 * @param canvas Pointer to image plane where the character is to be drawn.
379 * @param rowstride @a canvas <em>byte</em> distance from line to line.
380 * @param pen Pointer to color palette of @a canvas_type (index 0 ... 1 for
381 * depth 1 DRCS, 0 ... 3 for depth 2, 0 ... 15 for depth 4).
382 * @param color Offset into color palette.
383 * @param font Pointer to DRCS image. Each pixel is coded in four bits, an
384 * index into the color palette, and stored in LE order (i. e. first
385 * pixel 0x0F, second pixel 0xF0). Character size is 12 x 10 pixels,
386 * 60 bytes, without padding.
387 * @param glyph Glyph number in font image, 0x00 ... 0x3F.
388 * @param size Size of character, either NORMAL, DOUBLE_WIDTH (draws left
389 * and right half), DOUBLE_HEIGHT (draws upper half only),
390 * DOUBLE_SIZE (left and right upper half), DOUBLE_HEIGHT2
391 * (lower half), DOUBLE_SIZE2 (left and right lower half).
392 *
393 * Draw one Teletext Dynamically Redefinable Character (function template -
394 * define a static version with constant @a canvas_type, @a font).
395 */
396static inline void
397draw_drcs(int canvas_type, uint8_t *canvas, unsigned int rowstride,
398 uint8_t *pen, int color, uint8_t *font, int glyph, vbi_size size)
399{
400 uint8_t *src;
401 unsigned int col;
402 int x, y;
403
404 src = font + glyph * 60;
405 pen = pen + color * canvas_type;
406
407 switch (size) {
408 case VBI_NORMAL_SIZE:
409 for (y = 0; y < TCH; canvas += rowstride, y++)
410 for (x = 0; x < 12; src++, x += 2) {
411 poke(canvas, x + 0, peek(pen, *src & 15));
412 poke(canvas, x + 1, peek(pen, *src >> 4));
413 }
414 break;
415
416 case VBI_DOUBLE_HEIGHT2:
417 src += 30;
418
419 case VBI_DOUBLE_HEIGHT:
420 for (y = 0; y < TCH / 2; canvas += rowstride * 2, y++)
421 for (x = 0; x < 12; src++, x += 2) {
422 col = peek(pen, *src & 15);
423 poke(canvas, x + 0, col);
424 poke(canvas, x + rowstride / canvas_type + 0, col);
425
426 col = peek(pen, *src >> 4);
427 poke(canvas, x + 1, col);
428 poke(canvas, x + rowstride / canvas_type + 1, col);
429 }
430 break;
431
432 case VBI_DOUBLE_WIDTH:
433 for (y = 0; y < TCH; canvas += rowstride, y++)
434 for (x = 0; x < 12 * 2; src++, x += 4) {
435 col = peek(pen, *src & 15);
436 poke(canvas, x + 0, col);
437 poke(canvas, x + 1, col);
438
439 col = peek(pen, *src >> 4);
440 poke(canvas, x + 2, col);
441 poke(canvas, x + 3, col);
442 }
443 break;
444
445 case VBI_DOUBLE_SIZE2:
446 src += 30;
447
448 case VBI_DOUBLE_SIZE:
449 for (y = 0; y < TCH / 2; canvas += rowstride * 2, y++)
450 for (x = 0; x < 12 * 2; src++, x += 4) {
451 col = peek(pen, *src & 15);
452 poke(canvas, x + 0, col);
453 poke(canvas, x + 1, col);
454 poke(canvas, x + rowstride / canvas_type + 0, col);
455 poke(canvas, x + rowstride / canvas_type + 1, col);
456
457 col = peek(pen, *src >> 4);
458 poke(canvas, x + 2, col);
459 poke(canvas, x + 3, col);
460 poke(canvas, x + rowstride / canvas_type + 2, col);
461 poke(canvas, x + rowstride / canvas_type + 3, col);
462 }
463 break;
464
465 default:
466 break;
467 }
468}
469
470/**
471 * @internal
472 * @param canvas_type sizeof(char, short, int).
473 * @param canvas Pointer to image plane where the character is to be drawn.
474 * @param rowstride @a canvas <em>byte</em> distance from line to line.
475 * @param color Color value of @a canvas_type.
476 * @param cw Character width in pixels.
477 * @param ch Character height in pixels.
478 *
479 * Draw blank character.
480 */
481static inline void
482draw_blank(int canvas_type, uint8_t *canvas, unsigned int rowstride,
483 unsigned int color, int cw, int ch)
484{
485 int x, y;
486
487 for (y = 0; y < ch; y++) {
488 for (x = 0; x < cw; x++)
489 poke(canvas, x, color);
490
491 canvas += rowstride;
492 }
493}
494
495/**
496 * @param pg Source vbi_page, see vbi_fetch_cc_page().
497 * @param fmt Target format. For now only VBI_PIXFMT_RGBA32_LE (vbi_rgba) permitted.
498 * @param canvas Pointer to destination image (currently an array of vbi_rgba), this
499 * must be at least @a rowstride * @a height * 26 bytes large.
500 * @param rowstride @a canvas <em>byte</em> distance from line to line.
501 * If this is -1, pg->columns * 16 * sizeof(vbi_rgba) bytes will be assumed.
502 * @param column First source column, 0 ... pg->columns - 1.
503 * @param row First source row, 0 ... pg->rows - 1.
504 * @param width Number of columns to draw, 1 ... pg->columns.
505 * @param height Number of rows to draw, 1 ... pg->rows.
506 *
507 * Draw a subsection of a Closed Caption vbi_page. In this mode one
508 * character occupies 16 x 26 pixels.
509 */
510void
511vbi_draw_cc_page_region(vbi_page *pg,
512 vbi_pixfmt fmt, void *canvas, int rowstride,
513 int column, int row, int width, int height)
514{
515 union {
516 vbi_rgba rgba[2];
517 uint8_t pal8[2];
518 } pen;
519 int count, row_adv;
520 vbi_char *ac;
521 int canvas_type, fg_opacity, bg_opacity;
522
523 if (fmt == VBI_PIXFMT_RGBA32_LE) {
524 canvas_type = 4;
525 } else if (fmt == VBI_PIXFMT_PAL8) {
526 canvas_type = 1;
527 } else {
528 return;
529 }
530
531 if (0) {
532 int i, j;
533
534 for (i = 0; i < pg->rows; i++) {
535 fprintf(stderr, "%2d: ", i);
536 ac = &pg->text[i * pg->columns];
537 for (j = 0; j < pg->columns; j++)
538 fprintf(stderr, "%d%d%02x ",
539 ac[j].foreground,
540 ac[j].background,
541 ac[j].unicode & 0xFF);
542 fprintf(stderr, "\n");
543 }
544 }
545
546 if (rowstride == -1)
547 rowstride = pg->columns * CCW * canvas_type;
548
549 row_adv = rowstride * CCH - width * CCW * canvas_type;
550
551 for (; height > 0; height--, row++) {
552 ac = &pg->text[row * pg->columns + column];
553
554 for (count = width; count > 0; count--, ac++) {
555 if (canvas_type == 1) {
556 pen.pal8[0] = ac->background;
557 pen.pal8[1] = ac->foreground;
558 } else {
559 bg_opacity = ac->opacity&0x0f;
560 fg_opacity = (ac->opacity&0Xf0) >> 4;
561
562 if (bg_opacity == VBI_TRANSPARENT_SPACE){
563 pen.rgba[0] = 0;
564 }else if (bg_opacity == VBI_SEMI_TRANSPARENT){
565 pen.rgba[0] = pg->color_map[ac->background] & 0x80000000;
566 }else{
567 pen.rgba[0] = pg->color_map[ac->background];
568 }
569
570 if (fg_opacity == VBI_TRANSPARENT_SPACE){
571 pen.rgba[1] = 0;
572 }else if (fg_opacity == VBI_SEMI_TRANSPARENT){
573 pen.rgba[1] = pg->color_map[ac->foreground] & 0x80000000;
574 }else{
575 pen.rgba[1] = pg->color_map[ac->foreground];
576 }
577 }
578
579 draw_char (canvas_type,
580 (uint8_t *) canvas,
581 rowstride,
582 (uint8_t *) &pen,
583 (uint8_t *) ccfont2_bits,
584 CCPL, CCW, CCH,
585 unicode_ccfont2 (ac->unicode, ac->italic),
586 0 /* bold */,
587 (ac->underline
588 * (3 << 24)) /* cell row 24, 25 */,
589 VBI_NORMAL_SIZE);
590
591 canvas = (uint8_t *) canvas
592 + CCW * canvas_type;
593 }
594
595 canvas = (uint8_t *) canvas + row_adv;
596 }
597}
598
599/**
600 * @param pg Source page.
601 * @param fmt Target format. For now only VBI_PIXFMT_RGBA32_LE (vbi_rgba) and
602 * VBI_PIXFMT_PAL8 (1-byte palette indices) are permitted.
603 * @param canvas Pointer to destination image (depending on the format, either
604 * an array of vbi_rgba or uint8_t), this must be at least
605 * @a rowstride * @a height * 10 bytes large.
606 * @param rowstride @a canvas <em>byte</em> distance from line to line.
607 * If this is -1, pg->columns * 12 * sizeof(vbi_rgba) bytes will be assumed.
608 * @param column First source column, 0 ... pg->columns - 1.
609 * @param row First source row, 0 ... pg->rows - 1.
610 * @param width Number of columns to draw, 1 ... pg->columns.
611 * @param height Number of rows to draw, 1 ... pg->rows.
612 * @param reveal If FALSE, draw characters flagged 'concealed' (see vbi_char) as
613 * space (U+0020).
614 * @param flash_on If FALSE, draw characters flagged 'blink' (see vbi_char) as
615 * space (U+0020).
616 * @param subtitle If TRUE, draw subtitle mode teletext.
617 *
618 * Draw a subsection of a Teletext vbi_page. In this mode one
619 * character occupies 12 x 10 pixels. Note this function does
620 * not consider transparency (e.g. on boxed pages)
621 */
622void
623vbi_draw_vt_page_region(vbi_page *pg,
624 vbi_pixfmt fmt, void *canvas, int rowstride,
625 int column, int row, int width, int height,
626 int reveal, int flash_on, int subtitle)
627{
628 union {
629 vbi_rgba rgba[64];
630 uint8_t pal8[64];
631 } pen;
632 int count, row_adv;
633 int conceal, off, unicode;
634 vbi_char *ac;
635 int canvas_type;
636 int i;
637
638 if (fmt == VBI_PIXFMT_RGBA32_LE) {
639 canvas_type = 4;
640 } else if (fmt == VBI_PIXFMT_PAL8) {
641 canvas_type = 1;
642 } else {
643 return;
644 }
645
646 if (0) {
647 int i, j;
648
649 for (i = 0; i < pg->rows; i++) {
650 fprintf(stderr, "%2d: ", i);
651 ac = &pg->text[i * pg->columns];
652 for (j = 0; j < pg->columns; j++)
653 fprintf(stderr, "%04x ", ac[j].unicode);
654 fprintf(stderr, "\n");
655 }
656 }
657
658 if (rowstride == -1)
659 rowstride = pg->columns * 12 * canvas_type;
660
661 row_adv = rowstride * 10 - width * 12 * canvas_type;
662
663 conceal = !reveal;
664 off = !flash_on;
665
666 if (pg->drcs_clut)
667 for (i = 2; i < 2 + 8 + 32; i++)
668 if (canvas_type == 1)
669 pen.pal8[i] = pg->drcs_clut[i];
670 else
671 pen.rgba[i] = pg->color_map[pg->drcs_clut[i]];
672
673 for (; height > 0; height--, row++) {
674 ac = &pg->text[row * pg->columns + column];
675
676 for (count = width; count > 0; count--, ac++) {
677 if ((ac->conceal & conceal) || (ac->flash & off))
678 unicode = 0x0020;
679 else
680 unicode = ac->unicode;
681
682 if (subtitle && (row == 0))
683 unicode = 0x0020;
684
685 if (canvas_type == 1) {
686 pen.pal8[0] = ac->background;
687 pen.pal8[1] = ac->foreground;
688 } else {
689 pen.rgba[0] = pg->color_map[ac->background];
690 pen.rgba[1] = pg->color_map[ac->foreground];
691
692 if(subtitle){
693 if(vbi_is_drcs(unicode) || unicode==0x0020) {
694 pen.rgba[0] &= 0x00FFFFFF;
695 pen.rgba[1] &= 0x00FFFFFF;
696 }else{
697 pen.rgba[0] &= 0x00FFFFFF;
698 //pen.rgba[0] |= 0x80000000;
699 }
700 }
701 }
702
703 switch (ac->size) {
704 case VBI_OVER_TOP:
705 case VBI_OVER_BOTTOM:
706 break;
707
708 default:
709 if (vbi_is_drcs(unicode)) {
710 uint8_t *font = pg->drcs[(unicode >> 6) & 0x1F];
711
712 if (font)
713 draw_drcs(canvas_type, canvas, rowstride,
714 (uint8_t *) &pen, ac->drcs_clut_offs,
715 font, unicode & 0x3F, ac->size);
716 else /* shouldn't happen */
717 draw_blank(canvas_type, canvas, rowstride,
718 ((canvas_type == 1) ? pen.pal8[0]: pen.rgba[0]),
719 TCW, TCH);
720 } else {
721 draw_char (canvas_type,
722 canvas,
723 rowstride,
724 (uint8_t *) &pen,
725 (uint8_t *) wstfont2_bits,
726 TCPL, TCW, TCH,
727 unicode_wstfont2 (unicode, ac->italic),
728 ac->bold,
729 ac->underline << 9 /* cell row 9 */,
730 ac->size);
731 }
732 }
733
734 canvas = (uint8_t *)canvas + TCW * canvas_type;
735 }
736
737 canvas = (uint8_t *)canvas + row_adv;
738 }
739}
740
741/*
742 * This won't scale with proportional spacing or custom fonts,
743 * to be removed.
744 */
745
746/**
747 * @param w
748 * @param h
749 *
750 * @deprecated
751 * Character cells are 12 x 10 for Teletext and 16 x 26 for Caption.
752 * Page size is in vbi_page.
753 */
754void
755vbi_get_max_rendered_size(int *w, int *h)
756{
757 if (w) *w = 41 * TCW;
758 if (h) *h = 25 * TCH;
759}
760
761/**
762 * @param w
763 * @param h
764 *
765 * @deprecated
766 * Character cells are 12 x 10 for Teletext and 16 x 26 for Caption.
767 */
768void
769vbi_get_vt_cell_size(int *w, int *h)
770{
771 if (w) *w = TCW;
772 if (h) *h = TCH;
773}
774
775/*
776 * Shared export options
777 */
778
779typedef struct gfx_instance
780{
781 vbi_export export;
782
783 /* Options */
784 unsigned double_height : 1;
785 /*
786 * The raw image contains the same information a real TV
787 * would show, however a TV overlays the image on both fields.
788 * So raw pixel aspect is 2:1, and this option will double
789 * lines adding redundant information. The resulting images
790 * with pixel aspect 2:2 are still too narrow compared to a
791 * real TV closer to 4:3 (11 MHz TXT pixel clock), but I
792 * think one should export raw, not scaled data (which is
793 * still possible in Zapping using the screenshot plugin).
794 */
795 unsigned titled : 1;
796 /*
797 * By default a title string is embedded in the images which
798 * names the page number and optionally the network. This
799 * option can be used to suppress this feature
800 */
801 unsigned transparency : 1;
802 /*
803 * By default, image formats which support transparency
804 * use transparent background for boxed pages. This option
805 * can be used to define transparent areas as black.
806 */
807} gfx_instance;
808
809static vbi_export *
810gfx_new(void)
811{
812 gfx_instance *gfx;
813
814 if (!(gfx = calloc(1, sizeof(*gfx))))
815 return NULL;
816
817 return &gfx->export;
818}
819
820static void
821gfx_delete(vbi_export *e)
822{
823 free(PARENT(e, gfx_instance, export));
824}
825
826
827static vbi_option_info
828gfx_options[] = {
829 /* all formats */
830 VBI_OPTION_BOOL_INITIALIZER
831 ("aspect", N_("Correct aspect ratio"),
832 TRUE, N_("Approach an image aspect ratio similar to "
833 "a real TV. This will double the image size.")),
834 /* XPM and PNG only */
835 VBI_OPTION_BOOL_INITIALIZER
836 ("transparency", N_("Include transparency"),
837 TRUE, N_("If not enabled, transparency is mapped to black.")),
838 VBI_OPTION_BOOL_INITIALIZER
839 ("titled", N_("Include page title"),
840 TRUE, N_("Embed a title string which names network "
841 "and page number."))
842};
843
844#define elements(array) (sizeof(array) / sizeof(array[0]))
845
846static vbi_option_info *
847option_enum(vbi_export *e, int index)
848{
849 e = e;
850
851 if (index < 0 || index >= (int) elements(gfx_options))
852 return NULL;
853 else
854 return gfx_options + index;
855}
856
857static vbi_option_info *
858option_enum_ppm(vbi_export *e, int index)
859{
860 e = e;
861
862 if (index != 0)
863 return NULL;
864 else
865 return gfx_options + index;
866}
867
868static vbi_bool
869option_get(vbi_export *e, const char *keyword, vbi_option_value *value)
870{
871 gfx_instance *gfx = PARENT(e, gfx_instance, export);
872
873 if (strcmp(keyword, "aspect") == 0) {
874 value->num = gfx->double_height;
875 } else if (strcmp(keyword, "titled") == 0) {
876 value->num = gfx->titled;
877 } else if (strcmp(keyword, "transparency") == 0) {
878 value->num = gfx->transparency;
879 } else {
880 vbi_export_unknown_option(e, keyword);
881 return FALSE;
882 }
883
884 return TRUE;
885}
886
887static vbi_bool
888option_set(vbi_export *e, const char *keyword, va_list args)
889{
890 gfx_instance *gfx = PARENT(e, gfx_instance, export);
891
892 if (strcmp(keyword, "aspect") == 0) {
893 gfx->double_height = !!va_arg(args, int);
894 } else if (strcmp(keyword, "titled") == 0) {
895 gfx->titled = !!va_arg(args, int);
896 } else if (strcmp(keyword, "transparency") == 0) {
897 gfx->transparency = !!va_arg(args, int);
898 } else {
899 vbi_export_unknown_option(e, keyword);
900 return FALSE;
901 }
902
903 return TRUE;
904}
905
906/**
907 * @internal
908 * @param e Pointer to export context
909 * @param pg Page reference
910 * @param title Output buffer for returning the title
911 * @param title_max Size of @a title buffer
912 *
913 * Determine a suitable label for the hardcopy.
914 * The label is inserted as comment inside of XPM or PNG image files.
915 */
916static void
917get_image_title(vbi_export *e, const vbi_page *pg, char *title, int title_max)
918{
919 gfx_instance *gfx = PARENT(e, gfx_instance, export);
920 int size = 0;
921
922 if (!gfx->titled) {
923 title[0] = 0;
924 return;
925 }
926
927 if (e->network)
928 size = snprintf(title, title_max - 1, "%s ", e->network);
929 else
930 title[0] = 0;
931
932 /*
933 * FIXME
934 * ISO 8859-1 (Latin-1) character set required,
935 * see png spec for other
936 */
937 if (pg->pgno < 0x100) {
938 size += snprintf(title + size, title_max - size - 1,
939 "Closed Caption"); /* no i18n, proper name */
940 } else if (pg->subno != VBI_ANY_SUBNO) {
941 size += snprintf(title + size, title_max - size - 1,
942 _("Teletext Page %3x.%x"),
943 pg->pgno, pg->subno);
944 } else {
945 size += snprintf(title + size, title_max - size - 1,
946 _("Teletext Page %3x"), pg->pgno);
947 }
948}
949
950
951/*
952 * PPM - Portable Pixmap File (raw)
953 */
954
955static vbi_bool
956ppm_export (vbi_export * e,
957 vbi_page * pg)
958{
959 gfx_instance *gfx = PARENT(e, gfx_instance, export);
960 vbi_rgba *rgba_image;
961 const vbi_rgba *rgba_row_buffer;
962 unsigned int image_width; /* in pixels */
963 unsigned int image_height;
964 unsigned int char_width; /* in pixels */
965 unsigned int char_height;
966 unsigned int scale;
967 unsigned int row;
968 size_t rgba_row_size;
969 size_t ppm_row_size;
970 size_t needed;
971 vbi_bool result;
972
973 rgba_image = NULL;
974 result = FALSE;
975
976 if (pg->columns < 40) /* caption */ {
977 char_width = CCW;
978 char_height = CCH;
979 /* Characters are already line-doubled. */
980 scale = !!gfx->double_height;
981 } else {
982 char_width = TCW;
983 char_height = TCH;
984 scale = 1 + !!gfx->double_height;
985 }
986
987 image_width = char_width * pg->columns;
988 image_height = ((char_height * pg->rows) << scale) >> 1;
989
990 rgba_row_size = image_width * char_height;
991 ppm_row_size = ((rgba_row_size << scale) >> 1) * 3;
992 rgba_row_size *= sizeof (vbi_rgba);
993
994 if (VBI_EXPORT_TARGET_MEM == e->target) {
995 if (!vbi_export_printf (e, "P6 %u %u 255\n",
996 image_width, image_height))
997 goto failed;
998
999 /* Check in advance if enough space is available for
1000 the rest of the PPM image. */
1001 needed = ppm_row_size * pg->rows;
1002 if (!_vbi_export_grow_buffer_space (e, needed))
1003 goto failed;
1004
1005 rgba_image = malloc (rgba_row_size);
1006 if (NULL == rgba_image) {
1007 _vbi_export_malloc_error (e);
1008 goto failed;
1009 }
1010
1011 rgba_row_buffer = rgba_image;
1012 } else {
1013 size_t margin;
1014
1015 /* vbi_export_printf() and _vbi_export_grow_buffer_space()
1016 allocate more buffer memory as needed, but for
1017 efficiency we estimate the required space and
1018 allocate it all in advance. We use the same buffer
1019 for the RGBA and PPM image. One row is enough as we
1020 flush() after each row. It should be enough for the
1021 header too, if it is buffered at all. Otherwise
1022 vbi_export_printf() below will allocate more memory. */
1023 margin = (2 == scale) ? image_width * sizeof (vbi_rgba): 0;
1024 needed = MAX (rgba_row_size - margin,
1025 ppm_row_size) + margin;
1026
1027 if (VBI_EXPORT_TARGET_ALLOC == e->target) {
1028 /* The buffer must hold the entire PPM image.
1029 When we're done vbi_export_alloc() will
1030 truncate it with realloc(). */
1031 needed += 64; /* max. header size */
1032 needed += ppm_row_size * (pg->rows - 1);
1033 }
1034
1035 if (!_vbi_export_grow_buffer_space (e, needed))
1036 goto failed;
1037
1038 if (!vbi_export_printf (e, "P6 %u %u 255\n",
1039 image_width, image_height))
1040 goto failed;
1041
1042 if (!vbi_export_flush (e))
1043 goto failed;
1044
1045 rgba_row_buffer = (const vbi_rgba *)
1046 (e->buffer.data
1047 + ((e->buffer.capacity - rgba_row_size)
1048 & -sizeof (*rgba_row_buffer))); /* align */
1049 }
1050
1051 for (row = 0; row < (unsigned int) pg->rows; ++row) {
1052 uint8_t *d;
1053 uint8_t *d_end;
1054 const vbi_rgba *s;
1055 unsigned int count;
1056
1057 if (pg->columns < 40) {
1058 vbi_draw_cc_page_region (pg, VBI_PIXFMT_RGBA32_LE,
1059 rgba_row_buffer,
1060 /* rowstride: auto */ -1,
1061 /* column */ 0, row,
1062 pg->columns, /* rows */ 1);
1063 } else {
1064 vbi_draw_vt_page_region (pg, VBI_PIXFMT_RGBA32_LE,
1065 rgba_row_buffer,
1066 /* rowstride: auto */ -1,
1067 /* column */ 0, row,
1068 pg->columns, /* rows */ 1,
1069 /* reveal */ !e->reveal,
1070 /* flash_on */ TRUE, FALSE);
1071 }
1072
1073 d = (uint8_t *) e->buffer.data + e->buffer.offset;
1074 s = rgba_row_buffer;
1075
1076 switch (scale) {
1077 case 0:
1078 count = char_height >> 1;
1079 do {
1080 d_end = d + image_width * 3;
1081 do {
1082 vbi_rgba n1 = s[image_width];
1083 vbi_rgba n0 = *s++;
1084
1085 d[0] = ((n0 & 0xFF) + (n1 & 0xFF)
1086 + 0x01) >> 1;
1087 d[1] = ((n0 & 0xFF00) + (n1 & 0xFF00)
1088 + 0x0100) >> 9;
1089 d[2] = ((n0 & 0xFF0000)
1090 + (n1 & 0xFF0000)
1091 + 0x010000) >> 17;
1092 d += 3;
1093 } while (d < d_end);
1094
1095 s += image_width;
1096 } while (--count > 0);
1097
1098 break;
1099
1100 case 1:
1101 d_end = d + image_width * char_height * 3;
1102 do {
1103 vbi_rgba n = *s++;
1104
1105 d[0] = n;
1106 d[1] = n >> 8;
1107 d[2] = n >> 16;
1108 d += 3;
1109 } while (d < d_end);
1110
1111 break;
1112
1113 case 2:
1114 count = char_height;
1115 do {
1116 d_end = d + image_width * 3;
1117 do {
1118 vbi_rgba n = *s++;
1119
1120 d[0] = n;
1121 d[1] = n >> 8;
1122 d[2] = n >> 16;
1123 d[image_width * 3 + 0] = n;
1124 d[image_width * 3 + 1] = n >> 8;
1125 d[image_width * 3 + 2] = n >> 16;
1126 d += 3;
1127 } while (d < d_end);
1128
1129 d += image_width * 3;
1130 } while (--count > 0);
1131
1132 break;
1133
1134 default:
1135 assert (0);
1136 }
1137
1138 e->buffer.offset = (char *) d - e->buffer.data;
1139
1140 if (!vbi_export_flush (e))
1141 goto failed;
1142 }
1143
1144 result = TRUE;
1145
1146 failed:
1147 free (rgba_image);
1148
1149 return result;
1150}
1151
1152static vbi_export_info
1153info_ppm = {
1154 .keyword = "ppm",
1155 .label = N_("PPM"),
1156 .tooltip = N_("Export this page as raw PPM image"),
1157
1158 .mime_type = "image/x-portable-pixmap",
1159 .extension = "ppm",
1160};
1161
1162vbi_export_class
1163vbi_export_class_ppm = {
1164 ._public = &info_ppm,
1165 ._new = gfx_new,
1166 ._delete = gfx_delete,
1167 .option_enum = option_enum_ppm,
1168 .option_get = option_get,
1169 .option_set = option_set,
1170 .export = ppm_export
1171};
1172
1173VBI_AUTOREG_EXPORT_MODULE(vbi_export_class_ppm)
1174
1175
1176/*
1177 * PNG and XPM drawing functions (palette-based)
1178 */
1179static void
1180draw_char_cc_indexed(uint8_t * canvas, int rowstride, uint8_t * pen,
1181 int unicode, vbi_char *ac)
1182{
1183 draw_char(sizeof(*canvas), canvas, rowstride,
1184 pen, (uint8_t *) ccfont2_bits, CCPL, CCW, CCH,
1185 unicode_ccfont2(unicode, ac->italic), 0 /* bold */,
1186 ac->underline * (3 << 24) /* cell row 24, 25 */,
1187 VBI_NORMAL_SIZE);
1188}
1189
1190static void
1191draw_char_vt_indexed(uint8_t * canvas, int rowstride, uint8_t * pen,
1192 int unicode, vbi_char *ac)
1193{
1194 draw_char(sizeof(*canvas), canvas, rowstride,
1195 pen, (uint8_t *) wstfont2_bits, TCPL, TCW, TCH,
1196 unicode_wstfont2(unicode, ac->italic), ac->bold,
1197 ac->underline << 9 /* cell row 9 */, ac->size);
1198}
1199
1200static void
1201draw_drcs_indexed(uint8_t * canvas, int rowstride, uint8_t * pen,
1202 uint8_t *font, int glyph, vbi_size size)
1203{
1204 draw_drcs(sizeof(*canvas), canvas, rowstride,
1205 (uint8_t *) pen, 0, font, glyph, size);
1206}
1207
1208static void
1209draw_row_indexed(vbi_page * pg, vbi_char * ac, uint8_t * canvas, uint8_t * pen,
1210 int rowstride, vbi_bool conceal, vbi_bool is_cc)
1211{
1212 const int cw = is_cc ? CCW : TCW;
1213 const int ch = is_cc ? CCH : TCH;
1214 void (* draw_char_indexed)(uint8_t *, int, uint8_t *, int, vbi_char *)
1215 = is_cc ? draw_char_cc_indexed : draw_char_vt_indexed;
1216 int column;
1217 int unicode;
1218
1219 for (column = 0; column < pg->columns ; canvas += cw, column++, ac++) {
1220
1221 if (ac->size == VBI_OVER_TOP
1222 || ac->size == VBI_OVER_BOTTOM)
1223 continue;
1224
1225 unicode = (ac->conceal & conceal) ? 0x0020u : ac->unicode;
1226
1227 switch (ac->opacity) {
1228 case VBI_TRANSPARENT_SPACE:
1229 /*
1230 * Transparent foreground and background.
1231 */
1232 draw_blank(sizeof(*canvas), canvas,
1233 rowstride, VBI_TRANSPARENT_BLACK, cw, ch);
1234 break;
1235
1236 case VBI_TRANSPARENT_FULL:
1237 /*
1238 * Transparent background, opaque foreground. Currently not used.
1239 * Mind Teletext level 2.5 foreground and background transparency
1240 * by referencing colormap entry 8, VBI_TRANSPARENT_BLACK.
1241 * The background of multicolor DRCS is ambiguous, so we make
1242 * them opaque.
1243 */
1244 if (vbi_is_drcs(unicode)) {
1245 uint8_t *font = pg->drcs[(unicode >> 6) & 0x1F];
1246
1247 pen[0] = VBI_TRANSPARENT_BLACK;
1248 pen[1] = ac->foreground;
1249
1250 if (font && !is_cc)
1251 draw_drcs_indexed(canvas, rowstride, pen,
1252 font, unicode & 0x3F, ac->size);
1253 else /* shouldn't happen */
1254 draw_blank(sizeof(*canvas), (uint8_t *) canvas,
1255 rowstride, VBI_TRANSPARENT_BLACK, cw, ch);
1256 } else {
1257 pen[0] = VBI_TRANSPARENT_BLACK;
1258 pen[1] = ac->foreground;
1259
1260 draw_char_indexed(canvas, rowstride, pen, unicode, ac);
1261 }
1262
1263 break;
1264
1265 case VBI_SEMI_TRANSPARENT:
1266 /*
1267 * Translucent background (for 'boxed' text), opaque foreground.
1268 * The background of multicolor DRCS is ambiguous, so we make
1269 * them completely translucent.
1270 */
1271 if (vbi_is_drcs(unicode)) {
1272 uint8_t *font = pg->drcs[(unicode >> 6) & 0x1F];
1273
1274 pen[64] = ac->background + 40;
1275 pen[65] = ac->foreground;
1276
1277 if (font && !is_cc)
1278 draw_drcs_indexed(canvas, rowstride,
1279 (uint8_t *)(pen + 64),
1280 font, unicode & 0x3F, ac->size);
1281 else /* shouldn't happen */
1282 draw_blank(sizeof(*canvas), (uint8_t *) canvas,
1283 rowstride, VBI_TRANSPARENT_BLACK, cw, ch);
1284 } else {
1285 pen[0] = ac->background + 40; /* translucent */
1286 pen[1] = ac->foreground;
1287
1288 draw_char_indexed(canvas, rowstride, pen, unicode, ac);
1289 }
1290
1291 break;
1292
1293 case VBI_OPAQUE:
1294 pen[0] = ac->background;
1295 pen[1] = ac->foreground;
1296
1297 if (vbi_is_drcs(unicode)) {
1298 uint8_t *font = pg->drcs[(unicode >> 6) & 0x1F];
1299
1300 if (font && !is_cc)
1301 draw_drcs_indexed(canvas, rowstride, pen,
1302 font, unicode & 0x3F, ac->size);
1303 else /* shouldn't happen */
1304 draw_blank(sizeof(*canvas), (uint8_t *) canvas,
1305 rowstride, pen[0], cw, ch);
1306 } else
1307 draw_char_indexed(canvas, rowstride, pen, unicode, ac);
1308 break;
1309 }
1310 }
1311}
1312
1313/*
1314 * XPM - X Pixmap
1315 *
1316 * According to "XPM Manual" version 3.4i, 1996-09-10, by Arnaud Le Hors
1317 */
1318
1319static const uint8_t xpm_col_codes[40] =
1320 " 1234567.BCDEFGHIJKLMNOPabcdefghijklmnop";
1321
1322/**
1323 * @internal
1324 * @param e Export context
1325 * @param pg Page reference
1326 * @param image_width Image width
1327 * @param image_height Image height
1328 * @param title Optional title to be included in extension data, or @c NULL.
1329 * @param creator Optional software name and version to be included in
1330 * extension data, or @c NULL.
1331 *
1332 * This function writes an XPM header and color palette for an image with
1333 * the given size and optional extension data. The color palette is
1334 * retrieved from the page referenced by @a pg.
1335 *
1336 * @returns
1337 * @c FALSE on error.
1338 */
1339static vbi_bool
1340xpm_write_header (vbi_export * e,
1341 const vbi_page * pg,
1342 unsigned int image_width,
1343 unsigned int image_height,
1344 const char * title,
1345 const char * creator)
1346{
1347 gfx_instance *gfx = PARENT(e, gfx_instance, export);
1348 vbi_bool do_ext = ((NULL != title) && (0 != title[0]))
1349 || ((NULL != creator) && (0 != creator[0]));
1350 unsigned int i;
1351
1352 /* Warning: adapt buf size estimation when changing the text! */
1353 vbi_export_printf (e,
1354 "/* XPM */\n"
1355 "static char *image[] = {\n"
1356 "/* width height ncolors chars_per_pixel */\n"
1357 "\"%d %d %d %d%s\",\n"
1358 "/* colors */\n",
1359 image_width, image_height, 40, 1,
1360 do_ext ? " XPMEXT":"");
1361
1362 /* Write color palette (including unused colors
1363 - could be optimized). */
1364 for (i = 0; i < 40; ++i) {
1365 if ((8 == i) && gfx->transparency) {
1366 vbi_export_printf (e,
1367 "\"%c c None\",\n",
1368 xpm_col_codes[i]);
1369 } else {
1370 vbi_export_printf (e,
1371 "\"%c c #%02X%02X%02X\",\n",
1372 xpm_col_codes[i],
1373 pg->color_map[i] & 0xFF,
1374 (pg->color_map[i] >> 8) & 0xFF,
1375 (pg->color_map[i] >> 16) & 0xFF);
1376 }
1377 }
1378
1379 vbi_export_printf (e, "/* pixels */\n");
1380
1381 /* Note this also returns FALSE if any of the
1382 vbi_export_printf() calls above failed. */
1383 return vbi_export_flush (e);
1384}
1385
1386/**
1387 * @internal
1388 * @param e Export context
1389 * @param title Optional title to be included in extension data, or @c NULL.
1390 * @param creator Optional software name and version to be included in
1391 * extension data, or @c NULL.
1392 *
1393 * This function writes an XPM "footer" (i.e. anything following the actual
1394 * image data) and optionally appends image title and software names as
1395 * extension data.
1396 *
1397 * @returns
1398 * @c FALSE on error.
1399 */
1400static vbi_bool
1401xpm_write_footer (vbi_export * e,
1402 const char * title,
1403 const char * creator)
1404{
1405 char *p;
1406
1407 if ( ((NULL != title) && (0 != title[0]))
1408 || ((NULL != creator) && (0 != creator[0])) ) {
1409
1410 /* Warning: adapt buf size estimation when changing the text! */
1411 if (NULL != title && 0 != title[0]) {
1412 while ((p = strchr(title, '"')) != NULL)
1413 *p = '\'';
1414 vbi_export_printf (e, "\"XPMEXT title %s\",\n", title);
1415 }
1416
1417 if (NULL != creator && 0 != creator[0]) {
1418 while ((p = strchr(creator, '"')) != NULL)
1419 *p = '\'';
1420 vbi_export_printf (e, "\"XPMEXT software %s\",\n", creator);
1421 }
1422
1423 vbi_export_printf (e, "\"XPMENDEXT\"\n");
1424 }
1425
1426 vbi_export_printf (e, "};\n");
1427
1428 /* Note this also returns FALSE if any of the
1429 vbi_export_printf() calls above failed. */
1430 return vbi_export_flush (e);
1431}
1432
1433/**
1434 * @internal
1435 * @param e Export context
1436 * @param s Image source data as written by draw_row_indexed()
1437 * @param image_width Image width
1438 * @param char_height Character height
1439 * @param scale Scale image:
1440 * - 0 to half height
1441 * - 1 to normal height
1442 * - 2 to double height
1443 *
1444 * This function writes XPM image data for one Teletext or Closed Caption
1445 * row (i.e. several pixel lines) into the given buffer. The image is scaled
1446 * vertically on the fly.
1447 *
1448 * The conversion consists of adding C-style framing at the start and end of
1449 * each pixel row and converting "binary" palette indices into color code
1450 * characters. CLUT 1 color 0 is hard-coded as transparent; semi-transparent
1451 * colors are also mapped to the transparent color code since XPM does not
1452 * have an alpha channel.
1453 *
1454 * @returns
1455 * @c FALSE on error.
1456 */
1457static vbi_bool
1458xpm_write_row (vbi_export * e,
1459 const uint8_t * s,
1460 unsigned int image_width,
1461 unsigned int char_height,
1462 unsigned int scale)
1463{
1464 size_t needed;
1465 char *d;
1466
1467 needed = (((image_width + 4) * char_height) << scale) >> 1;
1468 if (unlikely (!_vbi_export_grow_buffer_space (e, needed)))
1469 return FALSE;
1470
1471 d = e->buffer.data + e->buffer.offset;
1472
1473 do {
1474 char *d_end;
1475
1476 *d++ = '"';
1477
1478 d_end = d + image_width;
1479 do {
1480 uint8_t c = *s++;
1481
1482 if (c < sizeof (xpm_col_codes)) {
1483 *d++ = xpm_col_codes[c];
1484 } else {
1485 *d++ = '.'; /* transparent */
1486 }
1487 } while (d < d_end);
1488
1489 d[0] = '"';
1490 d[1] = ',';
1491 d[2] = '\n';
1492 d += 3;
1493
1494 if (0 == scale) {
1495 /* Scale down - use every 2nd row. */
1496 --char_height;
1497 s += image_width;
1498 } else if (2 == scale) {
1499 /* Scale up - double each line. */
1500 memcpy (d, d - image_width - 4, image_width + 4);
1501 d += image_width + 4;
1502 }
1503 } while (--char_height > 0);
1504
1505 e->buffer.offset = d - e->buffer.data;
1506
1507 return vbi_export_flush (e);
1508}
1509
1510static vbi_bool
1511xpm_export (vbi_export * e,
1512 vbi_page * pg)
1513{
1514 gfx_instance *gfx = PARENT(e, gfx_instance, export);
1515 uint8_t pen[128];
1516 char title[80];
1517 uint8_t *indexed_image;
1518 unsigned int image_width;
1519 unsigned int image_height;
1520 unsigned int char_width;
1521 unsigned int char_height;
1522 unsigned int scale;
1523 unsigned int row;
1524 vbi_bool result;
1525
1526 indexed_image = NULL;
1527 result = FALSE;
1528
1529 if (pg->columns < 40) /* caption */ {
1530 char_width = CCW;
1531 char_height = CCH;
1532 /* Characters are already line-doubled. */
1533 scale = !!gfx->double_height;
1534 } else {
1535 char_width = TCW;
1536 char_height = TCH;
1537 scale = 1 + !!gfx->double_height;
1538 }
1539
1540 image_width = char_width * pg->columns;
1541 image_height = ((char_height * pg->rows) << scale) >> 1;
1542
1543 get_image_title (e, pg, title, sizeof (title));
1544
1545 if (pg->drcs_clut) {
1546 unsigned int i;
1547
1548 for (i = 2; i < 2 + 8 + 32; i++) {
1549 pen[i] = pg->drcs_clut[i]; /* opaque */
1550 pen[i + 64] = 40; /* translucent */
1551 }
1552 }
1553
1554 indexed_image = malloc (image_width * char_height);
1555 if (unlikely (NULL == indexed_image)) {
1556 _vbi_export_malloc_error (e);
1557 goto failed;
1558 }
1559
1560 switch (e->target) {
1561 size_t header_size;
1562 size_t footer_size;
1563 size_t xpm_row_size;
1564 size_t needed;
1565
1566 case VBI_EXPORT_TARGET_MEM:
1567 /* vbi_export_printf() and _vbi_export_grow_buffer_space()
1568 will check on the fly if enough space is available. */
1569 break;
1570
1571 case VBI_EXPORT_TARGET_FP:
1572 /* Header and footer are not e->buffered and we allocate
1573 the XPM data buffer once in xpm_write_row(). */
1574 break;
1575
1576 default:
1577 /* vbi_export_printf() and _vbi_export_grow_buffer_space()
1578 allocate more buffer memory as needed, but for
1579 efficiency we estimate the XPM image size and
1580 allocate it all in advance. */
1581
1582 header_size = 109 /* header (incl. 4-digit width
1583 and height and 2-digit col num) */
1584 + 15 * 40 /* color palette with 40 members */
1585 + 13; /* row start comment */
1586 if (gfx->transparency)
1587 header_size -= 7 - 4; /* "#RRGGBB" - "None" */
1588 xpm_row_size = (((image_width + 4) * char_height)
1589 << scale) >> 1;
1590 footer_size = 3; /* closing bracket */
1591
1592 if ( ((NULL != title) && (0 != title[0]))
1593 || ((NULL != e->creator) && (0 != e->creator[0])) ) {
1594 header_size += 7; /* XPMEXT keyword */
1595 footer_size += 12; /* XPMENDEXT keyword */
1596 if (NULL != title) {
1597 /* XPMEXT keywords + label + content */
1598 footer_size += 17 + strlen (title);
1599 }
1600 if (NULL != e->creator)
1601 footer_size += 20 + strlen (e->creator);
1602 }
1603
1604 if (VBI_EXPORT_TARGET_ALLOC == e->target) {
1605 needed = header_size + footer_size
1606 + xpm_row_size * pg->rows;
1607 } else {
1608 /* We flush() after writing header, footer and
1609 each row. */
1610 needed = MAX (header_size, footer_size);
1611 needed = MAX (needed, xpm_row_size);
1612 }
1613
1614 if (unlikely (!_vbi_export_grow_buffer_space (e, needed)))
1615 return FALSE;
1616 }
1617
1618 if (!xpm_write_header (e, pg, image_width, image_height,
1619 title, e->creator))
1620 goto failed;
1621
1622 for (row = 0; row < (unsigned int) pg->rows; ++row) {
1623 draw_row_indexed (pg, &pg->text[row * pg->columns],
1624 indexed_image, pen, image_width,
1625 !e->reveal, pg->columns < 40);
1626
1627 if (!xpm_write_row (e, indexed_image,
1628 image_width, char_height, scale))
1629 goto failed;
1630 }
1631
1632 if (!xpm_write_footer (e, title, e->creator))
1633 goto failed;
1634
1635 result = TRUE;
1636
1637 failed:
1638 free (indexed_image);
1639
1640 return result;
1641}
1642
1643static vbi_export_info
1644info_xpm = {
1645 .keyword = "xpm",
1646 .label = N_("XPM"),
1647 .tooltip = N_("Export this page as XPM image"),
1648
1649 .mime_type = "image/xpm",
1650 .extension = "xpm",
1651};
1652
1653vbi_export_class
1654vbi_export_class_xpm = {
1655 ._public = &info_xpm,
1656 ._new = gfx_new,
1657 ._delete = gfx_delete,
1658 .option_enum = option_enum,
1659 .option_get = option_get,
1660 .option_set = option_set,
1661 .export = xpm_export
1662};
1663
1664VBI_AUTOREG_EXPORT_MODULE(vbi_export_class_xpm)
1665
1666
1667/*
1668 * PNG - Portable Network Graphics File
1669 */
1670#ifdef HAVE_LIBPNG
1671
1672#include "png.h"
1673#include "setjmp.h"
1674
1675static void
1676write_data (png_structp png_ptr,
1677 png_bytep data,
1678 png_size_t length)
1679{
1680 gfx_instance *gfx = (gfx_instance *) png_get_io_ptr (png_ptr);
1681
1682 vbi_export_write (&gfx->export, data, length);
1683}
1684
1685static void
1686flush_data (png_structp png_ptr)
1687{
1688 gfx_instance *gfx = (gfx_instance *) png_get_io_ptr (png_ptr);
1689
1690 vbi_export_flush (&gfx->export);
1691}
1692
1693static vbi_bool
1694write_png (gfx_instance * gfx,
1695 const vbi_page * pg,
1696 png_structp png_ptr,
1697 png_infop info_ptr,
1698 png_bytep image,
1699 png_bytep * row_pointer,
1700 unsigned int ww,
1701 unsigned int wh,
1702 unsigned int scale)
1703{
1704 png_color palette[80];
1705 png_byte alpha[80];
1706 png_text text[4];
1707 char title[80];
1708 unsigned int i;
1709
1710 if (setjmp (png_ptr->jmpbuf))
1711 return FALSE;
1712
1713 png_set_write_fn (png_ptr,
1714 (voidp) gfx,
1715 write_data,
1716 flush_data);
1717
1718 png_set_IHDR (png_ptr,
1719 info_ptr,
1720 ww,
1721 (wh << scale) >> 1,
1722 /* bit_depth */ 8,
1723 PNG_COLOR_TYPE_PALETTE,
1724 gfx->double_height ?
1725 PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE,
1726 PNG_COMPRESSION_TYPE_DEFAULT,
1727 PNG_FILTER_TYPE_DEFAULT);
1728
1729 /* Could be optimized (or does libpng?) */
1730 for (i = 0; i < 40; i++) {
1731 /* opaque */
1732 palette[i].red = pg->color_map[i] & 0xFF;
1733 palette[i].green = (pg->color_map[i] >> 8) & 0xFF;
1734 palette[i].blue = (pg->color_map[i] >> 16) & 0xFF;
1735 alpha[i] = 255;
1736
1737 /* translucent */
1738 palette[i + 40] = palette[i];
1739 alpha[i + 40] = 128;
1740 }
1741
1742 alpha[VBI_TRANSPARENT_BLACK] = 0;
1743 alpha[40 + VBI_TRANSPARENT_BLACK] = 0;
1744
1745 png_set_PLTE (png_ptr, info_ptr, palette, 80);
1746
1747 if (gfx->transparency)
1748 png_set_tRNS (png_ptr, info_ptr, alpha, 80, NULL);
1749
1750 png_set_gAMA (png_ptr, info_ptr, 1.0 / 2.2);
1751
1752 get_image_title (&gfx->export, pg, title, sizeof (title));
1753
1754 CLEAR (text);
1755
1756 i = 0;
1757 if (0 != title[0]) {
1758 text[i].key = "Title";
1759 text[i].text = title;
1760 text[i].compression = PNG_TEXT_COMPRESSION_NONE;
1761 i++;
1762 }
1763 if (NULL != gfx->export.creator && 0 != gfx->export.creator[0]) {
1764 text[i].key = "Software";
1765 text[i].text = gfx->export.creator;
1766 text[i].compression = PNG_TEXT_COMPRESSION_NONE;
1767 i++;
1768 }
1769 png_set_text (png_ptr, info_ptr, text, i);
1770
1771 png_write_info (png_ptr, info_ptr);
1772
1773 switch (scale) {
1774 case 0:
1775 for (i = 0; i < wh / 2; i++)
1776 row_pointer[i] = image + i * 2 * ww;
1777 break;
1778
1779 case 1:
1780 for (i = 0; i < wh; i++)
1781 row_pointer[i] = image + i * ww;
1782 break;
1783
1784 case 2:
1785 for (i = 0; i < wh; i++)
1786 row_pointer[i * 2 + 0] =
1787 row_pointer[i * 2 + 1] = image + i * ww;
1788 break;
1789 }
1790
1791 png_write_image (png_ptr, row_pointer);
1792
1793 png_write_end (png_ptr, info_ptr);
1794
1795 return TRUE;
1796}
1797
1798static vbi_bool
1799png_export(vbi_export *e, vbi_page *pg)
1800{
1801 gfx_instance *gfx = PARENT(e, gfx_instance, export);
1802 png_structp png_ptr;
1803 png_infop info_ptr;
1804 uint8_t pen[128];
1805 png_bytep *row_pointer;
1806 png_bytep image;
1807 int ww, wh, rowstride, row_adv, scale;
1808 int row;
1809 int i;
1810
1811 assert ((sizeof(png_byte) == sizeof(uint8_t))
1812 && (sizeof(*image) == sizeof(uint8_t)));
1813
1814 if (pg->columns < 40) /* caption */ {
1815 /* characters are already line-doubled */
1816 scale = !!gfx->double_height;
1817 ww = CCW * pg->columns;
1818 wh = CCH * pg->rows;
1819 row_adv = pg->columns * CCW * CCH;
1820 } else {
1821 scale = 1 + !!gfx->double_height;
1822 ww = TCW * pg->columns;
1823 wh = TCH * pg->rows;
1824 row_adv = pg->columns * TCW * TCH;
1825 }
1826
1827 rowstride = ww * sizeof(*image);
1828
1829 if (!(row_pointer = malloc(sizeof(*row_pointer) * wh * 2))) {
1830 vbi_export_error_printf(e, _("Unable to allocate %d byte buffer."),
1831 sizeof(*row_pointer) * wh * 2);
1832 return FALSE;
1833 }
1834
1835 if (!(image = malloc(wh * ww * sizeof(*image)))) {
1836 vbi_export_error_printf(e, _("Unable to allocate %d KB image buffer."),
1837 wh * ww * sizeof(*image) / 1024);
1838 free(row_pointer);
1839 return FALSE;
1840 }
1841
1842 /* draw the image */
1843
1844 if (pg->drcs_clut) {
1845 for (i = 2; i < 2 + 8 + 32; i++) {
1846 pen[i] = pg->drcs_clut[i]; /* opaque */
1847 pen[i + 64] = pg->drcs_clut[i] + 40; /* translucent */
1848 }
1849 }
1850
1851 for (row = 0; row < pg->rows; row++) {
1852 draw_row_indexed(pg, &pg->text[row * pg->columns],
1853 image + row * row_adv, pen, rowstride,
1854 !e->reveal, pg->columns < 40);
1855 }
1856
1857 /* Now save the image */
1858
1859 if (!(png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
1860 NULL, NULL, NULL)))
1861 goto unknown_error;
1862
1863 if (!(info_ptr = png_create_info_struct(png_ptr))) {
1864 png_destroy_write_struct(&png_ptr, (png_infopp) NULL);
1865 goto unknown_error;
1866 }
1867
1868 if (!write_png (gfx, pg, png_ptr, info_ptr,
1869 image, row_pointer, ww, wh, scale)) {
1870 png_destroy_write_struct (&png_ptr, &info_ptr);
1871 goto write_error;
1872 }
1873
1874 png_destroy_write_struct (&png_ptr, &info_ptr);
1875
1876 if (gfx->export.write_error)
1877 goto failed;
1878
1879 free (row_pointer);
1880
1881 free (image);
1882
1883 return TRUE;
1884
1885write_error:
1886 vbi_export_write_error(e);
1887
1888unknown_error:
1889failed:
1890 free (row_pointer);
1891
1892 free (image);
1893
1894 return FALSE;
1895}
1896
1897static vbi_export_info
1898info_png = {
1899 .keyword = "png",
1900 .label = N_("PNG"),
1901 .tooltip = N_("Export this page as PNG image"),
1902
1903 .mime_type = "image/png",
1904 .extension = "png",
1905};
1906
1907vbi_export_class
1908vbi_export_class_png = {
1909 ._public = &info_png,
1910 ._new = gfx_new,
1911 ._delete = gfx_delete,
1912 .option_enum = option_enum,
1913 .option_get = option_get,
1914 .option_set = option_set,
1915 .export = png_export
1916};
1917
1918VBI_AUTOREG_EXPORT_MODULE(vbi_export_class_png)
1919
1920#endif /* HAVE_LIBPNG */
1921
1922/*
1923Local variables:
1924c-set-style: K&R
1925c-basic-offset: 8
1926End:
1927*/
1928