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 | |
63 | static void init_gfx(void) __attribute__ ((constructor)); |
64 | |
65 | static void |
66 | init_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 | */ |
108 | static unsigned int |
109 | unicode_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; |
167 | special: |
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 | */ |
189 | static unsigned int |
190 | unicode_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 | |
213 | slant: |
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 | */ |
271 | static inline void |
272 | draw_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 | */ |
396 | static inline void |
397 | draw_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 | */ |
481 | static inline void |
482 | draw_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 | */ |
510 | void |
511 | vbi_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 | */ |
622 | void |
623 | vbi_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 | */ |
801 | void |
802 | vbi_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 | */ |
815 | void |
816 | vbi_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 | |
826 | typedef 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 | |
856 | static vbi_export * |
857 | gfx_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 | |
867 | static void |
868 | gfx_delete(vbi_export *e) |
869 | { |
870 | free(PARENT(e, gfx_instance, export)); |
871 | } |
872 | |
873 | |
874 | static vbi_option_info |
875 | gfx_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 | |
893 | static vbi_option_info * |
894 | option_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 | |
904 | static vbi_option_info * |
905 | option_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 | |
915 | static vbi_bool |
916 | option_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 | |
934 | static vbi_bool |
935 | option_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 | */ |
963 | static void |
964 | get_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 | |
1002 | static vbi_bool |
1003 | ppm_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 | |
1199 | static vbi_export_info |
1200 | info_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 | |
1209 | vbi_export_class |
1210 | vbi_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 | |
1220 | VBI_AUTOREG_EXPORT_MODULE(vbi_export_class_ppm) |
1221 | |
1222 | |
1223 | /* |
1224 | * PNG and XPM drawing functions (palette-based) |
1225 | */ |
1226 | static void |
1227 | draw_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 | |
1237 | static void |
1238 | draw_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 | |
1247 | static void |
1248 | draw_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 | |
1255 | static void |
1256 | draw_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 | |
1366 | static 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 | */ |
1386 | static vbi_bool |
1387 | xpm_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 | */ |
1447 | static vbi_bool |
1448 | xpm_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 | */ |
1504 | static vbi_bool |
1505 | xpm_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 | |
1557 | static vbi_bool |
1558 | xpm_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 | |
1690 | static vbi_export_info |
1691 | info_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 | |
1700 | vbi_export_class |
1701 | vbi_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 | |
1711 | VBI_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 | |
1722 | static void |
1723 | write_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 | |
1732 | static void |
1733 | flush_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 | |
1740 | static vbi_bool |
1741 | write_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 | |
1845 | static vbi_bool |
1846 | png_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 | |
1932 | write_error: |
1933 | vbi_export_write_error(e); |
1934 | |
1935 | unknown_error: |
1936 | failed: |
1937 | free (row_pointer); |
1938 | |
1939 | free (image); |
1940 | |
1941 | return FALSE; |
1942 | } |
1943 | |
1944 | static vbi_export_info |
1945 | info_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 | |
1954 | vbi_export_class |
1955 | vbi_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 | |
1965 | VBI_AUTOREG_EXPORT_MODULE(vbi_export_class_png) |
1966 | |
1967 | #endif /* HAVE_LIBPNG */ |
1968 | |
1969 | /* |
1970 | Local variables: |
1971 | c-set-style: K&R |
1972 | c-basic-offset: 8 |
1973 | End: |
1974 | */ |
1975 |