Commits

Chris Reuter committed c9d2f89

Added gdImageFile(), gdImageCreateFromFile() and gdSupportsFileType().

These are convenience functions which load or save image data to a
file. They are roughly equivalent to opening a file handle with
fopen() and calling gdImageCreateFrom*() or gdImage*() on the FILE
pointer. However, these functions identify the input or output format
from the filename suffix and call the appropriate read or write
function accordingly.

gdSupportsFileType() can be used to test if a specific file format
is supported.

Most scripting interfaces already do something like this but now
there's support for doing it from C as well.

This change also adds test cases for the code and naturaldocs
documentation.

Comments (0)

Files changed (9)

docs/naturaldocs/project/Menu.txt

 
 File: About LibGD 2.1.1-dev  (preamble.txt)
 File: gd.h  (gd.h)
+File: gd_filename.c  (gd_filename.c)
 File: gd_filter.c  (gd_filter.c)
 File: gd_interpolation.c  (gd_interpolation.c)
 File: gdImageCreate  (gd.c)
                    gdfontmb.c gdfonts.c gdfontt.c gdft.c gdhelpers.c gdhelpers.h gdkanji.c gdtables.c gdxpm.c jisx0208.h wbmp.c wbmp.h   \
                    gd_filter.c gd_nnquant.c gd_rotate.c gd_matrix.c gd_interpolation.c gd_crop.c webpimg.c webpimg.h gd_webp.c gd_tiff.c \
                    gd_tga.c gd_tga.h gd_bmp.c bmp.h gd_color.h gd_nnquant.h gd_tga.h gd_intern.h gd_io_stream.h gd_xbm.c \
-		   gd_color_match.c gd_version.c
+		   gd_color_match.c gd_version.c gd_filename.c
 
 libgd_la_LDFLAGS = -version-info $(GDLIB_CURRENT):$(GDLIB_REVISION):$(GDLIB_AGE) -no-undefined
 
 BGD_DECLARE(gdImagePtr) gdImageCreateFromBmp (FILE * inFile);
 BGD_DECLARE(gdImagePtr) gdImageCreateFromBmpPtr (int size, void *data);
 BGD_DECLARE(gdImagePtr) gdImageCreateFromBmpCtx (gdIOCtxPtr infile);
+BGD_DECLARE(gdImagePtr) gdImageCreateFromFile(const char *filename);
 
 /* A custom data source. */
 /* The source function must return -1 on error, otherwise the number
 BGD_DECLARE(void) gdImageWBMP (gdImagePtr image, int fg, FILE * out);
 BGD_DECLARE(void) gdImageWBMPCtx (gdImagePtr image, int fg, gdIOCtx * out);
 
+BGD_DECLARE(int) gdImageFile(gdImagePtr im, const char *filename);
+BGD_DECLARE(int) gdSupportsFileType(const char *filename, int writing);
+
+
 /* Guaranteed to correctly free memory returned by the gdImage*Ptr
    functions */
 BGD_DECLARE(void) gdFree (void *m);

src/gd_filename.c

+/* Convenience functions to read or write images from or to disk,
+ * determining file type from the filename extension. */
+
+#ifdef HAVE_CONFIG_H
+#	include "config.h"
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+#include "gd.h"
+
+typedef gdImagePtr (*ReadFn)(FILE *in);
+typedef void (*WriteFn)(gdImagePtr im, FILE *out);
+typedef gdImagePtr (*LoadFn)(char *filename);
+
+#ifdef HAVE_LIBZ
+static void writegd2(gdImagePtr im, FILE *out) {
+    gdImageGd2(im, out, 0, GD2_FMT_COMPRESSED);
+}/* writegd*/
+#endif
+
+#ifdef HAVE_LIBJPEG
+static void writejpeg(gdImagePtr im, FILE *out) {
+    gdImageJpeg(im, out, -1);
+}/* writejpeg*/
+#endif
+
+static void writewbmp(gdImagePtr im, FILE *out) {
+    int fg = gdImageColorClosest(im, 0, 0, 0);
+    
+    gdImageWBMP(im, fg, out);
+}/* writejpeg*/
+
+static void writebmp(gdImagePtr im, FILE *out) {
+    gdImageBmp(im, out, GD_TRUE);
+}/* writejpeg*/
+
+
+enum FType {UNKNOWN, PNG, JPG, GIF, TIFF, GD, GD2, WEBP};
+static struct FileType {
+    const char *ext;
+    ReadFn reader;
+    WriteFn writer;
+    LoadFn loader;
+} Types[] = {
+    {".gif", gdImageCreateFromGif, gdImageGif, NULL},
+    {".gd",  gdImageCreateFromGd,  gdImageGd, NULL},
+    {".wbmp", gdImageCreateFromWBMP, writewbmp, NULL},
+    {".bmp", gdImageCreateFromBmp, writebmp, NULL},
+
+    {".xbm", gdImageCreateFromXbm, NULL, NULL},
+    {".tga", gdImageCreateFromTga, NULL, NULL},
+
+#ifdef HAVE_LIBPNG
+    {".png", gdImageCreateFromPng, gdImagePng, NULL},
+#endif
+
+#ifdef HAVE_LIBJPEG
+    {".jpg", gdImageCreateFromJpeg, writejpeg, NULL},
+    {".jpeg", gdImageCreateFromJpeg, writejpeg, NULL},
+#endif
+
+#ifdef HAVE_LIBTIFF    
+    {".tiff", gdImageCreateFromTiff, gdImageTiff, NULL},
+    {".tif" , gdImageCreateFromTiff, gdImageTiff, NULL},
+#endif
+
+#ifdef HAVE_LIBZ
+    {".gd2", gdImageCreateFromGd2, writegd2, NULL},
+#endif
+
+#ifdef HAVE_LIBVPX
+    {".webp", gdImageCreateFromWebp, gdImageWebp, NULL},
+#endif
+
+#ifdef HAVE_LIBXPM
+    {".xpm", NULL, NULL, gdImageCreateFromXpm},
+#endif
+
+    {NULL, NULL, NULL}
+};
+
+
+struct FileType *
+ftype(const char *filename) {
+    int n;
+    char *ext;
+
+    /* Find the file extension (i.e. the last period in the string. */
+    ext = rindex(filename, '.');
+    if (!ext) return NULL;
+    
+    for (n = 0; Types[n].ext; n++) {
+        if (strcasecmp(ext, Types[n].ext) == 0) {
+            return &Types[n];
+        }/* if */
+    }/* for */
+
+    return NULL;
+}/* ftype*/
+
+
+/*
+  Function: gdSupportsFileType
+  
+    Tests if a given file type is supported by GD.
+
+    Given the name of an image file (which does not have to exist),
+    returns 1 (i.e. TRUE) if <gdImageCreateFromFile> can read a file
+    of that type.  This is useful if you do not know which image types
+    were enabled at compile time.
+
+    If _writing_ is true, the result will be true only if
+    <gdImageFile> can write a file of this type.
+
+    Note that filename parsing is done exactly the same as is done by
+    <gdImageCreateFromFile> and <gdImageFile> and is subject to the
+    same limitations.
+
+    Assuming LibGD is compiled with support for these image types, the
+    following extensions are supported:
+
+        - .gif
+        - .gd, .gd2
+        - .wbmp
+        - .bmp
+        - .xbm
+        - .tga
+        - .png
+        - .jpg, .jpeg
+        - .tiff, .tif
+        - .webp
+        - .xpm
+
+    Names are parsed case-insenstively.
+
+  Parameters:
+
+    filename    - Filename with tested extension.
+    writing     - Flag: true tests if writing works
+
+  Returns:
+
+    GD_TRUE (1) if the file type is supported, GD_FALSE (0) if not.
+
+*/
+BGD_DECLARE(int) 
+gdSupportsFileType(const char *filename, int writing) {
+    struct FileType *entry = ftype(filename);
+    return !!entry && (!writing || !!entry->writer);
+}/* gdSupportsFiletype*/
+
+
+/*
+  Function: gdImageCreateFromFile
+
+    Read an image file of any supported.
+
+    Given the path to a file, <gdImageCreateFromFile> will open the
+    file, read its contents with the appropriate _gdImageCreateFrom*_
+    function and return it.  
+
+    File type is determined by the filename extension, so having an
+    incorrect extension will probably not work.  For example, renaming
+    PNG image "foo.png" to "foo.gif" and then attempting to load it
+    will fail even if GD supports both formats.  See
+    <gdSupportsFiletype> for more details.
+
+    NULL is returned on error.
+
+  Parameters:
+
+    filename    - the input file name
+
+  Returns:
+
+    A pointer to the new image or NULL if an error occurred.
+
+*/
+
+BGD_DECLARE(gdImagePtr) 
+gdImageCreateFromFile(const char *filename) {
+    struct FileType *entry = ftype(filename);
+    FILE *fh;
+    gdImagePtr result;
+ 
+    if (!entry) return NULL;
+    if (entry->loader) return entry->loader((char *)filename);
+    if (!entry->reader) return NULL;
+
+    fh = fopen(filename, "rb");
+    if (!fh) return NULL;
+
+    result = entry->reader(fh);
+    
+    fclose(fh);
+
+    return result;
+}/* gdImageCreateFromFile*/
+
+
+
+/*
+  Function: gdImageFile
+
+    Writes an image to a file in the format indicated by the filename.
+
+    File type is determined by the extension of the file name.  See
+    <gdSupportsFiletype> for an overview of the parsing.
+
+    For file types that require extra arguments, <gdImageFile>
+    attempts to use sane defaults:
+
+    <gdImageGd2>    - chunk size = 0, compression is enabled.
+    <gdImageJpeg>   - quality = -1 (i.e. the reasonable default)
+    <gdImageWBMP>   - foreground is the darkest available color
+
+    Everything else is called with the two-argument function and so
+    will use the default values.
+
+    <gdImageFile> has some rudimentary error detection and will return
+    GD_FALSE (0) if a detectable error occurred.  However, the image
+    loaders do not normally return their error status so a result of
+    GD_TRUE (1) does **not** mean the file was saved successfully.
+
+  Parameters:
+
+    im          - The image to save.
+    filename    - The path to the file to which the image is saved.
+
+  Returns:
+
+    GD_FALSE (0) if an error was detected, GD_TRUE (1) if not.
+
+*/
+
+BGD_DECLARE(int) 
+gdImageFile(gdImagePtr im, const char *filename) {
+    struct FileType *entry = ftype(filename);
+    FILE *fh;
+
+    if (!entry || !entry->writer) return GD_FALSE;
+
+    fh = fopen(filename, "wb");
+    if (!fh) return GD_FALSE;
+
+    entry->writer(im, fh);
+
+    fclose(fh);
+
+    return GD_TRUE;
+}/* gdImageFile*/
+

tests/Makefile.am

 	gdinterpolatedscale/gdTrivialResize \
 	gdinterpolatedscale/gdModesAndPalettes \
 	gd/gd_versiontest \
-	gdimagefilter/gdCopyBlurred
+	gdimagefilter/gdCopyBlurred \
+	gdimagefile/gdnametest
 
 EXTRA_PROGRAMS = \
 	gdimagestringft/gdimagestringft_bbox \

tests/gdimagefile/gdnametest.c

+#include <stdio.h>
+#include <stdlib.h>
+
+#include "gd.h"
+#include "gdtest.h"
+
+#define WIDTH 60
+#define HEIGHT 50
+#define LX (WIDTH/2)    // Line X
+#define LY (HEIGHT/2)   // Line Y
+#define HT 2            // Half of line-thickness
+
+
+gdImagePtr mkwhite(int x, int y)
+{
+    gdImagePtr im;
+
+	im = gdImageCreateTrueColor(x, y);
+	gdImageFilledRectangle(im, 0, 0, x-1, y-1,
+                           gdImageColorExactAlpha(im, 255, 255, 255, 0));
+
+    gdTestAssert(im != NULL);
+
+    gdImageSetInterpolationMethod(im, GD_BICUBIC);    // FP interp'n
+
+    return im;
+}/* mkwhite*/
+
+
+gdImagePtr mkcross() {
+    gdImagePtr im;
+    int fg, n;
+
+    im = mkwhite(WIDTH, HEIGHT);
+    fg = gdImageColorAllocate(im, 0, 0, 0);
+
+    for (n = -HT; n < HT; n++) {
+        gdImageLine(im, LX-n, 0, LX-n, HEIGHT-1, fg);
+        gdImageLine(im, 0, LY-n, WIDTH-1, LY-n, fg);
+    }/* for */
+
+    return im;
+}/* mkcross*/
+
+
+
+
+void
+do_test() {
+    int n;
+    struct {
+        const char *nm;     // Filename
+        unsigned maxdiff;   // Maximum total pixel diff
+        int required;       // 1 -> image type always supported, -1 -> skip it
+        int readonly;       // 1 -> gd can only read this type
+    } names[] = {
+        {"img.png",     0,  0,  0},
+        {"img.gif",     5,  1,  0},     // This seems to come from tc<->palette
+        {"img.GIF",     5,  1,  0},     // Test for case insensitivity
+        {"img.gd",      0,  1,  0}, 
+        {"img.gd2",     0,  0,  0}, 
+        {"img.jpg",    25,  0,  0},
+        {"img.jpeg",   25,  0,  0},
+        {"img.wbmp",    0,  1,  0},
+        {"img.bmp",     0,  1,  0},
+        {"img-ref.xpm", 0,  0,  1},
+        
+        // These break the test so I'm skipping them since the point
+        // of this test is not those loaders.
+        {"img-ref.xbm", 0, -1,  1},
+        {"img-ref.tga", 0, -1,  1},
+        {"img.webp",    0, -1,  0},
+
+        {NULL, 0}
+    };
+
+    for (n = 0; names[n].nm; n++) {
+        gdImagePtr orig, copy;
+        int status;
+        char full_filename[255];
+
+        /* Some image readers are buggy and crash the program so we
+         * skip them.  Bug fixers should remove these from the list of
+         * skipped items as bugs are fixed. */
+        if (names[n].required < 0) {
+            printf("Skipping test for '%s'.  FIX THIS!\n", names[n].nm);
+            continue;
+        }/* if */
+
+        /* Skip this file if the current library build doesn't support
+         * it.  (If it's one of the built-in types, *that* a different
+         * problem; we assert that here.) */
+        if (!gdSupportsFileType(names[n].nm, 0)) {
+            gdTestAssert(!names[n].required);
+            continue;
+        }/* if */
+
+        orig = mkcross();
+
+        /* Prepend the test directory; this is expected to be run in
+         * the parent dir. */
+        snprintf(full_filename, sizeof(full_filename), "gdimagefile/%s",
+                 names[n].nm);
+
+        /* Write the image unless writing is not supported. */
+        if (!names[n].readonly) {
+            gdImageFile(orig, full_filename);
+        }/* if */
+
+        copy = gdImageCreateFromFile(full_filename);
+        gdTestAssert(!!copy);
+        if (!copy) continue;
+
+        /* Debug printf. */
+        //printf("%s -> %d\n", full_filename, gdMaxPixelDiff(orig, copy));
+        
+        gdTestAssert(gdMaxPixelDiff(orig, copy) <= names[n].maxdiff);
+
+        if (!names[n].readonly) {
+            status = remove(full_filename);
+            gdTestAssert(status == 0);
+        }/* if */
+
+        gdImageDestroy(orig);
+        gdImageDestroy(copy);
+    }/* for */
+
+}/* do_test*/
+
+
+void
+do_errortest() {
+    gdImagePtr im;
+
+    im = mkcross();
+
+    gdTestAssert(!gdImageFile(im, "img.xpng"));
+    gdTestAssert(!gdImageFile(im, "bobo"));
+    gdTestAssert(!gdImageFile(im, "png"));
+    gdTestAssert(!gdImageFile(im, ""));
+
+    gdImageDestroy(im);
+}/* do_errortest*/
+
+
+int main(int argc, char **argv)
+{
+
+    do_test();
+    do_errortest();
+
+    return gdNumFailures();
+}

tests/gdimagefile/img-ref.tga

Added
New image

tests/gdimagefile/img-ref.xbm

Added
New image
+#define foo-ref_width 60
+#define foo-ref_height 50
+static char foo-ref_bits[] = {
+  0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 
+  0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 
+  0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 
+  0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 
+  0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 
+  0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 
+  0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 
+  0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 
+  0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 
+  0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0xE0, 
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 
+  0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 
+  0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 
+  0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 
+  0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 
+  0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 
+  0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 
+  0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 
+  0x00, 0x00, 0x00, 0xE0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 
+  0x01, 0x00, 0x00, 0x00, };

tests/gdimagefile/img-ref.xpm

Added
New image
+/* XPM */
+static char *img_ref[] = {
+/* columns rows colors chars-per-pixel */
+"60 50 2 1 ",
+"  c gray100",
+". c black",
+/* pixels */
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"............................................................",
+"............................................................",
+"............................................................",
+"............................................................",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           ",
+"                             ....                           "
+};