xref: /petsc/src/sys/classes/draw/utils/image.c (revision a69119a591a03a9d906b29c0a4e9802e4d7c9795)
1 #include <petsc/private/petscimpl.h> /*I "petscsys.h" I*/
2 
3 PETSC_EXTERN PetscErrorCode PetscDrawImageSave(const char[], const char[], unsigned char[][3], unsigned int, unsigned int, const unsigned char[]);
4 PETSC_EXTERN PetscErrorCode PetscDrawMovieSave(const char[], PetscInt, const char[], PetscInt, const char[]);
5 PETSC_EXTERN PetscErrorCode PetscDrawImageCheckFormat(const char *[]);
6 PETSC_EXTERN PetscErrorCode PetscDrawMovieCheckFormat(const char *[]);
7 
8 /*
9    Code to write images in PPM format
10 */
11 PETSC_EXTERN PetscErrorCode PetscDrawImageSavePPM(const char filename[], unsigned char palette[][3], unsigned int w, unsigned int h, const unsigned char pixels[]) {
12   int            fd;
13   char           header[32];
14   size_t         hdrlen;
15   unsigned char *rgb;
16 
17   PetscFunctionBegin;
18   PetscValidCharPointer(filename, 1);
19   if (palette) PetscValidCharPointer(palette, 2);
20   PetscValidCharPointer(pixels, 5);
21   /* map pixels to RGB colors */
22   if (palette) {
23     int                  k, p, n = (int)(w * h);
24     const unsigned char *colordef;
25     PetscCall(PetscMalloc1(3 * w * h, &rgb));
26     for (k = p = 0; k < n; k++) {
27       colordef = palette[pixels[k]];
28       rgb[p++] = colordef[0];
29       rgb[p++] = colordef[1];
30       rgb[p++] = colordef[2];
31     }
32   } else { /* assume pixels are RGB colors */
33     rgb = (unsigned char *)pixels;
34   }
35   /* open file and write PPM header */
36   PetscCall(PetscBinaryOpen(filename, FILE_MODE_WRITE, &fd));
37   PetscCall(PetscSNPrintf(header, sizeof(header), "P6\n%d %d\n255\n%c", (int)w, (int)h, '\0'));
38   PetscCall(PetscStrlen(header, &hdrlen));
39   PetscCall(PetscBinaryWrite(fd, header, hdrlen, PETSC_CHAR));
40   /* write image data and close file */
41   PetscCall(PetscBinaryWrite(fd, rgb, 3 * w * h, PETSC_CHAR));
42   PetscCall(PetscBinaryClose(fd));
43   if (palette) PetscCall(PetscFree(rgb));
44   PetscFunctionReturn(0);
45 }
46 
47 static PetscErrorCode PetscDrawImageSave_PPM(const char filename[], unsigned char palette[][3], unsigned int w, unsigned int h, const unsigned char pixels[]) {
48   return PetscDrawImageSavePPM(filename, palette, w, h, pixels);
49 }
50 
51 /*
52    Code to write images in PNG format
53 */
54 #if defined(PETSC_HAVE_LIBPNG)
55 
56 #include <png.h>
57 
58 #if defined(PNG_SETJMP_SUPPORTED)
59 #ifndef png_jmpbuf
60 #define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf)
61 #endif
62 #endif
63 
64 PETSC_EXTERN PetscErrorCode PetscDrawImageSavePNG(const char filename[], unsigned char palette[][3], unsigned int w, unsigned int h, const unsigned char pixels[]) {
65   FILE        *fp;
66   png_struct  *png_ptr;
67   png_info    *info_ptr;
68   unsigned int row, stride = palette ? w : 3 * w;
69 
70   PetscFunctionBegin;
71   PetscValidCharPointer(filename, 1);
72   if (palette) PetscValidCharPointer(palette, 2);
73   PetscValidCharPointer(pixels, 5);
74 
75   /* open file and create libpng structures */
76   PetscCall(PetscFOpen(PETSC_COMM_SELF, filename, "wb", &fp));
77   png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
78   PetscCheck(png_ptr, PETSC_COMM_SELF, PETSC_ERR_LIB, "Cannot create PNG context");
79   info_ptr = png_create_info_struct(png_ptr);
80   PetscCheck(info_ptr, PETSC_COMM_SELF, PETSC_ERR_LIB, "Cannot create PNG context");
81 
82   /* setup libpng error handling */
83 #if defined(PNG_SETJMP_SUPPORTED)
84   if (setjmp(png_jmpbuf(png_ptr))) {
85     png_destroy_write_struct(&png_ptr, &info_ptr);
86     (void)PetscFClose(PETSC_COMM_SELF, fp);
87     SETERRQ(PETSC_COMM_SELF, PETSC_ERR_LIB, "Error writing PNG file %s", filename);
88   }
89 #endif
90 
91   /* setup PNG image metadata */
92   png_init_io(png_ptr, fp);
93   png_set_IHDR(png_ptr, info_ptr, w, h, /*depth*/ 8, palette ? PNG_COLOR_TYPE_PALETTE : PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
94   if (palette) png_set_PLTE(png_ptr, info_ptr, (png_color *)palette, 256);
95 
96   /* write PNG image header and data */
97   png_write_info(png_ptr, info_ptr);
98   for (row = 0; row < h; row++) png_write_row(png_ptr, pixels + row * stride);
99   png_write_end(png_ptr, NULL);
100 
101   /* destroy libpng structures and close file */
102   png_destroy_write_struct(&png_ptr, &info_ptr);
103   PetscCall(PetscFClose(PETSC_COMM_SELF, fp));
104   PetscFunctionReturn(0);
105 }
106 
107 static PetscErrorCode PetscDrawImageSave_PNG(const char filename[], unsigned char palette[][3], unsigned int w, unsigned int h, const unsigned char pixels[]) {
108   return PetscDrawImageSavePNG(filename, palette, w, h, pixels);
109 }
110 
111 #endif /*!PETSC_HAVE_LIBPNG*/
112 
113 /*
114    Code to write images in GIF format
115 */
116 #if defined(PETSC_HAVE_GIFLIB)
117 
118 #include <gif_lib.h>
119 
120 #if !defined(GIFLIB_MAJOR) || GIFLIB_MAJOR < 5
121 #define GifMakeMapObject            MakeMapObject
122 #define GifFreeMapObject            FreeMapObject
123 #define EGifOpenFileName(n, b, err) EGifOpenFileName(n, b)
124 #define EGifOpenFileHandle(h, err)  EGifOpenFileName(h)
125 #define EGifCloseFile(f, err)       EGifCloseFile(f)
126 #define DGifOpenFileName(n, err)    DGifOpenFileName(n)
127 #define DGifOpenFileHandle(h, err)  DGifOpenFileName(h)
128 #define DGifCloseFile(f, err)       DGifCloseFile(f)
129 #endif
130 
131 PETSC_EXTERN PetscErrorCode PetscDrawImageSaveGIF(const char filename[], unsigned char palette[][3], unsigned int w, unsigned int h, const unsigned char pixels[]) {
132   int             Row;
133   int             Width      = (int)w;
134   int             Height     = (int)h;
135   int             ColorRes   = 8;
136   int             ColorCount = 256;
137   ColorMapObject *GifCMap    = NULL;
138   GifFileType    *GifFile    = NULL;
139 #define SETERRGIF(msg) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_LIB, msg ", GIF file: %s", filename)
140 #define PetscCallGIF(msg, ...) \
141   do { \
142     int Error = __VA_ARGS__; \
143     if (PetscUnlikely(Error != GIF_OK)) SETERRGIF(msg); \
144   } while (0)
145 
146   PetscFunctionBegin;
147   PetscValidCharPointer(filename, 1);
148   PetscValidCharPointer(palette, 2);
149   PetscValidCharPointer(pixels, 5);
150 
151   GifCMap = GifMakeMapObject(ColorCount, (GifColorType *)palette);
152   if (!GifCMap) SETERRGIF("Allocating colormap");
153   GifFile = EGifOpenFileName(filename, 0, NULL);
154   if (!GifFile) SETERRGIF("Opening");
155   PetscCallGIF("Writing screen descriptor", EGifPutScreenDesc(GifFile, Width, Height, ColorRes, 0, GifCMap));
156   PetscCallGIF("Writing image descriptor", EGifPutImageDesc(GifFile, 0, 0, Width, Height, 0, NULL));
157   for (Row = 0; Row < Height; Row++) PetscCallGIF("Writing image pixels", EGifPutLine(GifFile, (GifPixelType *)pixels + Row * Width, Width));
158   PetscCallGIF("Closing", EGifCloseFile(GifFile, NULL));
159   GifFreeMapObject(GifCMap);
160   GifCMap = NULL;
161 
162 #undef SETERRGIF
163 #undef CHKERRGIF
164   PetscFunctionReturn(0);
165 }
166 
167 static PetscErrorCode PetscDrawImageSave_GIF(const char filename[], unsigned char palette[][3], unsigned int w, unsigned int h, const unsigned char pixels[]) {
168   return PetscDrawImageSaveGIF(filename, palette, w, h, pixels);
169 }
170 
171 PETSC_EXTERN PetscErrorCode PetscDrawMovieSaveGIF(const char pattern[], PetscInt count, const char movie[]) {
172   int          i, j, Row;
173   char         image[PETSC_MAX_PATH_LEN];
174   GifFileType *GifMovie = NULL;
175   GifFileType *GifImage = NULL;
176 #define SETERRGIF(msg, fn) SETERRQ(PETSC_COMM_SELF, PETSC_ERR_LIB, msg " GIF file %s", fn)
177 
178   PetscFunctionBegin;
179   PetscValidCharPointer(pattern, 1);
180   PetscValidCharPointer(movie, 3);
181   if (count < 1) PetscFunctionReturn(0);
182 
183   for (i = 0; i < count; i++) {
184     PetscCall(PetscSNPrintf(image, sizeof(image), pattern, (int)i));
185     /* open and read image file */
186     if ((GifImage = DGifOpenFileName(image, NULL)) == NULL) SETERRGIF("Opening input", image);
187     if (DGifSlurp(GifImage) != GIF_OK) SETERRGIF("Reading input", image);
188     /* open movie file and write header */
189     if (i == 0) {
190       if ((GifMovie = EGifOpenFileName(movie, 0, NULL)) == NULL) SETERRGIF("Opening output", movie);
191       if (EGifPutScreenDesc(GifMovie, GifImage->SWidth, GifImage->SHeight, GifImage->SColorResolution, GifImage->SBackGroundColor, GifImage->SColorMap) != GIF_OK) SETERRGIF("Writing screen descriptor,", movie);
192     }
193     /* loop over all frames in image */
194     for (j = 0; j < GifImage->ImageCount; j++) {
195       SavedImage     *sp            = &GifImage->SavedImages[j];
196       GifImageDesc   *GifFrame      = &sp->ImageDesc;
197       ColorMapObject *FrameColorMap = GifFrame->ColorMap ? GifFrame->ColorMap : GifImage->SColorMap;
198       if (GifMovie->SColorMap && GifMovie->SColorMap->ColorCount == FrameColorMap->ColorCount && !memcmp(GifMovie->SColorMap->Colors, FrameColorMap->Colors, (size_t)FrameColorMap->ColorCount * sizeof(GifColorType))) FrameColorMap = NULL;
199       /* add frame to movie */
200       if (EGifPutImageDesc(GifMovie, GifFrame->Left, GifFrame->Top, GifFrame->Width, GifFrame->Height, GifFrame->Interlace, FrameColorMap) != GIF_OK) SETERRGIF("Writing image descriptor,", movie);
201       for (Row = 0; Row < GifFrame->Height; Row++) {
202         if (EGifPutLine(GifMovie, sp->RasterBits + Row * GifFrame->Width, GifFrame->Width) != GIF_OK) SETERRGIF("Writing image pixels,", movie);
203       }
204     }
205     if (DGifCloseFile(GifImage, NULL) != GIF_OK) SETERRGIF("Closing input", image);
206   }
207   if (EGifCloseFile(GifMovie, NULL) != GIF_OK) SETERRGIF("Closing output", movie);
208 
209 #undef SETERRGIF
210   PetscFunctionReturn(0);
211 }
212 
213 #endif /*!PETSC_HAVE_GIFLIB*/
214 
215 /*
216    Code to write images in JPEG format
217 */
218 #if defined(PETSC_HAVE_LIBJPEG)
219 
220 #include <jpeglib.h>
221 
222 #if defined(PETSC_HAVE_SETJMP_H)
223 #include <setjmp.h>
224 static jmp_buf petsc_jpeg_jumpbuf;
225 static void    petsc_jpeg_error_longjmp(j_common_ptr cinfo) {
226      (void)cinfo;
227      longjmp(petsc_jpeg_jumpbuf, 1);
228 }
229 #endif
230 
231 PETSC_EXTERN PetscErrorCode PetscDrawImageSaveJPG(const char filename[], unsigned char palette[][3], unsigned int w, unsigned int h, const unsigned char pixels[]) {
232   unsigned char              *rgbpixels;
233   FILE                       *fp;
234   struct jpeg_compress_struct cinfo;
235   struct jpeg_error_mgr       jerr;
236 
237   PetscFunctionBegin;
238   PetscValidCharPointer(filename, 1);
239   if (palette) PetscValidCharPointer(palette, 2);
240   PetscValidCharPointer(pixels, 5);
241   /* map pixels to RGB colors */
242   if (palette) {
243     int                  k, p, n = (int)(w * h);
244     const unsigned char *colordef;
245     PetscCall(PetscMalloc1(3 * w * h, &rgbpixels));
246     for (k = p = 0; k < n; k++) {
247       colordef       = palette[pixels[k]];
248       rgbpixels[p++] = colordef[0];
249       rgbpixels[p++] = colordef[1];
250       rgbpixels[p++] = colordef[2];
251     }
252   } else { /* assume pixels are RGB colors */
253     rgbpixels = (unsigned char *)pixels;
254   }
255   PetscCall(PetscFOpen(PETSC_COMM_SELF, filename, "wb", &fp));
256 
257   cinfo.err = jpeg_std_error(&jerr);
258 #if defined(PETSC_HAVE_SETJMP_H)
259   jerr.error_exit = petsc_jpeg_error_longjmp;
260   if (setjmp(petsc_jpeg_jumpbuf)) {
261     char message[JMSG_LENGTH_MAX];
262     jerr.format_message((j_common_ptr)&cinfo, message);
263     jpeg_destroy_compress(&cinfo);
264     (void)PetscFClose(PETSC_COMM_SELF, fp);
265     SETERRQ(PETSC_COMM_SELF, PETSC_ERR_LIB, "Error writing JPEG file %s\n%s", filename, message);
266   }
267 #endif
268   jpeg_create_compress(&cinfo);
269   jpeg_stdio_dest(&cinfo, fp);
270   cinfo.image_width      = w;
271   cinfo.image_height     = h;
272   cinfo.input_components = 3;
273   cinfo.in_color_space   = JCS_RGB;
274   jpeg_set_defaults(&cinfo);
275   jpeg_start_compress(&cinfo, TRUE);
276   while (cinfo.next_scanline < cinfo.image_height) {
277     unsigned char *rowptr = rgbpixels + cinfo.next_scanline * 3 * w;
278     (void)jpeg_write_scanlines(&cinfo, &rowptr, 1);
279   }
280   jpeg_finish_compress(&cinfo);
281   jpeg_destroy_compress(&cinfo);
282 
283   PetscCall(PetscFClose(PETSC_COMM_SELF, fp));
284   if (palette) PetscCall(PetscFree(rgbpixels));
285   PetscFunctionReturn(0);
286 }
287 
288 static PetscErrorCode PetscDrawImageSave_JPG(const char filename[], unsigned char palette[][3], unsigned int w, unsigned int h, const unsigned char pixels[]) {
289   return PetscDrawImageSaveJPG(filename, palette, w, h, pixels);
290 }
291 
292 #endif /*!PETSC_HAVE_LIBJPEG*/
293 
294 static struct {
295   const char *extension;
296   PetscErrorCode (*SaveImage)(const char[], unsigned char[][3], unsigned int, unsigned int, const unsigned char[]);
297 } PetscDrawImageSaveTable[] = {
298 #if defined(PETSC_HAVE_LIBPNG)
299   {".png", PetscDrawImageSave_PNG},
300 #endif
301 #if defined(PETSC_HAVE_GIFLIB)
302   {".gif", PetscDrawImageSave_GIF},
303 #endif
304 #if defined(PETSC_HAVE_LIBJPEG)
305   {".jpg", PetscDrawImageSave_JPG},
306 #endif
307   {".ppm", PetscDrawImageSave_PPM}
308 };
309 
310 PetscErrorCode PetscDrawImageCheckFormat(const char *ext[]) {
311   size_t    k;
312   PetscBool match = PETSC_FALSE;
313 
314   PetscFunctionBegin;
315   /* if extension is empty, return default format to caller */
316   PetscValidPointer(ext, 1);
317   if (!*ext || !**ext) {
318     *ext = PetscDrawImageSaveTable[0].extension;
319     PetscFunctionReturn(0);
320   }
321   /* check the extension matches a supported format */
322   PetscValidCharPointer(*ext, 1);
323   for (k = 0; k < PETSC_STATIC_ARRAY_LENGTH(PetscDrawImageSaveTable); k++) {
324     PetscCall(PetscStrcasecmp(*ext, PetscDrawImageSaveTable[k].extension, &match));
325     if (match && PetscDrawImageSaveTable[k].SaveImage) PetscFunctionReturn(0);
326   }
327   SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "Image extension %s not supported, use .ppm or see PetscDrawSetSave() for what ./configure option you may need", *ext);
328 }
329 
330 PetscErrorCode PetscDrawImageSave(const char basename[], const char ext[], unsigned char palette[][3], unsigned int w, unsigned int h, const unsigned char pixels[]) {
331   size_t    k;
332   PetscBool match = PETSC_FALSE;
333   char      filename[PETSC_MAX_PATH_LEN];
334 
335   PetscFunctionBegin;
336   PetscValidCharPointer(basename, 1);
337   if (ext) PetscValidCharPointer(ext, 2);
338   if (palette) PetscValidPointer(palette, 3);
339   PetscValidCharPointer(pixels, 6);
340 
341   PetscCall(PetscDrawImageCheckFormat(&ext));
342   PetscCall(PetscSNPrintf(filename, sizeof(filename), "%s%s", basename, ext));
343   for (k = 0; k < PETSC_STATIC_ARRAY_LENGTH(PetscDrawImageSaveTable); k++) {
344     PetscCall(PetscStrcasecmp(ext, PetscDrawImageSaveTable[k].extension, &match));
345     if (match && PetscDrawImageSaveTable[k].SaveImage) {
346       PetscCall(PetscDrawImageSaveTable[k].SaveImage(filename, palette, w, h, pixels));
347       PetscFunctionReturn(0);
348     }
349   }
350   SETERRQ(PETSC_COMM_SELF, PETSC_ERR_SUP, "Image extension %s not supported, use .ppm", ext);
351 }
352 
353 PetscErrorCode PetscDrawMovieCheckFormat(const char *ext[]) {
354   PetscFunctionBegin;
355   PetscValidPointer(ext, 1);
356   if (!*ext || !**ext) *ext = ".m4v";
357   PetscFunctionReturn(0);
358 }
359 
360 PetscErrorCode PetscDrawMovieSave(const char basename[], PetscInt count, const char imext[], PetscInt fps, const char mvext[]) {
361   char      input[PETSC_MAX_PATH_LEN];
362   char      output[PETSC_MAX_PATH_LEN];
363   PetscBool gifinput;
364 
365   PetscFunctionBegin;
366   PetscValidCharPointer(basename, 1);
367   PetscValidCharPointer(imext, 3);
368   if (mvext) PetscValidCharPointer(mvext, 5);
369   if (count < 1) PetscFunctionReturn(0);
370 
371   PetscCall(PetscStrcasecmp(imext, ".gif", &gifinput));
372   PetscCall(PetscDrawMovieCheckFormat(&mvext));
373   PetscCall(PetscSNPrintf(input, sizeof(input), "%s/%s_%%d%s", basename, basename, imext));
374   PetscCall(PetscSNPrintf(output, sizeof(output), "%s%s", basename, mvext));
375 
376   /* use GIFLIB to generate an intermediate GIF animation */
377 #if defined(PETSC_HAVE_GIFLIB)
378   if (gifinput) {
379     char gifmovie[PETSC_MAX_PATH_LEN];
380     PetscCall(PetscSNPrintf(gifmovie, sizeof(gifmovie), "%s/%s_movie.gif", basename, basename));
381     PetscCall(PetscDrawMovieSaveGIF(input, count, gifmovie));
382     PetscCall(PetscStrcpy(input, gifmovie));
383   }
384 #endif
385 
386   /* use FFmpeg to generate a movie */
387 #if defined(PETSC_HAVE_POPEN)
388   {
389     FILE *fd;
390     char  options[64] = "-loglevel error -y", extraopts[32] = "", framerate[24] = "";
391     char  command[sizeof(options) + sizeof(extraopts) + sizeof(framerate) + PETSC_MAX_PATH_LEN * 2];
392     if (fps > 0) PetscCall(PetscSNPrintf(framerate, sizeof(framerate), "-r %d", (int)fps));
393     if (gifinput) {
394       PetscCall(PetscStrlcat(options, " -f gif", sizeof(options)));
395       PetscCall(PetscSNPrintf(extraopts, sizeof(extraopts), " -default_delay %d", (fps > 0) ? 100 / (int)fps : 4));
396     } else {
397       PetscCall(PetscStrlcat(options, " -f image2", sizeof(options)));
398       if (fps > 0) PetscCall(PetscSNPrintf(extraopts, sizeof(extraopts), " -framerate %d", (int)fps));
399     }
400     if (extraopts[0]) PetscCall(PetscStrlcat(options, extraopts, sizeof(options)));
401     PetscCall(PetscSNPrintf(command, sizeof(command), "ffmpeg %s -i \"%s\" %s \"%s\"", options, input, framerate, output));
402     PetscCall(PetscPOpen(PETSC_COMM_SELF, NULL, command, "r", &fd));
403     PetscCall(PetscPClose(PETSC_COMM_SELF, fd));
404   }
405 #endif
406   PetscFunctionReturn(0);
407 }
408