1 /**
2 General functions.
3 
4 Copyright: Copyright Guillaume Piolat 2022
5 License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6 */
7 module gamut.plugin;
8 
9 import core.stdc.string;
10 import gamut.types;
11 import gamut.image;
12 import gamut.io;
13 
14 import gamut.plugins.jpeg;
15 import gamut.plugins.png;
16 import gamut.plugins.qoi;
17 import gamut.plugins.qoix;
18 import gamut.plugins.dds;
19 
20 nothrow @nogc @safe:
21 
22 /// Function that loads a image from this format.
23 /// I/O rewinding: this function must be given an I/O cursor at the start of the the format.
24 ///                It doesn't have to preserve that I/O cursor.
25 alias LoadImageProc = void function(ref Image image, IOStream *io, IOHandle handle, int page, int flags, void *data);
26 
27 /// Saves an image from this format.
28 alias SaveImageProc = bool function(ref const(Image) image, IOStream *io, IOHandle handle, int page, int flags, void *data);
29 
30 /// Function that detects this format.
31 /// I/O rewinding: this function must preserve the I/O cursor by contract.
32 alias DetectImageFormatProc = bool function(IOStream *io, IOHandle handle);
33 
34 struct ImageFormatPlugin
35 {
36     /// Type string for the bitmap. For example, a plugin that loads BMPs returns the string "BMP".
37     const(char)* format;
38 
39     /// Comma-separated list of extension. A JPEG plugin would return "jpeg,jif,jfif".
40     const(char)* extensionList;
41 
42     /// MIME types, the first one being the best one.
43     const(char)* mimeTypes;
44 
45     LoadImageProc loadProc = null; // null => no read supported
46     SaveImageProc saveProc = null; // null => no write supported
47     DetectImageFormatProc detectProc = null;
48 }
49 
50 ImageFormat identifyImageFormatFromFilename(const(char) *filename) @trusted
51 {
52     if (filename is null)
53         return ImageFormat.unknown;
54 
55     // find extension inside filename
56     size_t ilen = strlen(filename);
57     size_t pos = ilen;
58     assert(filename[pos] == 0);
59     while(pos > 0 && filename[pos] != '.')
60         pos = pos - 1;
61     if (filename[pos] == '.') 
62         pos++;
63 
64     int extLength = cast(int)(ilen - pos);
65 
66     const(char)* fextension = filename + pos; // ex: "jpg", "png"...
67 
68     for(ImageFormat fif = ImageFormat.first; fif <= ImageFormat.max; ++fif)
69     {
70             // Is fextension in the list?
71         const(char)* str = g_plugins[fif].extensionList;
72 
73         while(true)
74         {
75             const(char)* end = str;
76             while (*end != ',' && *end != '\0')
77                 end++;
78             size_t sublen = end - str;
79             if (sublen == 0)
80                 break;
81 
82             if (extLength == sublen && strncmp(fextension, str, sublen) == 0)
83                 return fif;
84 
85             if (*end == '\0') // last extension for this format
86                 break;
87 
88             str = end + 1;
89         }
90     }
91     return ImageFormat.unknown;
92 }
93 unittest
94 {
95     assert(identifyImageFormatFromFilename("mysueprduperphoto.jpg") == ImageFormat.JPEG);
96     assert(identifyImageFormatFromFilename("mysueprduperphoto.jfif") == ImageFormat.JPEG);
97     assert(identifyImageFormatFromFilename("c:\\compromising-photo.qoi") == ImageFormat.QOI);
98     assert(identifyImageFormatFromFilename("my/path/to/file.qoix") == ImageFormat.QOIX);
99 }
100 
101 package:
102 
103 
104 
105 // For now, all plugin resides in a static __gshared part of the memory.
106 static immutable __gshared ImageFormatPlugin[ImageFormat.max+1] g_plugins =
107 [
108     makeJPEGPlugin(),
109     makePNGPlugin(),
110     makeQOIPlugin(),
111     makeQOIXPlugin(),
112     makeDDSPlugin(),
113 ];