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