1 //========================================================================
2 //
3 // SplashBitmap.cc
4 //
5 //========================================================================
6
7 //========================================================================
8 //
9 // Modified under the Poppler project - http://poppler.freedesktop.org
10 //
11 // All changes made under the Poppler project to this file are licensed
12 // under GPL version 2 or later
13 //
14 // Copyright (C) 2006, 2009, 2010, 2012, 2015 Albert Astals Cid <aacid@kde.org>
15 // Copyright (C) 2007 Ilmari Heikkinen <ilmari.heikkinen@gmail.com>
16 // Copyright (C) 2009 Shen Liang <shenzhuxi@gmail.com>
17 // Copyright (C) 2009 Stefan Thomas <thomas@eload24.com>
18 // Copyright (C) 2010, 2012 Adrian Johnson <ajohnson@redneon.com>
19 // Copyright (C) 2010 Harry Roberts <harry.roberts@midnight-labs.org>
20 // Copyright (C) 2010 Christian Feuers�nger <cfeuersaenger@googlemail.com>
21 // Copyright (C) 2010 William Bader <williambader@hotmail.com>
22 // Copyright (C) 2011-2013 Thomas Freitag <Thomas.Freitag@alfa.de>
23 // Copyright (C) 2012 Anthony Wesley <awesley@smartnetworks.com.au>
24 //
25 // To see a description of the changes please see the Changelog file that
26 // came with your tarball or type make ChangeLog if you are building from git
27 //
28 //========================================================================
29
30 #include <config.h>
31
32 #ifdef USE_GCC_PRAGMAS
33 #pragma implementation
34 #endif
35
36 #include <stdio.h>
37 #include <string.h>
38 #include <stdlib.h>
39 #include <limits.h>
40 #include "goo/gmem.h"
41 #include "SplashErrorCodes.h"
42 #include "SplashBitmap.h"
43 #include "poppler/Error.h"
44 #include "goo/JpegWriter.h"
45 #include "goo/PNGWriter.h"
46 #include "goo/TiffWriter.h"
47 #include "goo/ImgWriter.h"
48 #include "goo/GooList.h"
49
50 //------------------------------------------------------------------------
51 // SplashBitmap
52 //------------------------------------------------------------------------
53
SplashBitmap(int widthA,int heightA,int rowPadA,SplashColorMode modeA,GBool alphaA,GBool topDown,GooList * separationListA)54 SplashBitmap::SplashBitmap(int widthA, int heightA, int rowPadA,
55 SplashColorMode modeA, GBool alphaA,
56 GBool topDown, GooList *separationListA) {
57 width = widthA;
58 height = heightA;
59 mode = modeA;
60 rowPad = rowPadA;
61 switch (mode) {
62 case splashModeMono1:
63 if (width > 0) {
64 rowSize = (width + 7) >> 3;
65 } else {
66 rowSize = -1;
67 }
68 break;
69 case splashModeMono8:
70 if (width > 0) {
71 rowSize = width;
72 } else {
73 rowSize = -1;
74 }
75 break;
76 case splashModeRGB8:
77 case splashModeBGR8:
78 if (width > 0 && width <= INT_MAX / 3) {
79 rowSize = width * 3;
80 } else {
81 rowSize = -1;
82 }
83 break;
84 case splashModeXBGR8:
85 if (width > 0 && width <= INT_MAX / 4) {
86 rowSize = width * 4;
87 } else {
88 rowSize = -1;
89 }
90 break;
91 #if SPLASH_CMYK
92 case splashModeCMYK8:
93 if (width > 0 && width <= INT_MAX / 4) {
94 rowSize = width * 4;
95 } else {
96 rowSize = -1;
97 }
98 break;
99 case splashModeDeviceN8:
100 if (width > 0 && width <= INT_MAX / 4) {
101 rowSize = width * (SPOT_NCOMPS + 4);
102 } else {
103 rowSize = -1;
104 }
105 break;
106 #endif
107 }
108 if (rowSize > 0) {
109 rowSize += rowPad - 1;
110 rowSize -= rowSize % rowPad;
111 }
112 data = (SplashColorPtr)gmallocn_checkoverflow(rowSize, height);
113 if (data != NULL) {
114 if (!topDown) {
115 data += (height - 1) * rowSize;
116 rowSize = -rowSize;
117 }
118 if (alphaA) {
119 alpha = (Guchar *)gmallocn(width, height);
120 } else {
121 alpha = NULL;
122 }
123 } else {
124 alpha = NULL;
125 }
126 separationList = new GooList();
127 if (separationListA != NULL)
128 for (int i = 0; i < separationListA->getLength(); i++)
129 separationList->append(((GfxSeparationColorSpace *) separationListA->get(i))->copy());
130 }
131
copy(SplashBitmap * src)132 SplashBitmap *SplashBitmap::copy(SplashBitmap *src) {
133 SplashBitmap *result = new SplashBitmap(src->getWidth(), src->getHeight(), src->getRowPad(),
134 src->getMode(), src->getAlphaPtr() != NULL, src->getRowSize() >= 0, src->getSeparationList());
135 Guchar *dataSource = src->getDataPtr();
136 Guchar *dataDest = result->getDataPtr();
137 int amount = src->getRowSize();
138 if (amount < 0) {
139 dataSource = dataSource + (src->getHeight() - 1) * amount;
140 dataDest = dataDest + (src->getHeight() - 1) * amount;
141 amount *= -src->getHeight();
142 } else {
143 amount *= src->getHeight();
144 }
145 memcpy(dataDest, dataSource, amount);
146 if (src->getAlphaPtr() != NULL) {
147 memcpy(result->getAlphaPtr(), src->getAlphaPtr(), src->getWidth() * src->getHeight());
148 }
149 return result;
150 }
151
~SplashBitmap()152 SplashBitmap::~SplashBitmap() {
153 if (data) {
154 if (rowSize < 0) {
155 gfree(data + (height - 1) * rowSize);
156 } else {
157 gfree(data);
158 }
159 }
160 gfree(alpha);
161 deleteGooList(separationList, GfxSeparationColorSpace);
162 }
163
164
writePNMFile(char * fileName)165 SplashError SplashBitmap::writePNMFile(char *fileName) {
166 FILE *f;
167 SplashError e;
168
169 if (!(f = fopen(fileName, "wb"))) {
170 return splashErrOpenFile;
171 }
172
173 e = this->writePNMFile(f);
174
175 fclose(f);
176 return e;
177 }
178
179
writePNMFile(FILE * f)180 SplashError SplashBitmap::writePNMFile(FILE *f) {
181 SplashColorPtr row, p;
182 int x, y;
183
184 switch (mode) {
185
186 case splashModeMono1:
187 fprintf(f, "P4\n%d %d\n", width, height);
188 row = data;
189 for (y = 0; y < height; ++y) {
190 p = row;
191 for (x = 0; x < width; x += 8) {
192 fputc(*p ^ 0xff, f);
193 ++p;
194 }
195 row += rowSize;
196 }
197 break;
198
199 case splashModeMono8:
200 fprintf(f, "P5\n%d %d\n255\n", width, height);
201 row = data;
202 for (y = 0; y < height; ++y) {
203 fwrite(row, 1, width, f);
204 row += rowSize;
205 }
206 break;
207
208 case splashModeRGB8:
209 fprintf(f, "P6\n%d %d\n255\n", width, height);
210 row = data;
211 for (y = 0; y < height; ++y) {
212 fwrite(row, 1, 3 * width, f);
213 row += rowSize;
214 }
215 break;
216
217 case splashModeXBGR8:
218 fprintf(f, "P6\n%d %d\n255\n", width, height);
219 row = data;
220 for (y = 0; y < height; ++y) {
221 p = row;
222 for (x = 0; x < width; ++x) {
223 fputc(splashBGR8R(p), f);
224 fputc(splashBGR8G(p), f);
225 fputc(splashBGR8B(p), f);
226 p += 4;
227 }
228 row += rowSize;
229 }
230 break;
231
232
233 case splashModeBGR8:
234 fprintf(f, "P6\n%d %d\n255\n", width, height);
235 row = data;
236 for (y = 0; y < height; ++y) {
237 p = row;
238 for (x = 0; x < width; ++x) {
239 fputc(splashBGR8R(p), f);
240 fputc(splashBGR8G(p), f);
241 fputc(splashBGR8B(p), f);
242 p += 3;
243 }
244 row += rowSize;
245 }
246 break;
247
248 #if SPLASH_CMYK
249 case splashModeCMYK8:
250 case splashModeDeviceN8:
251 // PNM doesn't support CMYK
252 error(errInternal, -1, "unsupported SplashBitmap mode");
253 return splashErrGeneric;
254 break;
255 #endif
256 }
257 return splashOk;
258 }
259
writeAlphaPGMFile(char * fileName)260 SplashError SplashBitmap::writeAlphaPGMFile(char *fileName) {
261 FILE *f;
262
263 if (!alpha) {
264 return splashErrModeMismatch;
265 }
266 if (!(f = fopen(fileName, "wb"))) {
267 return splashErrOpenFile;
268 }
269 fprintf(f, "P5\n%d %d\n255\n", width, height);
270 fwrite(alpha, 1, width * height, f);
271 fclose(f);
272 return splashOk;
273 }
274
getPixel(int x,int y,SplashColorPtr pixel)275 void SplashBitmap::getPixel(int x, int y, SplashColorPtr pixel) {
276 SplashColorPtr p;
277
278 if (y < 0 || y >= height || x < 0 || x >= width || !data) {
279 return;
280 }
281 switch (mode) {
282 case splashModeMono1:
283 p = &data[y * rowSize + (x >> 3)];
284 pixel[0] = (p[0] & (0x80 >> (x & 7))) ? 0xff : 0x00;
285 break;
286 case splashModeMono8:
287 p = &data[y * rowSize + x];
288 pixel[0] = p[0];
289 break;
290 case splashModeRGB8:
291 p = &data[y * rowSize + 3 * x];
292 pixel[0] = p[0];
293 pixel[1] = p[1];
294 pixel[2] = p[2];
295 break;
296 case splashModeXBGR8:
297 p = &data[y * rowSize + 4 * x];
298 pixel[0] = p[2];
299 pixel[1] = p[1];
300 pixel[2] = p[0];
301 pixel[3] = p[3];
302 break;
303 case splashModeBGR8:
304 p = &data[y * rowSize + 3 * x];
305 pixel[0] = p[2];
306 pixel[1] = p[1];
307 pixel[2] = p[0];
308 break;
309 #if SPLASH_CMYK
310 case splashModeCMYK8:
311 p = &data[y * rowSize + 4 * x];
312 pixel[0] = p[0];
313 pixel[1] = p[1];
314 pixel[2] = p[2];
315 pixel[3] = p[3];
316 break;
317 case splashModeDeviceN8:
318 p = &data[y * rowSize + (SPOT_NCOMPS + 4) * x];
319 for (int cp = 0; cp < SPOT_NCOMPS + 4; cp++)
320 pixel[cp] = p[cp];
321 break;
322 #endif
323 }
324 }
325
getAlpha(int x,int y)326 Guchar SplashBitmap::getAlpha(int x, int y) {
327 return alpha[y * width + x];
328 }
329
takeData()330 SplashColorPtr SplashBitmap::takeData() {
331 SplashColorPtr data2;
332
333 data2 = data;
334 data = NULL;
335 return data2;
336 }
337
writeImgFile(SplashImageFileFormat format,char * fileName,int hDPI,int vDPI,const char * compressionString)338 SplashError SplashBitmap::writeImgFile(SplashImageFileFormat format, char *fileName, int hDPI, int vDPI, const char *compressionString) {
339 FILE *f;
340 SplashError e;
341
342 if (!(f = fopen(fileName, "wb"))) {
343 return splashErrOpenFile;
344 }
345
346 e = writeImgFile(format, f, hDPI, vDPI, compressionString);
347
348 fclose(f);
349 return e;
350 }
351
writeImgFile(SplashImageFileFormat format,FILE * f,int hDPI,int vDPI,const char * compressionString)352 SplashError SplashBitmap::writeImgFile(SplashImageFileFormat format, FILE *f, int hDPI, int vDPI, const char *compressionString) {
353 ImgWriter *writer;
354 SplashError e;
355
356 switch (format) {
357 #ifdef ENABLE_LIBPNG
358 case splashFormatPng:
359 writer = new PNGWriter();
360 break;
361 #endif
362
363 #ifdef ENABLE_LIBJPEG
364 #ifdef SPLASH_CMYK
365 case splashFormatJpegCMYK:
366 writer = new JpegWriter(JpegWriter::CMYK);
367 break;
368 #endif
369 case splashFormatJpeg:
370 writer = new JpegWriter();
371 break;
372 #endif
373
374 #ifdef ENABLE_LIBTIFF
375 case splashFormatTiff:
376 switch (mode) {
377 case splashModeMono1:
378 writer = new TiffWriter(TiffWriter::MONOCHROME);
379 break;
380 case splashModeMono8:
381 writer = new TiffWriter(TiffWriter::GRAY);
382 break;
383 case splashModeRGB8:
384 case splashModeBGR8:
385 writer = new TiffWriter(TiffWriter::RGB);
386 break;
387 #if SPLASH_CMYK
388 case splashModeCMYK8:
389 case splashModeDeviceN8:
390 writer = new TiffWriter(TiffWriter::CMYK);
391 break;
392 #endif
393 default:
394 fprintf(stderr, "TiffWriter: Mode %d not supported\n", mode);
395 writer = new TiffWriter();
396 }
397 if (writer) {
398 ((TiffWriter *)writer)->setCompressionString(compressionString);
399 }
400 break;
401 #endif
402
403 default:
404 // Not the greatest error message, but users of this function should
405 // have already checked whether their desired format is compiled in.
406 error(errInternal, -1, "Support for this image type not compiled in");
407 return splashErrGeneric;
408 }
409
410 e = writeImgFile(writer, f, hDPI, vDPI);
411 delete writer;
412 return e;
413 }
414
415 #include "poppler/GfxState_helpers.h"
416
getRGBLine(int yl,SplashColorPtr line)417 void SplashBitmap::getRGBLine(int yl, SplashColorPtr line) {
418 SplashColor col;
419 double c, m, y, k, c1, m1, y1, k1, r, g, b;
420
421 for (int x = 0; x < width; x++) {
422 getPixel(x, yl, col);
423 c = byteToDbl(col[0]);
424 m = byteToDbl(col[1]);
425 y = byteToDbl(col[2]);
426 k = byteToDbl(col[3]);
427 #if SPLASH_CMYK
428 if (separationList->getLength() > 0) {
429 for (int i = 0; i < separationList->getLength(); i++) {
430 if (col[i+4] > 0) {
431 GfxCMYK cmyk;
432 GfxColor input;
433 input.c[0] = byteToCol(col[i+4]);
434 GfxSeparationColorSpace *sepCS = (GfxSeparationColorSpace *)separationList->get(i);
435 sepCS->getCMYK(&input, &cmyk);
436 col[0] = colToByte(cmyk.c);
437 col[1] = colToByte(cmyk.m);
438 col[2] = colToByte(cmyk.y);
439 col[3] = colToByte(cmyk.k);
440 c += byteToDbl(col[0]);
441 m += byteToDbl(col[1]);
442 y += byteToDbl(col[2]);
443 k += byteToDbl(col[3]);
444 }
445 }
446 if (c > 1) c = 1;
447 if (m > 1) m = 1;
448 if (y > 1) y = 1;
449 if (k > 1) k = 1;
450 }
451 #endif
452 c1 = 1 - c;
453 m1 = 1 - m;
454 y1 = 1 - y;
455 k1 = 1 - k;
456 cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b);
457 *line++ = dblToByte(clip01(r));
458 *line++ = dblToByte(clip01(g));
459 *line++ = dblToByte(clip01(b));
460 }
461 }
462
getXBGRLine(int yl,SplashColorPtr line)463 void SplashBitmap::getXBGRLine(int yl, SplashColorPtr line) {
464 SplashColor col;
465 double c, m, y, k, c1, m1, y1, k1, r, g, b;
466
467 for (int x = 0; x < width; x++) {
468 getPixel(x, yl, col);
469 c = byteToDbl(col[0]);
470 m = byteToDbl(col[1]);
471 y = byteToDbl(col[2]);
472 k = byteToDbl(col[3]);
473 #if SPLASH_CMYK
474 if (separationList->getLength() > 0) {
475 for (int i = 0; i < separationList->getLength(); i++) {
476 if (col[i+4] > 0) {
477 GfxCMYK cmyk;
478 GfxColor input;
479 input.c[0] = byteToCol(col[i+4]);
480 GfxSeparationColorSpace *sepCS = (GfxSeparationColorSpace *)separationList->get(i);
481 sepCS->getCMYK(&input, &cmyk);
482 col[0] = colToByte(cmyk.c);
483 col[1] = colToByte(cmyk.m);
484 col[2] = colToByte(cmyk.y);
485 col[3] = colToByte(cmyk.k);
486 c += byteToDbl(col[0]);
487 m += byteToDbl(col[1]);
488 y += byteToDbl(col[2]);
489 k += byteToDbl(col[3]);
490 }
491 }
492 if (c > 1) c = 1;
493 if (m > 1) m = 1;
494 if (y > 1) y = 1;
495 if (k > 1) k = 1;
496 }
497 #endif
498 c1 = 1 - c;
499 m1 = 1 - m;
500 y1 = 1 - y;
501 k1 = 1 - k;
502 cmykToRGBMatrixMultiplication(c, m, y, k, c1, m1, y1, k1, r, g, b);
503 *line++ = dblToByte(clip01(b));
504 *line++ = dblToByte(clip01(g));
505 *line++ = dblToByte(clip01(r));
506 *line++ = 255;
507 }
508 }
509
convertToXBGR()510 GBool SplashBitmap::convertToXBGR() {
511 if (mode == splashModeXBGR8)
512 return gTrue;
513
514 int newrowSize = width * 4;
515 SplashColorPtr newdata = (SplashColorPtr)gmallocn_checkoverflow(newrowSize, height);
516 if (newdata != NULL) {
517 for (int y = 0; y < height; y++) {
518 unsigned char *row = newdata + y * newrowSize;
519 getXBGRLine(y, row);
520 }
521 if (rowSize < 0) {
522 gfree(data + (height - 1) * rowSize);
523 } else {
524 gfree(data);
525 }
526 data = newdata;
527 rowSize = newrowSize;
528 mode = splashModeXBGR8;
529 }
530 return newdata != NULL;
531 }
532
533 #if SPLASH_CMYK
getCMYKLine(int yl,SplashColorPtr line)534 void SplashBitmap::getCMYKLine(int yl, SplashColorPtr line) {
535 SplashColor col;
536
537 for (int x = 0; x < width; x++) {
538 getPixel(x, yl, col);
539 if (separationList->getLength() > 0) {
540 double c, m, y, k;
541 c = byteToDbl(col[0]);
542 m = byteToDbl(col[1]);
543 y = byteToDbl(col[2]);
544 k = byteToDbl(col[3]);
545 for (int i = 0; i < separationList->getLength(); i++) {
546 if (col[i+4] > 0) {
547 GfxCMYK cmyk;
548 GfxColor input;
549 input.c[0] = byteToCol(col[i+4]);
550 GfxSeparationColorSpace *sepCS = (GfxSeparationColorSpace *)separationList->get(i);
551 sepCS->getCMYK(&input, &cmyk);
552 col[0] = colToByte(cmyk.c);
553 col[1] = colToByte(cmyk.m);
554 col[2] = colToByte(cmyk.y);
555 col[3] = colToByte(cmyk.k);
556 c += byteToDbl(col[0]);
557 m += byteToDbl(col[1]);
558 y += byteToDbl(col[2]);
559 k += byteToDbl(col[3]);
560 }
561 }
562 col[0] = dblToByte(clip01(c));
563 col[1] = dblToByte(clip01(m));
564 col[2] = dblToByte(clip01(y));
565 col[3] = dblToByte(clip01(k));
566 }
567 *line++ = col[0];
568 *line++ = col[1];
569 *line++ = col[2];
570 *line++ = col[3];
571 }
572 }
573 #endif
574
writeImgFile(ImgWriter * writer,FILE * f,int hDPI,int vDPI)575 SplashError SplashBitmap::writeImgFile(ImgWriter *writer, FILE *f, int hDPI, int vDPI) {
576 if (mode != splashModeRGB8 && mode != splashModeMono8 && mode != splashModeMono1 && mode != splashModeXBGR8 && mode != splashModeBGR8
577 #if SPLASH_CMYK
578 && mode != splashModeCMYK8 && mode != splashModeDeviceN8
579 #endif
580 ) {
581 error(errInternal, -1, "unsupported SplashBitmap mode");
582 return splashErrGeneric;
583 }
584
585 if (!writer->init(f, width, height, hDPI, vDPI)) {
586 return splashErrGeneric;
587 }
588
589 switch (mode) {
590 #if SPLASH_CMYK
591 case splashModeCMYK8:
592 if (writer->supportCMYK()) {
593 SplashColorPtr row;
594 unsigned char **row_pointers = new unsigned char*[height];
595 row = data;
596
597 for (int y = 0; y < height; ++y) {
598 row_pointers[y] = row;
599 row += rowSize;
600 }
601 if (!writer->writePointers(row_pointers, height)) {
602 delete[] row_pointers;
603 return splashErrGeneric;
604 }
605 delete[] row_pointers;
606 } else {
607 unsigned char *row = new unsigned char[3 * width];
608 for (int y = 0; y < height; y++) {
609 getRGBLine(y, row);
610 if (!writer->writeRow(&row)) {
611 delete[] row;
612 return splashErrGeneric;
613 }
614 }
615 delete[] row;
616 }
617 break;
618 case splashModeDeviceN8:
619 if (writer->supportCMYK()) {
620 unsigned char *row = new unsigned char[4 * width];
621 for (int y = 0; y < height; y++) {
622 getCMYKLine(y, row);
623 if (!writer->writeRow(&row)) {
624 delete[] row;
625 return splashErrGeneric;
626 }
627 }
628 delete[] row;
629 } else {
630 unsigned char *row = new unsigned char[3 * width];
631 for (int y = 0; y < height; y++) {
632 getRGBLine(y, row);
633 if (!writer->writeRow(&row)) {
634 delete[] row;
635 return splashErrGeneric;
636 }
637 }
638 delete[] row;
639 }
640 break;
641 #endif
642 case splashModeRGB8:
643 {
644 SplashColorPtr row;
645 unsigned char **row_pointers = new unsigned char*[height];
646 row = data;
647
648 for (int y = 0; y < height; ++y) {
649 row_pointers[y] = row;
650 row += rowSize;
651 }
652 if (!writer->writePointers(row_pointers, height)) {
653 delete[] row_pointers;
654 return splashErrGeneric;
655 }
656 delete[] row_pointers;
657 }
658 break;
659
660 case splashModeBGR8:
661 {
662 unsigned char *row = new unsigned char[3 * width];
663 for (int y = 0; y < height; y++) {
664 // Convert into a PNG row
665 for (int x = 0; x < width; x++) {
666 row[3*x] = data[y * rowSize + x * 3 + 2];
667 row[3*x+1] = data[y * rowSize + x * 3 + 1];
668 row[3*x+2] = data[y * rowSize + x * 3];
669 }
670
671 if (!writer->writeRow(&row)) {
672 delete[] row;
673 return splashErrGeneric;
674 }
675 }
676 delete[] row;
677 }
678 break;
679
680 case splashModeXBGR8:
681 {
682 unsigned char *row = new unsigned char[3 * width];
683 for (int y = 0; y < height; y++) {
684 // Convert into a PNG row
685 for (int x = 0; x < width; x++) {
686 row[3*x] = data[y * rowSize + x * 4 + 2];
687 row[3*x+1] = data[y * rowSize + x * 4 + 1];
688 row[3*x+2] = data[y * rowSize + x * 4];
689 }
690
691 if (!writer->writeRow(&row)) {
692 delete[] row;
693 return splashErrGeneric;
694 }
695 }
696 delete[] row;
697 }
698 break;
699
700 case splashModeMono8:
701 {
702 unsigned char *row = new unsigned char[3 * width];
703 for (int y = 0; y < height; y++) {
704 // Convert into a PNG row
705 for (int x = 0; x < width; x++) {
706 row[3*x] = data[y * rowSize + x];
707 row[3*x+1] = data[y * rowSize + x];
708 row[3*x+2] = data[y * rowSize + x];
709 }
710
711 if (!writer->writeRow(&row)) {
712 delete[] row;
713 return splashErrGeneric;
714 }
715 }
716 delete[] row;
717 }
718 break;
719
720 case splashModeMono1:
721 {
722 unsigned char *row = new unsigned char[3 * width];
723 for (int y = 0; y < height; y++) {
724 // Convert into a PNG row
725 for (int x = 0; x < width; x++) {
726 getPixel(x, y, &row[3*x]);
727 row[3*x+1] = row[3*x];
728 row[3*x+2] = row[3*x];
729 }
730
731 if (!writer->writeRow(&row)) {
732 delete[] row;
733 return splashErrGeneric;
734 }
735 }
736 delete[] row;
737 }
738 break;
739
740 default:
741 // can't happen
742 break;
743 }
744
745 if (!writer->close()) {
746 return splashErrGeneric;
747 }
748
749 return splashOk;
750 }
751