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