1 /*****
2  * psfile.cc
3  * Andy Hammerlindl 2002/06/10
4  *
5  * Encapsulates the writing of commands to a PostScript file.
6  * Allows identification and removal of redundant commands.
7  *****/
8 
9 #include <ctime>
10 #include <iomanip>
11 #include <sstream>
12 #include <zlib.h>
13 
14 #include "psfile.h"
15 #include "settings.h"
16 #include "errormsg.h"
17 #include "array.h"
18 #include "stack.h"
19 
20 using std::ofstream;
21 using std::setw;
22 using vm::array;
23 using vm::read;
24 using vm::stack;
25 using vm::callable;
26 using vm::pop;
27 
28 namespace camp {
29 
checkColorSpace(ColorSpace colorspace)30 void checkColorSpace(ColorSpace colorspace)
31 {
32   switch(colorspace) {
33     case DEFCOLOR:
34     case INVISIBLE:
35       reportError("Cannot shade with invisible pen");
36     case PATTERN:
37       reportError("Cannot shade with pattern");
38       break;
39     default:
40       break;
41   }
42 }
43 
psfile(const string & filename,bool pdfformat)44 psfile::psfile(const string& filename, bool pdfformat)
45   : filename(filename), pdfformat(pdfformat), pdf(false),
46     buffer(NULL), out(NULL)
47 {
48   if(filename.empty()) out=&cout;
49   else out=new ofstream(filename.c_str());
50   out->setf(std::ios::boolalpha);
51   if(!out || !*out)
52     reportError("Cannot write to "+filename);
53 }
54 
55 static const char *inconsistent="inconsistent colorspaces";
56 static const char *rectangular="matrix is not rectangular";
57 
writefromRGB(unsigned char r,unsigned char g,unsigned char b,ColorSpace colorspace,size_t ncomponents)58 void psfile::writefromRGB(unsigned char r, unsigned char g, unsigned char b,
59                           ColorSpace colorspace, size_t ncomponents)
60 {
61   static const double factor=1.0/255.0;
62   pen p(r*factor,g*factor,b*factor);
63   p.convert();
64   if(!p.promote(colorspace))
65     reportError(inconsistent);
66   write(&p,ncomponents);
67 }
68 
average(unsigned char * a,size_t dx,size_t dy)69 inline unsigned char average(unsigned char *a, size_t dx, size_t dy)
70 {
71   return ((unsigned) a[0]+(unsigned) a[dx]+(unsigned) a[dy]+
72           (unsigned) a[dx+dy])/4;
73 }
74 
dealias(unsigned char * a,size_t width,size_t height,size_t n,bool convertrgb,ColorSpace colorspace)75 void psfile::dealias(unsigned char *a, size_t width, size_t height, size_t n,
76                      bool convertrgb, ColorSpace colorspace)
77 {
78   // Dealias all but the last row and column of pixels.
79   size_t istop=width-1;
80   size_t jstop=height-1;
81   if(convertrgb) {
82     size_t nwidth=3*width;
83     for(size_t j=0; j < height; ++j) {
84       unsigned char *aj=a+nwidth*j;
85       for(size_t i=0; i < width; ++i) {
86         unsigned char *ai=aj+3*i;
87         if(i < istop && j < jstop)
88           writefromRGB(average(ai,3,nwidth),
89                        average(ai+1,3,nwidth),
90                        average(ai+2,3,nwidth),colorspace,n);
91         else
92           writefromRGB(ai[0],ai[1],ai[2],colorspace,n);
93       }
94     }
95   } else {
96     size_t nwidth=n*width;
97     for(size_t j=0; j < jstop; ++j) {
98       unsigned char *aj=a+nwidth*j;
99       for(size_t i=0; i < istop; ++i) {
100         unsigned char *ai=aj+n*i;
101         for(size_t k=0; k < n; ++k)
102           ai[k]=average(ai+k,n,nwidth);
103       }
104     }
105   }
106 }
107 
writeCompressed(const unsigned char * a,size_t size)108 void psfile::writeCompressed(const unsigned char *a, size_t size)
109 {
110   uLongf compressedSize=compressBound(size);
111   Bytef *compressed=new Bytef[compressedSize];
112 
113   if(compress(compressed,&compressedSize,a,size) != Z_OK)
114     reportError("image compression failed");
115 
116   encode85 e(out);
117   for(size_t i=0; i < compressedSize; ++i)
118     e.put(compressed[i]);
119 }
120 
close()121 void psfile::close()
122 {
123   if(out) {
124     out->flush();
125     if(!filename.empty()) {
126 #ifdef __MSDOS__
127       chmod(filename.c_str(),~settings::mask & 0777);
128 #endif
129       if(!out->good())
130         // Don't call reportError since this may be called on handled_error.
131         reportFatal("Cannot write to "+filename);
132       delete out;
133       out=NULL;
134     }
135   }
136 }
137 
~psfile()138 psfile::~psfile()
139 {
140   close();
141 }
142 
header(bool eps)143 void psfile::header(bool eps)
144 {
145   Int level=settings::getSetting<Int>("level");
146   *out << "%!PS-Adobe-" << level << ".0";
147   if(eps)
148     *out << " EPSF-" << level << ".0";
149   *out << newl;
150 }
151 
prologue(const bbox & box)152 void psfile::prologue(const bbox& box)
153 {
154   header(true);
155   BoundingBox(box);
156   *out << "%%Creator: " << settings::PROGRAM << " " << settings::VERSION
157        << REVISION <<  newl;
158 
159   time_t t; time(&t);
160   struct tm *tt = localtime(&t);
161   char prev = out->fill('0');
162   *out << "%%CreationDate: " << tt->tm_year + 1900 << "."
163        << setw(2) << tt->tm_mon+1 << "." << setw(2) << tt->tm_mday << " "
164        << setw(2) << tt->tm_hour << ":" << setw(2) << tt->tm_min << ":"
165        << setw(2) << tt->tm_sec << newl;
166   out->fill(prev);
167 
168   *out << "%%Pages: 1" << newl;
169   *out << "%%Page: 1 1" << newl;
170 
171   if(!pdfformat)
172     *out
173       << "/Setlinewidth {0 exch dtransform dup abs 1 lt {pop 0}{round} ifelse"
174       << newl
175       << "idtransform setlinewidth pop} bind def" << newl;
176 }
177 
epilogue()178 void psfile::epilogue()
179 {
180   *out << "showpage" << newl;
181   *out << "%%EOF" << newl;
182 }
183 
setcolor(const pen & p,const string & begin="",const string & end="")184 void psfile::setcolor(const pen& p, const string& begin="",
185                       const string& end="")
186 {
187   if(p.cmyk() && (!lastpen.cmyk() ||
188                   (p.cyan() != lastpen.cyan() ||
189                    p.magenta() != lastpen.magenta() ||
190                    p.yellow() != lastpen.yellow() ||
191                    p.black() != lastpen.black()))) {
192     *out << begin << p.cyan() << " " << p.magenta() << " " << p.yellow() << " "
193          << p.black() << (pdf ? " k" : " setcmykcolor") << end << newl;
194   } else if(p.rgb() && (!lastpen.rgb() ||
195                         (p.red() != lastpen.red() ||
196                          p.green() != lastpen.green() ||
197                          p.blue() != lastpen.blue()))) {
198     *out << begin << p.red() << " " << p.green() << " " << p.blue()
199          << (pdf ? " rg" : " setrgbcolor") << end << newl;
200   } else if(p.grayscale() && (!lastpen.grayscale() ||
201                               p.gray() != lastpen.gray())) {
202     *out << begin << p.gray() << (pdf ? " g" : " setgray") << end << newl;
203   }
204 }
205 
setopacity(const pen & p)206 void psfile::setopacity(const pen& p)
207 {
208   if(p.blend() != lastpen.blend()) {
209     *out << "/" << p.blend() << " .setblendmode" << newl;
210   }
211 
212   string outputformat=settings::getSetting<string>("outformat");
213   if(p.opacity() != lastpen.opacity() &&
214      (pdf || outputformat == "pdf" || outputformat == "html" ||
215       outputformat == "svg")) {
216     *out << p.opacity() << " .setfillconstantalpha" << newl
217          << p.opacity() << " .setstrokeconstantalpha" << newl;
218   }
219 
220   lastpen.settransparency(p);
221 }
222 
223 
setpen(pen p)224 void psfile::setpen(pen p)
225 {
226   p.convert();
227 
228   setopacity(p);
229 
230   if(!p.fillpattern().empty() && p.fillpattern() != lastpen.fillpattern())
231     *out << p.fillpattern() << " setpattern" << newl;
232   else setcolor(p);
233 
234   // Defer dynamic linewidth until stroke time in case currentmatrix changes.
235   if(p.width() != lastpen.width())
236     *out << p.width() << (pdfformat ? " setlinewidth" : " Setlinewidth")
237          << newl;
238 
239   if(p.cap() != lastpen.cap())
240     *out << p.cap() << " setlinecap" << newl;
241 
242   if(p.join() != lastpen.join())
243     *out << p.join() << " setlinejoin" << newl;
244 
245   if(p.miter() != lastpen.miter())
246     *out << p.miter() << " setmiterlimit" << newl;
247 
248   const LineType *linetype=p.linetype();
249   const LineType *lastlinetype=lastpen.linetype();
250 
251   if(!(linetype->pattern == lastlinetype->pattern) ||
252      linetype->offset != lastlinetype->offset) {
253     out->setf(std::ios::fixed);
254     *out << linetype->pattern << " " << linetype->offset << " setdash" << newl;
255     out->unsetf(std::ios::fixed);
256   }
257 
258   lastpen=p;
259 }
260 
write(const pen & p)261 void psfile::write(const pen& p)
262 {
263   if(p.cmyk())
264     *out << p.cyan() << " " << p.magenta() << " " << p.yellow() << " "
265          << p.black();
266   else if(p.rgb())
267     *out << p.red() << " " << p.green() << " " << p.blue();
268   else if(p.grayscale())
269     *out << p.gray();
270 }
271 
write(path p,bool newPath)272 void psfile::write(path p, bool newPath)
273 {
274   Int n = p.size();
275   assert(n != 0);
276 
277   if(newPath) newpath();
278 
279   pair z0=p.point((Int) 0);
280 
281   // Draw points
282   moveto(z0);
283 
284   for(Int i = 1; i < n; i++) {
285     if(p.straight(i-1)) lineto(p.point(i));
286     else curveto(p.postcontrol(i-1),p.precontrol(i),p.point(i));
287   }
288 
289   if(p.cyclic()) {
290     if(p.straight(n-1)) lineto(z0);
291     else curveto(p.postcontrol(n-1),p.precontrol((Int) 0),z0);
292     closepath();
293   } else {
294     if(n == 1) lineto(z0);
295   }
296 }
297 
latticeshade(const vm::array & a,const transform & t)298 void psfile::latticeshade(const vm::array& a, const transform& t)
299 {
300   checkLevel();
301   size_t n=a.size();
302   if(n == 0) return;
303 
304   array *a0=read<array *>(a,0);
305   size_t m=a0->size();
306   setfirstopacity(*a0);
307 
308   ColorSpace colorspace=maxcolorspace2(a);
309   checkColorSpace(colorspace);
310 
311   size_t ncomponents=ColorComponents[colorspace];
312 
313   *out << "<< /ShadingType 1" << newl
314        << "/Matrix ";
315   write(t);
316   *out << newl;
317   *out << "/ColorSpace /Device" << ColorDeviceSuffix[colorspace] << newl
318        << "/Function" << newl
319        << "<< /FunctionType 0" << newl
320        << "/Order 1" << newl
321        << "/Domain [0 1 0 1]" << newl
322        << "/Range [";
323   for(size_t i=0; i < ncomponents; ++i)
324     *out << "0 1 ";
325   *out << "]" << newl
326        << "/Decode [";
327   for(size_t i=0; i < ncomponents; ++i)
328     *out << "0 1 ";
329   *out << "]" << newl;
330   *out << "/BitsPerSample 8" << newl;
331   *out << "/Size [" << m << " " << n << "]" << newl
332        << "/DataSource <" << newl;
333 
334   for(size_t i=n; i > 0;) {
335     array *ai=read<array *>(a,--i);
336     checkArray(ai);
337     size_t aisize=ai->size();
338     if(aisize != m) reportError(rectangular);
339     for(size_t j=0; j < m; j++) {
340       pen *p=read<pen *>(ai,j);
341       p->convert();
342       if(!p->promote(colorspace))
343         reportError(inconsistent);
344       *out << p->hex() << newl;
345     }
346   }
347 
348   *out << ">" << newl
349        << ">>" << newl
350        << ">>" << newl
351        << "shfill" << newl;
352 }
353 
354 // Axial and radial shading
gradientshade(bool axial,ColorSpace colorspace,const pen & pena,const pair & a,double ra,bool extenda,const pen & penb,const pair & b,double rb,bool extendb)355 void psfile::gradientshade(bool axial, ColorSpace colorspace,
356                            const pen& pena, const pair& a, double ra,
357                            bool extenda, const pen& penb, const pair& b,
358                            double rb, bool extendb)
359 {
360   checkLevel();
361   endclip(pena);
362 
363   setopacity(pena);
364   checkColorSpace(colorspace);
365 
366   *out << "<< /ShadingType " << (axial ? "2" : "3") << newl
367        << "/ColorSpace /Device" << ColorDeviceSuffix[colorspace] << newl
368        << "/Coords [";
369   write(a);
370   if(!axial) write(ra);
371   write(b);
372   if(!axial) write(rb);
373   *out << "]" << newl
374        << "/Extend [" << extenda << " " << extendb << "]" << newl
375        << "/Function" << newl
376        << "<< /FunctionType 2" << newl
377        << "/Domain [0 1]" << newl
378        << "/C0 [";
379   write(pena);
380   *out << "]" << newl
381        << "/C1 [";
382   write(penb);
383   *out << "]" << newl
384        << "/N 1" << newl
385        << ">>" << newl
386        << ">>" << newl
387        << "shfill" << newl;
388 }
389 
gouraudshade(const pen & pentype,const array & pens,const array & vertices,const array & edges)390 void psfile::gouraudshade(const pen& pentype,
391                           const array& pens, const array& vertices,
392                           const array& edges)
393 {
394   checkLevel();
395   endclip(pentype);
396 
397   size_t size=pens.size();
398   if(size == 0) return;
399 
400   setfirstopacity(pens);
401   ColorSpace colorspace=maxcolorspace(pens);
402 
403   *out << "<< /ShadingType 4" << newl
404        << "/ColorSpace /Device" << ColorDeviceSuffix[colorspace] << newl
405        << "/DataSource [" << newl;
406   for(size_t i=0; i < size; i++) {
407     write(read<Int>(edges,i));
408     write(read<pair>(vertices,i));
409     pen *p=read<pen *>(pens,i);
410     p->convert();
411     if(!p->promote(colorspace))
412       reportError(inconsistent);
413     *out << " ";
414     write(*p);
415     *out << newl;
416   }
417   *out << "]" << newl
418        << ">>" << newl
419        << "shfill" << newl;
420 }
421 
vertexpen(array * pi,int j,ColorSpace colorspace)422 void psfile::vertexpen(array *pi, int j, ColorSpace colorspace)
423 {
424   pen *p=read<pen *>(pi,j);
425   p->convert();
426   if(!p->promote(colorspace))
427     reportError(inconsistent);
428   *out << " ";
429   write(*p);
430 }
431 
432 // Tensor-product patch shading
tensorshade(const pen & pentype,const array & pens,const array & boundaries,const array & z)433 void psfile::tensorshade(const pen& pentype, const array& pens,
434                          const array& boundaries, const array& z)
435 {
436   checkLevel();
437   endclip(pentype);
438 
439   size_t size=pens.size();
440   if(size == 0) return;
441   size_t nz=z.size();
442 
443   array *p0=read<array *>(pens,0);
444   if(checkArray(p0) != 4)
445     reportError("4 pens required");
446   setfirstopacity(*p0);
447 
448   ColorSpace colorspace=maxcolorspace2(pens);
449   checkColorSpace(colorspace);
450 
451   *out << "<< /ShadingType 7" << newl
452        << "/ColorSpace /Device" << ColorDeviceSuffix[colorspace] << newl
453        << "/DataSource [" << newl;
454 
455   for(size_t i=0; i < size; i++) {
456     // Only edge flag 0 (new patch) is implemented since the 32% data
457     // compression (for RGB) afforded by other edge flags really isn't worth
458     // the trouble or confusion for the user.
459     write(0);
460     path g=read<path>(boundaries,i);
461     if(!(g.cyclic() && g.size() == 4))
462       reportError("specify cyclic path of length 4");
463     for(Int j=4; j > 0; --j) {
464       write(g.point(j));
465       write(g.precontrol(j));
466       write(g.postcontrol(j-1));
467     }
468     if(nz == 0) { // Coons patch
469       static double nineth=1.0/9.0;
470       for(Int j=0; j < 4; ++j) {
471         write(nineth*(-4.0*g.point(j)+6.0*(g.precontrol(j)+g.postcontrol(j))
472                       -2.0*(g.point(j-1)+g.point(j+1))
473                       +3.0*(g.precontrol(j-1)+g.postcontrol(j+1))
474                       -g.point(j+2)));
475       }
476     } else {
477       array *zi=read<array *>(z,i);
478       if(checkArray(zi) != 4)
479         reportError("specify 4 internal control points for each path");
480       write(read<pair>(zi,0));
481       write(read<pair>(zi,3));
482       write(read<pair>(zi,2));
483       write(read<pair>(zi,1));
484     }
485 
486     array *pi=read<array *>(pens,i);
487     if(checkArray(pi) != 4)
488       reportError("specify 4 pens for each path");
489     vertexpen(pi,0,colorspace);
490     vertexpen(pi,3,colorspace);
491     vertexpen(pi,2,colorspace);
492     vertexpen(pi,1,colorspace);
493     *out << newl;
494   }
495 
496   *out << "]" << newl
497        << ">>" << newl
498        << "shfill" << newl;
499 }
500 
write(pen * p,size_t ncomponents)501 void psfile::write(pen *p, size_t ncomponents)
502 {
503   switch(ncomponents) {
504     case 0:
505       break;
506     case 1:
507       writeByte(byte(p->gray()));
508       break;
509     case 3:
510       writeByte(byte(p->red()));
511       writeByte(byte(p->green()));
512       writeByte(byte(p->blue()));
513       break;
514     case 4:
515       writeByte(byte(p->cyan()));
516       writeByte(byte(p->magenta()));
517       writeByte(byte(p->yellow()));
518       writeByte(byte(p->black()));
519     default:
520       break;
521   }
522 }
523 
filter()524 string filter()
525 {
526   return settings::getSetting<Int>("level") >= 3 ?
527     "1 (~>) /SubFileDecode filter /ASCII85Decode filter\n/FlateDecode" :
528     "1 (~>) /SubFileDecode filter /ASCII85Decode";
529 }
530 
imageheader(size_t width,size_t height,ColorSpace colorspace)531 void psfile::imageheader(size_t width, size_t height, ColorSpace colorspace)
532 {
533   size_t ncomponents=ColorComponents[colorspace];
534   *out << "/Device" << ColorDeviceSuffix[colorspace] << " setcolorspace"
535        << newl
536        << "<<" << newl
537        << "/ImageType 1" << newl
538        << "/Width " << width << newl
539        << "/Height " << height << newl
540        << "/BitsPerComponent 8" << newl
541        << "/Decode [";
542 
543   for(size_t i=0; i < ncomponents; ++i)
544     *out << "0 1 ";
545 
546   *out << "]" << newl
547        << "/ImageMatrix [" << width << " 0 0 " << height << " 0 0]" << newl
548        << "/DataSource currentfile " << filter() << " filter" << newl
549        << ">>" << newl
550        << "image" << newl;
551 }
552 
image(const array & a,const array & P,bool antialias)553 void psfile::image(const array& a, const array& P, bool antialias)
554 {
555   size_t asize=a.size();
556   size_t Psize=P.size();
557   if(asize == 0 || Psize == 0) return;
558 
559   array *a0=read<array *>(a,0);
560   size_t a0size=a0->size();
561   if(a0size == 0) return;
562 
563   setfirstopacity(P);
564 
565   ColorSpace colorspace=maxcolorspace(P);
566   checkColorSpace(colorspace);
567 
568   size_t ncomponents=ColorComponents[colorspace];
569 
570   imageheader(a0size,asize,colorspace);
571 
572   double min=read<double>(a0,0);
573   double max=min;
574   for(size_t i=0; i < asize; i++) {
575     array *ai=read<array *>(a,i);
576     size_t size=ai->size();
577     if(size != a0size)
578       reportError(rectangular);
579     for(size_t j=0; j < size; j++) {
580       double val=read<double>(ai,j);
581       if(val > max) max=val;
582       else if(val < min) min=val;
583     }
584   }
585 
586   double step=(max == min) ? 0.0 : (Psize-1)/(max-min);
587 
588   beginImage(ncomponents*a0size*asize);
589   for(size_t i=0; i < asize; i++) {
590     array *ai=read<array *>(a,i);
591     for(size_t j=0; j < a0size; j++) {
592       double val=read<double>(ai,j);
593       size_t index=(size_t) ((val-min)*step+0.5);
594       pen *p=read<pen *>(P,index < Psize ? index : Psize-1);
595       p->convert();
596       if(!p->promote(colorspace))
597         reportError(inconsistent);
598       write(p,ncomponents);
599     }
600   }
601   endImage(antialias,a0size,asize,ncomponents);
602 }
603 
image(const array & a,bool antialias)604 void psfile::image(const array& a, bool antialias)
605 {
606   size_t asize=a.size();
607   if(asize == 0) return;
608 
609   array *a0=read<array *>(a,0);
610   size_t a0size=a0->size();
611   if(a0size == 0) return;
612 
613   setfirstopacity(*a0);
614 
615   ColorSpace colorspace=maxcolorspace2(a);
616   checkColorSpace(colorspace);
617 
618   size_t ncomponents=ColorComponents[colorspace];
619 
620   imageheader(a0size,asize,colorspace);
621 
622   beginImage(ncomponents*a0size*asize);
623   for(size_t i=0; i < asize; i++) {
624     array *ai=read<array *>(a,i);
625     size_t size=ai->size();
626     if(size != a0size)
627       reportError(rectangular);
628     for(size_t j=0; j < size; j++) {
629       pen *p=read<pen *>(ai,j);
630       p->convert();
631       if(!p->promote(colorspace))
632         reportError(inconsistent);
633       write(p,ncomponents);
634     }
635   }
636   endImage(antialias,a0size,asize,ncomponents);
637 }
638 
image(stack * Stack,callable * f,Int width,Int height,bool antialias)639 void psfile::image(stack *Stack, callable *f, Int width, Int height,
640                    bool antialias)
641 {
642   if(width <= 0 || height <= 0) return;
643 
644   Stack->push(0);
645   Stack->push(0);
646   f->call(Stack);
647   pen p=pop<pen>(Stack);
648 
649   setopacity(p);
650 
651   ColorSpace colorspace=p.colorspace();
652   checkColorSpace(colorspace);
653 
654   size_t ncomponents=ColorComponents[colorspace];
655 
656   imageheader(width,height,colorspace);
657 
658   beginImage(ncomponents*width*height);
659   for(Int j=0; j < height; j++) {
660     for(Int i=0; i < width; i++) {
661       Stack->push(j);
662       Stack->push(i);
663       f->call(Stack);
664       pen p=pop<pen>(Stack);
665       p.convert();
666       if(!p.promote(colorspace))
667         reportError(inconsistent);
668       write(&p,ncomponents);
669     }
670   }
671   endImage(antialias,width,height,ncomponents);
672 }
673 
outImage(bool antialias,size_t width,size_t height,size_t ncomponents)674 void psfile::outImage(bool antialias, size_t width, size_t height,
675                       size_t ncomponents)
676 {
677   if(antialias) dealias(buffer,width,height,ncomponents);
678   if(settings::getSetting<Int>("level") >= 3)
679     writeCompressed(buffer,count);
680   else {
681     encode85 e(out);
682     for(size_t i=0; i < count; ++i)
683       e.put(buffer[i]);
684   }
685 }
686 
rawimage(unsigned char * a,size_t width,size_t height,bool antialias)687 void psfile::rawimage(unsigned char *a, size_t width, size_t height,
688                       bool antialias)
689 {
690   pen p(0.0,0.0,0.0);
691   p.convert();
692   ColorSpace colorspace=p.colorspace();
693   checkColorSpace(colorspace);
694 
695   size_t ncomponents=ColorComponents[colorspace];
696 
697   imageheader(width,height,colorspace);
698 
699   count=ncomponents*width*height;
700   if(colorspace == RGB) {
701     buffer=a;
702     outImage(antialias,width,height,ncomponents);
703   } else {
704     beginImage(count);
705     if(antialias)
706       dealias(a,width,height,ncomponents,true,colorspace);
707     else {
708       size_t height3=3*height;
709       for(size_t i=0; i < width; ++i) {
710         unsigned char *ai=a+height3*i;
711         for(size_t j=0; j < height; ++j) {
712           unsigned char *aij=ai+3*j;
713           writefromRGB(aij[0],aij[1],aij[2],colorspace,ncomponents);
714         }
715       }
716     }
717     endImage(false,width,height,ncomponents);
718   }
719 }
720 
721 } //namespace camp
722