summaryrefslogtreecommitdiff
path: root/src/exp-gfx.c (plain)
blob: 4232ddadc623be53cb68ad980e0cb6bfa2ce4e24
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 int transparent;
678
679 if ((ac->conceal & conceal) || (ac->flash & off))
680 unicode = 0x0020;
681 else
682 unicode = ac->unicode;
683
684 if (subtitle && (row == 0))
685 unicode = 0x0020;
686
687 if (!subtitle) {
688 transparent = 0;
689 } else if (subtitle == 1) {
690 if (vbi_is_drcs(unicode) || unicode==0x0020)
691 transparent = 1;
692 else
693 transparent = 0;
694 } else {
695 if (row == 0) {
696 transparent = 1;
697 } else if (vbi_is_drcs(unicode) || unicode==0x0020) {
698 vbi_char *tc;
699 int n, uc;
700 int left, right;
701
702 left = right = 0;
703
704 for (n = count + 1, tc = ac - 1; n <= width; tc --, n ++) {
705 if ((tc->conceal & conceal) || (tc->flash & off))
706 uc = 0x0020;
707 else
708 uc = tc->unicode;
709
710 if(!vbi_is_drcs(uc) && uc!=0x0020) {
711 left = 1;
712 break;
713 }
714 }
715
716 for (n = count - 1, tc = ac + 1; n > 0; tc ++, n --) {
717 if ((tc->conceal & conceal) || (tc->flash & off))
718 uc = 0x0020;
719 else
720 uc = tc->unicode;
721
722 if(!vbi_is_drcs(uc) && uc!=0x0020) {
723 right = 1;
724 break;
725 }
726 }
727
728 transparent = !(left & right);
729 } else {
730 transparent = 0;
731 }
732 }
733
734 if (canvas_type == 1) {
735 pen.pal8[0] = ac->background;
736 pen.pal8[1] = ac->foreground;
737 } else {
738 pen.rgba[0] = pg->color_map[ac->background];
739 pen.rgba[1] = pg->color_map[ac->foreground];
740
741 if (transparent) {
742 pen.rgba[0] = 0x00FFFFFF;
743 pen.rgba[1] = 0x00FFFFFF;
744 }
745
746 if (subtitle == 1)
747 pen.rgba[0] = 0x00FFFFFF;
748 }
749
750 switch (ac->size) {
751 case VBI_OVER_TOP:
752 case VBI_OVER_BOTTOM:
753 break;
754
755 default:
756 if (vbi_is_drcs(unicode)) {
757 uint8_t *font = pg->drcs[(unicode >> 6) & 0x1F];
758
759 if (font)
760 draw_drcs(canvas_type, canvas, rowstride,
761 (uint8_t *) &pen, ac->drcs_clut_offs,
762 font, unicode & 0x3F, ac->size);
763 else /* shouldn't happen */
764 draw_blank(canvas_type, canvas, rowstride,
765 ((canvas_type == 1) ? pen.pal8[0]: pen.rgba[0]),
766 TCW, TCH);
767 } else {
768 draw_char (canvas_type,
769 canvas,
770 rowstride,
771 (uint8_t *) &pen,
772 (uint8_t *) wstfont2_bits,
773 TCPL, TCW, TCH,
774 unicode_wstfont2 (unicode, ac->italic),
775 ac->bold,
776 ac->underline << 9 /* cell row 9 */,
777 ac->size);
778 }
779 }
780
781 canvas = (uint8_t *)canvas + TCW * canvas_type;
782 }
783
784 canvas = (uint8_t *)canvas + row_adv;
785 }
786}
787
788/*
789 * This won't scale with proportional spacing or custom fonts,
790 * to be removed.
791 */
792
793/**
794 * @param w
795 * @param h
796 *
797 * @deprecated
798 * Character cells are 12 x 10 for Teletext and 16 x 26 for Caption.
799 * Page size is in vbi_page.
800 */
801void
802vbi_get_max_rendered_size(int *w, int *h)
803{
804 if (w) *w = 41 * TCW;
805 if (h) *h = 25 * TCH;
806}
807
808/**
809 * @param w
810 * @param h
811 *
812 * @deprecated
813 * Character cells are 12 x 10 for Teletext and 16 x 26 for Caption.
814 */
815void
816vbi_get_vt_cell_size(int *w, int *h)
817{
818 if (w) *w = TCW;
819 if (h) *h = TCH;
820}
821
822/*
823 * Shared export options
824 */
825
826typedef struct gfx_instance
827{
828 vbi_export export;
829
830 /* Options */
831 unsigned double_height : 1;
832 /*
833 * The raw image contains the same information a real TV
834 * would show, however a TV overlays the image on both fields.
835 * So raw pixel aspect is 2:1, and this option will double
836 * lines adding redundant information. The resulting images
837 * with pixel aspect 2:2 are still too narrow compared to a
838 * real TV closer to 4:3 (11 MHz TXT pixel clock), but I
839 * think one should export raw, not scaled data (which is
840 * still possible in Zapping using the screenshot plugin).
841 */
842 unsigned titled : 1;
843 /*
844 * By default a title string is embedded in the images which
845 * names the page number and optionally the network. This
846 * option can be used to suppress this feature
847 */
848 unsigned transparency : 1;
849 /*
850 * By default, image formats which support transparency
851 * use transparent background for boxed pages. This option
852 * can be used to define transparent areas as black.
853 */
854} gfx_instance;
855
856static vbi_export *
857gfx_new(void)
858{
859 gfx_instance *gfx;
860
861 if (!(gfx = calloc(1, sizeof(*gfx))))
862 return NULL;
863
864 return &gfx->export;
865}
866
867static void
868gfx_delete(vbi_export *e)
869{
870 free(PARENT(e, gfx_instance, export));
871}
872
873
874static vbi_option_info
875gfx_options[] = {
876 /* all formats */
877 VBI_OPTION_BOOL_INITIALIZER
878 ("aspect", N_("Correct aspect ratio"),
879 TRUE, N_("Approach an image aspect ratio similar to "
880 "a real TV. This will double the image size.")),
881 /* XPM and PNG only */
882 VBI_OPTION_BOOL_INITIALIZER
883 ("transparency", N_("Include transparency"),
884 TRUE, N_("If not enabled, transparency is mapped to black.")),
885 VBI_OPTION_BOOL_INITIALIZER
886 ("titled", N_("Include page title"),
887 TRUE, N_("Embed a title string which names network "
888 "and page number."))
889};
890
891#define elements(array) (sizeof(array) / sizeof(array[0]))
892
893static vbi_option_info *
894option_enum(vbi_export *e, int index)
895{
896 e = e;
897
898 if (index < 0 || index >= (int) elements(gfx_options))
899 return NULL;
900 else
901 return gfx_options + index;
902}
903
904static vbi_option_info *
905option_enum_ppm(vbi_export *e, int index)
906{
907 e = e;
908
909 if (index != 0)
910 return NULL;
911 else
912 return gfx_options + index;
913}
914
915static vbi_bool
916option_get(vbi_export *e, const char *keyword, vbi_option_value *value)
917{
918 gfx_instance *gfx = PARENT(e, gfx_instance, export);
919
920 if (strcmp(keyword, "aspect") == 0) {
921 value->num = gfx->double_height;
922 } else if (strcmp(keyword, "titled") == 0) {
923 value->num = gfx->titled;
924 } else if (strcmp(keyword, "transparency") == 0) {
925 value->num = gfx->transparency;
926 } else {
927 vbi_export_unknown_option(e, keyword);
928 return FALSE;
929 }
930
931 return TRUE;
932}
933
934static vbi_bool
935option_set(vbi_export *e, const char *keyword, va_list args)
936{
937 gfx_instance *gfx = PARENT(e, gfx_instance, export);
938
939 if (strcmp(keyword, "aspect") == 0) {
940 gfx->double_height = !!va_arg(args, int);
941 } else if (strcmp(keyword, "titled") == 0) {
942 gfx->titled = !!va_arg(args, int);
943 } else if (strcmp(keyword, "transparency") == 0) {
944 gfx->transparency = !!va_arg(args, int);
945 } else {
946 vbi_export_unknown_option(e, keyword);
947 return FALSE;
948 }
949
950 return TRUE;
951}
952
953/**
954 * @internal
955 * @param e Pointer to export context
956 * @param pg Page reference
957 * @param title Output buffer for returning the title
958 * @param title_max Size of @a title buffer
959 *
960 * Determine a suitable label for the hardcopy.
961 * The label is inserted as comment inside of XPM or PNG image files.
962 */
963static void
964get_image_title(vbi_export *e, const vbi_page *pg, char *title, int title_max)
965{
966 gfx_instance *gfx = PARENT(e, gfx_instance, export);
967 int size = 0;
968
969 if (!gfx->titled) {
970 title[0] = 0;
971 return;
972 }
973
974 if (e->network)
975 size = snprintf(title, title_max - 1, "%s ", e->network);
976 else
977 title[0] = 0;
978
979 /*
980 * FIXME
981 * ISO 8859-1 (Latin-1) character set required,
982 * see png spec for other
983 */
984 if (pg->pgno < 0x100) {
985 size += snprintf(title + size, title_max - size - 1,
986 "Closed Caption"); /* no i18n, proper name */
987 } else if (pg->subno != VBI_ANY_SUBNO) {
988 size += snprintf(title + size, title_max - size - 1,
989 _("Teletext Page %3x.%x"),
990 pg->pgno, pg->subno);
991 } else {
992 size += snprintf(title + size, title_max - size - 1,
993 _("Teletext Page %3x"), pg->pgno);
994 }
995}
996
997
998/*
999 * PPM - Portable Pixmap File (raw)
1000 */
1001
1002static vbi_bool
1003ppm_export (vbi_export * e,
1004 vbi_page * pg)
1005{
1006 gfx_instance *gfx = PARENT(e, gfx_instance, export);
1007 vbi_rgba *rgba_image;
1008 const vbi_rgba *rgba_row_buffer;
1009 unsigned int image_width; /* in pixels */
1010 unsigned int image_height;
1011 unsigned int char_width; /* in pixels */
1012 unsigned int char_height;
1013 unsigned int scale;
1014 unsigned int row;
1015 size_t rgba_row_size;
1016 size_t ppm_row_size;
1017 size_t needed;
1018 vbi_bool result;
1019
1020 rgba_image = NULL;
1021 result = FALSE;
1022
1023 if (pg->columns < 40) /* caption */ {
1024 char_width = CCW;
1025 char_height = CCH;
1026 /* Characters are already line-doubled. */
1027 scale = !!gfx->double_height;
1028 } else {
1029 char_width = TCW;
1030 char_height = TCH;
1031 scale = 1 + !!gfx->double_height;
1032 }
1033
1034 image_width = char_width * pg->columns;
1035 image_height = ((char_height * pg->rows) << scale) >> 1;
1036
1037 rgba_row_size = image_width * char_height;
1038 ppm_row_size = ((rgba_row_size << scale) >> 1) * 3;
1039 rgba_row_size *= sizeof (vbi_rgba);
1040
1041 if (VBI_EXPORT_TARGET_MEM == e->target) {
1042 if (!vbi_export_printf (e, "P6 %u %u 255\n",
1043 image_width, image_height))
1044 goto failed;
1045
1046 /* Check in advance if enough space is available for
1047 the rest of the PPM image. */
1048 needed = ppm_row_size * pg->rows;
1049 if (!_vbi_export_grow_buffer_space (e, needed))
1050 goto failed;
1051
1052 rgba_image = malloc (rgba_row_size);
1053 if (NULL == rgba_image) {
1054 _vbi_export_malloc_error (e);
1055 goto failed;
1056 }
1057
1058 rgba_row_buffer = rgba_image;
1059 } else {
1060 size_t margin;
1061
1062 /* vbi_export_printf() and _vbi_export_grow_buffer_space()
1063 allocate more buffer memory as needed, but for
1064 efficiency we estimate the required space and
1065 allocate it all in advance. We use the same buffer
1066 for the RGBA and PPM image. One row is enough as we
1067 flush() after each row. It should be enough for the
1068 header too, if it is buffered at all. Otherwise
1069 vbi_export_printf() below will allocate more memory. */
1070 margin = (2 == scale) ? image_width * sizeof (vbi_rgba): 0;
1071 needed = MAX (rgba_row_size - margin,
1072 ppm_row_size) + margin;
1073
1074 if (VBI_EXPORT_TARGET_ALLOC == e->target) {
1075 /* The buffer must hold the entire PPM image.
1076 When we're done vbi_export_alloc() will
1077 truncate it with realloc(). */
1078 needed += 64; /* max. header size */
1079 needed += ppm_row_size * (pg->rows - 1);
1080 }
1081
1082 if (!_vbi_export_grow_buffer_space (e, needed))
1083 goto failed;
1084
1085 if (!vbi_export_printf (e, "P6 %u %u 255\n",
1086 image_width, image_height))
1087 goto failed;
1088
1089 if (!vbi_export_flush (e))
1090 goto failed;
1091
1092 rgba_row_buffer = (const vbi_rgba *)
1093 (e->buffer.data
1094 + ((e->buffer.capacity - rgba_row_size)
1095 & -sizeof (*rgba_row_buffer))); /* align */
1096 }
1097
1098 for (row = 0; row < (unsigned int) pg->rows; ++row) {
1099 uint8_t *d;
1100 uint8_t *d_end;
1101 const vbi_rgba *s;
1102 unsigned int count;
1103
1104 if (pg->columns < 40) {
1105 vbi_draw_cc_page_region (pg, VBI_PIXFMT_RGBA32_LE,
1106 rgba_row_buffer,
1107 /* rowstride: auto */ -1,
1108 /* column */ 0, row,
1109 pg->columns, /* rows */ 1);
1110 } else {
1111 vbi_draw_vt_page_region (pg, VBI_PIXFMT_RGBA32_LE,
1112 rgba_row_buffer,
1113 /* rowstride: auto */ -1,
1114 /* column */ 0, row,
1115 pg->columns, /* rows */ 1,
1116 /* reveal */ !e->reveal,
1117 /* flash_on */ TRUE, FALSE);
1118 }
1119
1120 d = (uint8_t *) e->buffer.data + e->buffer.offset;
1121 s = rgba_row_buffer;
1122
1123 switch (scale) {
1124 case 0:
1125 count = char_height >> 1;
1126 do {
1127 d_end = d + image_width * 3;
1128 do {
1129 vbi_rgba n1 = s[image_width];
1130 vbi_rgba n0 = *s++;
1131
1132 d[0] = ((n0 & 0xFF) + (n1 & 0xFF)
1133 + 0x01) >> 1;
1134 d[1] = ((n0 & 0xFF00) + (n1 & 0xFF00)
1135 + 0x0100) >> 9;
1136 d[2] = ((n0 & 0xFF0000)
1137 + (n1 & 0xFF0000)
1138 + 0x010000) >> 17;
1139 d += 3;
1140 } while (d < d_end);
1141
1142 s += image_width;
1143 } while (--count > 0);
1144
1145 break;
1146
1147 case 1:
1148 d_end = d + image_width * char_height * 3;
1149 do {
1150 vbi_rgba n = *s++;
1151
1152 d[0] = n;
1153 d[1] = n >> 8;
1154 d[2] = n >> 16;
1155 d += 3;
1156 } while (d < d_end);
1157
1158 break;
1159
1160 case 2:
1161 count = char_height;
1162 do {
1163 d_end = d + image_width * 3;
1164 do {
1165 vbi_rgba n = *s++;
1166
1167 d[0] = n;
1168 d[1] = n >> 8;
1169 d[2] = n >> 16;
1170 d[image_width * 3 + 0] = n;
1171 d[image_width * 3 + 1] = n >> 8;
1172 d[image_width * 3 + 2] = n >> 16;
1173 d += 3;
1174 } while (d < d_end);
1175
1176 d += image_width * 3;
1177 } while (--count > 0);
1178
1179 break;
1180
1181 default:
1182 assert (0);
1183 }
1184
1185 e->buffer.offset = (char *) d - e->buffer.data;
1186
1187 if (!vbi_export_flush (e))
1188 goto failed;
1189 }
1190
1191 result = TRUE;
1192
1193 failed:
1194 free (rgba_image);
1195
1196 return result;
1197}
1198
1199static vbi_export_info
1200info_ppm = {
1201 .keyword = "ppm",
1202 .label = N_("PPM"),
1203 .tooltip = N_("Export this page as raw PPM image"),
1204
1205 .mime_type = "image/x-portable-pixmap",
1206 .extension = "ppm",
1207};
1208
1209vbi_export_class
1210vbi_export_class_ppm = {
1211 ._public = &info_ppm,
1212 ._new = gfx_new,
1213 ._delete = gfx_delete,
1214 .option_enum = option_enum_ppm,
1215 .option_get = option_get,
1216 .option_set = option_set,
1217 .export = ppm_export
1218};
1219
1220VBI_AUTOREG_EXPORT_MODULE(vbi_export_class_ppm)
1221
1222
1223/*
1224 * PNG and XPM drawing functions (palette-based)
1225 */
1226static void
1227draw_char_cc_indexed(uint8_t * canvas, int rowstride, uint8_t * pen,
1228 int unicode, vbi_char *ac)
1229{
1230 draw_char(sizeof(*canvas), canvas, rowstride,
1231 pen, (uint8_t *) ccfont2_bits, CCPL, CCW, CCH,
1232 unicode_ccfont2(unicode, ac->italic), 0 /* bold */,
1233 ac->underline * (3 << 24) /* cell row 24, 25 */,
1234 VBI_NORMAL_SIZE);
1235}
1236
1237static void
1238draw_char_vt_indexed(uint8_t * canvas, int rowstride, uint8_t * pen,
1239 int unicode, vbi_char *ac)
1240{
1241 draw_char(sizeof(*canvas), canvas, rowstride,
1242 pen, (uint8_t *) wstfont2_bits, TCPL, TCW, TCH,
1243 unicode_wstfont2(unicode, ac->italic), ac->bold,
1244 ac->underline << 9 /* cell row 9 */, ac->size);
1245}
1246
1247static void
1248draw_drcs_indexed(uint8_t * canvas, int rowstride, uint8_t * pen,
1249 uint8_t *font, int glyph, vbi_size size)
1250{
1251 draw_drcs(sizeof(*canvas), canvas, rowstride,
1252 (uint8_t *) pen, 0, font, glyph, size);
1253}
1254
1255static void
1256draw_row_indexed(vbi_page * pg, vbi_char * ac, uint8_t * canvas, uint8_t * pen,
1257 int rowstride, vbi_bool conceal, vbi_bool is_cc)
1258{
1259 const int cw = is_cc ? CCW : TCW;
1260 const int ch = is_cc ? CCH : TCH;
1261 void (* draw_char_indexed)(uint8_t *, int, uint8_t *, int, vbi_char *)
1262 = is_cc ? draw_char_cc_indexed : draw_char_vt_indexed;
1263 int column;
1264 int unicode;
1265
1266 for (column = 0; column < pg->columns ; canvas += cw, column++, ac++) {
1267
1268 if (ac->size == VBI_OVER_TOP
1269 || ac->size == VBI_OVER_BOTTOM)
1270 continue;
1271
1272 unicode = (ac->conceal & conceal) ? 0x0020u : ac->unicode;
1273
1274 switch (ac->opacity) {
1275 case VBI_TRANSPARENT_SPACE:
1276 /*
1277 * Transparent foreground and background.
1278 */
1279 draw_blank(sizeof(*canvas), canvas,
1280 rowstride, VBI_TRANSPARENT_BLACK, cw, ch);
1281 break;
1282
1283 case VBI_TRANSPARENT_FULL:
1284 /*
1285 * Transparent background, opaque foreground. Currently not used.
1286 * Mind Teletext level 2.5 foreground and background transparency
1287 * by referencing colormap entry 8, VBI_TRANSPARENT_BLACK.
1288 * The background of multicolor DRCS is ambiguous, so we make
1289 * them opaque.
1290 */
1291 if (vbi_is_drcs(unicode)) {
1292 uint8_t *font = pg->drcs[(unicode >> 6) & 0x1F];
1293
1294 pen[0] = VBI_TRANSPARENT_BLACK;
1295 pen[1] = ac->foreground;
1296
1297 if (font && !is_cc)
1298 draw_drcs_indexed(canvas, rowstride, pen,
1299 font, unicode & 0x3F, ac->size);
1300 else /* shouldn't happen */
1301 draw_blank(sizeof(*canvas), (uint8_t *) canvas,
1302 rowstride, VBI_TRANSPARENT_BLACK, cw, ch);
1303 } else {
1304 pen[0] = VBI_TRANSPARENT_BLACK;
1305 pen[1] = ac->foreground;
1306
1307 draw_char_indexed(canvas, rowstride, pen, unicode, ac);
1308 }
1309
1310 break;
1311
1312 case VBI_SEMI_TRANSPARENT:
1313 /*
1314 * Translucent background (for 'boxed' text), opaque foreground.
1315 * The background of multicolor DRCS is ambiguous, so we make
1316 * them completely translucent.
1317 */
1318 if (vbi_is_drcs(unicode)) {
1319 uint8_t *font = pg->drcs[(unicode >> 6) & 0x1F];
1320
1321 pen[64] = ac->background + 40;
1322 pen[65] = ac->foreground;
1323
1324 if (font && !is_cc)
1325 draw_drcs_indexed(canvas, rowstride,
1326 (uint8_t *)(pen + 64),
1327 font, unicode & 0x3F, ac->size);
1328 else /* shouldn't happen */
1329 draw_blank(sizeof(*canvas), (uint8_t *) canvas,
1330 rowstride, VBI_TRANSPARENT_BLACK, cw, ch);
1331 } else {
1332 pen[0] = ac->background + 40; /* translucent */
1333 pen[1] = ac->foreground;
1334
1335 draw_char_indexed(canvas, rowstride, pen, unicode, ac);
1336 }
1337
1338 break;
1339
1340 case VBI_OPAQUE:
1341 pen[0] = ac->background;
1342 pen[1] = ac->foreground;
1343
1344 if (vbi_is_drcs(unicode)) {
1345 uint8_t *font = pg->drcs[(unicode >> 6) & 0x1F];
1346
1347 if (font && !is_cc)
1348 draw_drcs_indexed(canvas, rowstride, pen,
1349 font, unicode & 0x3F, ac->size);
1350 else /* shouldn't happen */
1351 draw_blank(sizeof(*canvas), (uint8_t *) canvas,
1352 rowstride, pen[0], cw, ch);
1353 } else
1354 draw_char_indexed(canvas, rowstride, pen, unicode, ac);
1355 break;
1356 }
1357 }
1358}
1359
1360/*
1361 * XPM - X Pixmap
1362 *
1363 * According to "XPM Manual" version 3.4i, 1996-09-10, by Arnaud Le Hors
1364 */
1365
1366static const uint8_t xpm_col_codes[40] =
1367 " 1234567.BCDEFGHIJKLMNOPabcdefghijklmnop";
1368
1369/**
1370 * @internal
1371 * @param e Export context
1372 * @param pg Page reference
1373 * @param image_width Image width
1374 * @param image_height Image height
1375 * @param title Optional title to be included in extension data, or @c NULL.
1376 * @param creator Optional software name and version to be included in
1377 * extension data, or @c NULL.
1378 *
1379 * This function writes an XPM header and color palette for an image with
1380 * the given size and optional extension data. The color palette is
1381 * retrieved from the page referenced by @a pg.
1382 *
1383 * @returns
1384 * @c FALSE on error.
1385 */
1386static vbi_bool
1387xpm_write_header (vbi_export * e,
1388 const vbi_page * pg,
1389 unsigned int image_width,
1390 unsigned int image_height,
1391 const char * title,
1392 const char * creator)
1393{
1394 gfx_instance *gfx = PARENT(e, gfx_instance, export);
1395 vbi_bool do_ext = ((NULL != title) && (0 != title[0]))
1396 || ((NULL != creator) && (0 != creator[0]));
1397 unsigned int i;
1398
1399 /* Warning: adapt buf size estimation when changing the text! */
1400 vbi_export_printf (e,
1401 "/* XPM */\n"
1402 "static char *image[] = {\n"
1403 "/* width height ncolors chars_per_pixel */\n"
1404 "\"%d %d %d %d%s\",\n"
1405 "/* colors */\n",
1406 image_width, image_height, 40, 1,
1407 do_ext ? " XPMEXT":"");
1408
1409 /* Write color palette (including unused colors
1410 - could be optimized). */
1411 for (i = 0; i < 40; ++i) {
1412 if ((8 == i) && gfx->transparency) {
1413 vbi_export_printf (e,
1414 "\"%c c None\",\n",
1415 xpm_col_codes[i]);
1416 } else {
1417 vbi_export_printf (e,
1418 "\"%c c #%02X%02X%02X\",\n",
1419 xpm_col_codes[i],
1420 pg->color_map[i] & 0xFF,
1421 (pg->color_map[i] >> 8) & 0xFF,
1422 (pg->color_map[i] >> 16) & 0xFF);
1423 }
1424 }
1425
1426 vbi_export_printf (e, "/* pixels */\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 title Optional title to be included in extension data, or @c NULL.
1437 * @param creator Optional software name and version to be included in
1438 * extension data, or @c NULL.
1439 *
1440 * This function writes an XPM "footer" (i.e. anything following the actual
1441 * image data) and optionally appends image title and software names as
1442 * extension data.
1443 *
1444 * @returns
1445 * @c FALSE on error.
1446 */
1447static vbi_bool
1448xpm_write_footer (vbi_export * e,
1449 const char * title,
1450 const char * creator)
1451{
1452 char *p;
1453
1454 if ( ((NULL != title) && (0 != title[0]))
1455 || ((NULL != creator) && (0 != creator[0])) ) {
1456
1457 /* Warning: adapt buf size estimation when changing the text! */
1458 if (NULL != title && 0 != title[0]) {
1459 while ((p = strchr(title, '"')) != NULL)
1460 *p = '\'';
1461 vbi_export_printf (e, "\"XPMEXT title %s\",\n", title);
1462 }
1463
1464 if (NULL != creator && 0 != creator[0]) {
1465 while ((p = strchr(creator, '"')) != NULL)
1466 *p = '\'';
1467 vbi_export_printf (e, "\"XPMEXT software %s\",\n", creator);
1468 }
1469
1470 vbi_export_printf (e, "\"XPMENDEXT\"\n");
1471 }
1472
1473 vbi_export_printf (e, "};\n");
1474
1475 /* Note this also returns FALSE if any of the
1476 vbi_export_printf() calls above failed. */
1477 return vbi_export_flush (e);
1478}
1479
1480/**
1481 * @internal
1482 * @param e Export context
1483 * @param s Image source data as written by draw_row_indexed()
1484 * @param image_width Image width
1485 * @param char_height Character height
1486 * @param scale Scale image:
1487 * - 0 to half height
1488 * - 1 to normal height
1489 * - 2 to double height
1490 *
1491 * This function writes XPM image data for one Teletext or Closed Caption
1492 * row (i.e. several pixel lines) into the given buffer. The image is scaled
1493 * vertically on the fly.
1494 *
1495 * The conversion consists of adding C-style framing at the start and end of
1496 * each pixel row and converting "binary" palette indices into color code
1497 * characters. CLUT 1 color 0 is hard-coded as transparent; semi-transparent
1498 * colors are also mapped to the transparent color code since XPM does not
1499 * have an alpha channel.
1500 *
1501 * @returns
1502 * @c FALSE on error.
1503 */
1504static vbi_bool
1505xpm_write_row (vbi_export * e,
1506 const uint8_t * s,
1507 unsigned int image_width,
1508 unsigned int char_height,
1509 unsigned int scale)
1510{
1511 size_t needed;
1512 char *d;
1513
1514 needed = (((image_width + 4) * char_height) << scale) >> 1;
1515 if (unlikely (!_vbi_export_grow_buffer_space (e, needed)))
1516 return FALSE;
1517
1518 d = e->buffer.data + e->buffer.offset;
1519
1520 do {
1521 char *d_end;
1522
1523 *d++ = '"';
1524
1525 d_end = d + image_width;
1526 do {
1527 uint8_t c = *s++;
1528
1529 if (c < sizeof (xpm_col_codes)) {
1530 *d++ = xpm_col_codes[c];
1531 } else {
1532 *d++ = '.'; /* transparent */
1533 }
1534 } while (d < d_end);
1535
1536 d[0] = '"';
1537 d[1] = ',';
1538 d[2] = '\n';
1539 d += 3;
1540
1541 if (0 == scale) {
1542 /* Scale down - use every 2nd row. */
1543 --char_height;
1544 s += image_width;
1545 } else if (2 == scale) {
1546 /* Scale up - double each line. */
1547 memcpy (d, d - image_width - 4, image_width + 4);
1548 d += image_width + 4;
1549 }
1550 } while (--char_height > 0);
1551
1552 e->buffer.offset = d - e->buffer.data;
1553
1554 return vbi_export_flush (e);
1555}
1556
1557static vbi_bool
1558xpm_export (vbi_export * e,
1559 vbi_page * pg)
1560{
1561 gfx_instance *gfx = PARENT(e, gfx_instance, export);
1562 uint8_t pen[128];
1563 char title[80];
1564 uint8_t *indexed_image;
1565 unsigned int image_width;
1566 unsigned int image_height;
1567 unsigned int char_width;
1568 unsigned int char_height;
1569 unsigned int scale;
1570 unsigned int row;
1571 vbi_bool result;
1572
1573 indexed_image = NULL;
1574 result = FALSE;
1575
1576 if (pg->columns < 40) /* caption */ {
1577 char_width = CCW;
1578 char_height = CCH;
1579 /* Characters are already line-doubled. */
1580 scale = !!gfx->double_height;
1581 } else {
1582 char_width = TCW;
1583 char_height = TCH;
1584 scale = 1 + !!gfx->double_height;
1585 }
1586
1587 image_width = char_width * pg->columns;
1588 image_height = ((char_height * pg->rows) << scale) >> 1;
1589
1590 get_image_title (e, pg, title, sizeof (title));
1591
1592 if (pg->drcs_clut) {
1593 unsigned int i;
1594
1595 for (i = 2; i < 2 + 8 + 32; i++) {
1596 pen[i] = pg->drcs_clut[i]; /* opaque */
1597 pen[i + 64] = 40; /* translucent */
1598 }
1599 }
1600
1601 indexed_image = malloc (image_width * char_height);
1602 if (unlikely (NULL == indexed_image)) {
1603 _vbi_export_malloc_error (e);
1604 goto failed;
1605 }
1606
1607 switch (e->target) {
1608 size_t header_size;
1609 size_t footer_size;
1610 size_t xpm_row_size;
1611 size_t needed;
1612
1613 case VBI_EXPORT_TARGET_MEM:
1614 /* vbi_export_printf() and _vbi_export_grow_buffer_space()
1615 will check on the fly if enough space is available. */
1616 break;
1617
1618 case VBI_EXPORT_TARGET_FP:
1619 /* Header and footer are not e->buffered and we allocate
1620 the XPM data buffer once in xpm_write_row(). */
1621 break;
1622
1623 default:
1624 /* vbi_export_printf() and _vbi_export_grow_buffer_space()
1625 allocate more buffer memory as needed, but for
1626 efficiency we estimate the XPM image size and
1627 allocate it all in advance. */
1628
1629 header_size = 109 /* header (incl. 4-digit width
1630 and height and 2-digit col num) */
1631 + 15 * 40 /* color palette with 40 members */
1632 + 13; /* row start comment */
1633 if (gfx->transparency)
1634 header_size -= 7 - 4; /* "#RRGGBB" - "None" */
1635 xpm_row_size = (((image_width + 4) * char_height)
1636 << scale) >> 1;
1637 footer_size = 3; /* closing bracket */
1638
1639 if ( ((NULL != title) && (0 != title[0]))
1640 || ((NULL != e->creator) && (0 != e->creator[0])) ) {
1641 header_size += 7; /* XPMEXT keyword */
1642 footer_size += 12; /* XPMENDEXT keyword */
1643 if (NULL != title) {
1644 /* XPMEXT keywords + label + content */
1645 footer_size += 17 + strlen (title);
1646 }
1647 if (NULL != e->creator)
1648 footer_size += 20 + strlen (e->creator);
1649 }
1650
1651 if (VBI_EXPORT_TARGET_ALLOC == e->target) {
1652 needed = header_size + footer_size
1653 + xpm_row_size * pg->rows;
1654 } else {
1655 /* We flush() after writing header, footer and
1656 each row. */
1657 needed = MAX (header_size, footer_size);
1658 needed = MAX (needed, xpm_row_size);
1659 }
1660
1661 if (unlikely (!_vbi_export_grow_buffer_space (e, needed)))
1662 return FALSE;
1663 }
1664
1665 if (!xpm_write_header (e, pg, image_width, image_height,
1666 title, e->creator))
1667 goto failed;
1668
1669 for (row = 0; row < (unsigned int) pg->rows; ++row) {
1670 draw_row_indexed (pg, &pg->text[row * pg->columns],
1671 indexed_image, pen, image_width,
1672 !e->reveal, pg->columns < 40);
1673
1674 if (!xpm_write_row (e, indexed_image,
1675 image_width, char_height, scale))
1676 goto failed;
1677 }
1678
1679 if (!xpm_write_footer (e, title, e->creator))
1680 goto failed;
1681
1682 result = TRUE;
1683
1684 failed:
1685 free (indexed_image);
1686
1687 return result;
1688}
1689
1690static vbi_export_info
1691info_xpm = {
1692 .keyword = "xpm",
1693 .label = N_("XPM"),
1694 .tooltip = N_("Export this page as XPM image"),
1695
1696 .mime_type = "image/xpm",
1697 .extension = "xpm",
1698};
1699
1700vbi_export_class
1701vbi_export_class_xpm = {
1702 ._public = &info_xpm,
1703 ._new = gfx_new,
1704 ._delete = gfx_delete,
1705 .option_enum = option_enum,
1706 .option_get = option_get,
1707 .option_set = option_set,
1708 .export = xpm_export
1709};
1710
1711VBI_AUTOREG_EXPORT_MODULE(vbi_export_class_xpm)
1712
1713
1714/*
1715 * PNG - Portable Network Graphics File
1716 */
1717#ifdef HAVE_LIBPNG
1718
1719#include "png.h"
1720#include "setjmp.h"
1721
1722static void
1723write_data (png_structp png_ptr,
1724 png_bytep data,
1725 png_size_t length)
1726{
1727 gfx_instance *gfx = (gfx_instance *) png_get_io_ptr (png_ptr);
1728
1729 vbi_export_write (&gfx->export, data, length);
1730}
1731
1732static void
1733flush_data (png_structp png_ptr)
1734{
1735 gfx_instance *gfx = (gfx_instance *) png_get_io_ptr (png_ptr);
1736
1737 vbi_export_flush (&gfx->export);
1738}
1739
1740static vbi_bool
1741write_png (gfx_instance * gfx,
1742 const vbi_page * pg,
1743 png_structp png_ptr,
1744 png_infop info_ptr,
1745 png_bytep image,
1746 png_bytep * row_pointer,
1747 unsigned int ww,
1748 unsigned int wh,
1749 unsigned int scale)
1750{
1751 png_color palette[80];
1752 png_byte alpha[80];
1753 png_text text[4];
1754 char title[80];
1755 unsigned int i;
1756
1757 if (setjmp (png_ptr->jmpbuf))
1758 return FALSE;
1759
1760 png_set_write_fn (png_ptr,
1761 (voidp) gfx,
1762 write_data,
1763 flush_data);
1764
1765 png_set_IHDR (png_ptr,
1766 info_ptr,
1767 ww,
1768 (wh << scale) >> 1,
1769 /* bit_depth */ 8,
1770 PNG_COLOR_TYPE_PALETTE,
1771 gfx->double_height ?
1772 PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE,
1773 PNG_COMPRESSION_TYPE_DEFAULT,
1774 PNG_FILTER_TYPE_DEFAULT);
1775
1776 /* Could be optimized (or does libpng?) */
1777 for (i = 0; i < 40; i++) {
1778 /* opaque */
1779 palette[i].red = pg->color_map[i] & 0xFF;
1780 palette[i].green = (pg->color_map[i] >> 8) & 0xFF;
1781 palette[i].blue = (pg->color_map[i] >> 16) & 0xFF;
1782 alpha[i] = 255;
1783
1784 /* translucent */
1785 palette[i + 40] = palette[i];
1786 alpha[i + 40] = 128;
1787 }
1788
1789 alpha[VBI_TRANSPARENT_BLACK] = 0;
1790 alpha[40 + VBI_TRANSPARENT_BLACK] = 0;
1791
1792 png_set_PLTE (png_ptr, info_ptr, palette, 80);
1793
1794 if (gfx->transparency)
1795 png_set_tRNS (png_ptr, info_ptr, alpha, 80, NULL);
1796
1797 png_set_gAMA (png_ptr, info_ptr, 1.0 / 2.2);
1798
1799 get_image_title (&gfx->export, pg, title, sizeof (title));
1800
1801 CLEAR (text);
1802
1803 i = 0;
1804 if (0 != title[0]) {
1805 text[i].key = "Title";
1806 text[i].text = title;
1807 text[i].compression = PNG_TEXT_COMPRESSION_NONE;
1808 i++;
1809 }
1810 if (NULL != gfx->export.creator && 0 != gfx->export.creator[0]) {
1811 text[i].key = "Software";
1812 text[i].text = gfx->export.creator;
1813 text[i].compression = PNG_TEXT_COMPRESSION_NONE;
1814 i++;
1815 }
1816 png_set_text (png_ptr, info_ptr, text, i);
1817
1818 png_write_info (png_ptr, info_ptr);
1819
1820 switch (scale) {
1821 case 0:
1822 for (i = 0; i < wh / 2; i++)
1823 row_pointer[i] = image + i * 2 * ww;
1824 break;
1825
1826 case 1:
1827 for (i = 0; i < wh; i++)
1828 row_pointer[i] = image + i * ww;
1829 break;
1830
1831 case 2:
1832 for (i = 0; i < wh; i++)
1833 row_pointer[i * 2 + 0] =
1834 row_pointer[i * 2 + 1] = image + i * ww;
1835 break;
1836 }
1837
1838 png_write_image (png_ptr, row_pointer);
1839
1840 png_write_end (png_ptr, info_ptr);
1841
1842 return TRUE;
1843}
1844
1845static vbi_bool
1846png_export(vbi_export *e, vbi_page *pg)
1847{
1848 gfx_instance *gfx = PARENT(e, gfx_instance, export);
1849 png_structp png_ptr;
1850 png_infop info_ptr;
1851 uint8_t pen[128];
1852 png_bytep *row_pointer;
1853 png_bytep image;
1854 int ww, wh, rowstride, row_adv, scale;
1855 int row;
1856 int i;
1857
1858 assert ((sizeof(png_byte) == sizeof(uint8_t))
1859 && (sizeof(*image) == sizeof(uint8_t)));
1860
1861 if (pg->columns < 40) /* caption */ {
1862 /* characters are already line-doubled */
1863 scale = !!gfx->double_height;
1864 ww = CCW * pg->columns;
1865 wh = CCH * pg->rows;
1866 row_adv = pg->columns * CCW * CCH;
1867 } else {
1868 scale = 1 + !!gfx->double_height;
1869 ww = TCW * pg->columns;
1870 wh = TCH * pg->rows;
1871 row_adv = pg->columns * TCW * TCH;
1872 }
1873
1874 rowstride = ww * sizeof(*image);
1875
1876 if (!(row_pointer = malloc(sizeof(*row_pointer) * wh * 2))) {
1877 vbi_export_error_printf(e, _("Unable to allocate %d byte buffer."),
1878 sizeof(*row_pointer) * wh * 2);
1879 return FALSE;
1880 }
1881
1882 if (!(image = malloc(wh * ww * sizeof(*image)))) {
1883 vbi_export_error_printf(e, _("Unable to allocate %d KB image buffer."),
1884 wh * ww * sizeof(*image) / 1024);
1885 free(row_pointer);
1886 return FALSE;
1887 }
1888
1889 /* draw the image */
1890
1891 if (pg->drcs_clut) {
1892 for (i = 2; i < 2 + 8 + 32; i++) {
1893 pen[i] = pg->drcs_clut[i]; /* opaque */
1894 pen[i + 64] = pg->drcs_clut[i] + 40; /* translucent */
1895 }
1896 }
1897
1898 for (row = 0; row < pg->rows; row++) {
1899 draw_row_indexed(pg, &pg->text[row * pg->columns],
1900 image + row * row_adv, pen, rowstride,
1901 !e->reveal, pg->columns < 40);
1902 }
1903
1904 /* Now save the image */
1905
1906 if (!(png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
1907 NULL, NULL, NULL)))
1908 goto unknown_error;
1909
1910 if (!(info_ptr = png_create_info_struct(png_ptr))) {
1911 png_destroy_write_struct(&png_ptr, (png_infopp) NULL);
1912 goto unknown_error;
1913 }
1914
1915 if (!write_png (gfx, pg, png_ptr, info_ptr,
1916 image, row_pointer, ww, wh, scale)) {
1917 png_destroy_write_struct (&png_ptr, &info_ptr);
1918 goto write_error;
1919 }
1920
1921 png_destroy_write_struct (&png_ptr, &info_ptr);
1922
1923 if (gfx->export.write_error)
1924 goto failed;
1925
1926 free (row_pointer);
1927
1928 free (image);
1929
1930 return TRUE;
1931
1932write_error:
1933 vbi_export_write_error(e);
1934
1935unknown_error:
1936failed:
1937 free (row_pointer);
1938
1939 free (image);
1940
1941 return FALSE;
1942}
1943
1944static vbi_export_info
1945info_png = {
1946 .keyword = "png",
1947 .label = N_("PNG"),
1948 .tooltip = N_("Export this page as PNG image"),
1949
1950 .mime_type = "image/png",
1951 .extension = "png",
1952};
1953
1954vbi_export_class
1955vbi_export_class_png = {
1956 ._public = &info_png,
1957 ._new = gfx_new,
1958 ._delete = gfx_delete,
1959 .option_enum = option_enum,
1960 .option_get = option_get,
1961 .option_set = option_set,
1962 .export = png_export
1963};
1964
1965VBI_AUTOREG_EXPORT_MODULE(vbi_export_class_png)
1966
1967#endif /* HAVE_LIBPNG */
1968
1969/*
1970Local variables:
1971c-set-style: K&R
1972c-basic-offset: 8
1973End:
1974*/
1975