The OpenD Programming Language

1 /**
2 GIF support.
3 
4 Copyright: Copyright Guillaume Piolat 2023
5 License:   $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
6 */
7 module gamut.plugins.gif;
8 
9 nothrow @nogc @safe:
10 
11 import core.stdc.stdlib: malloc, free, realloc;
12 import core.stdc.string: memcpy;
13 import gamut.types;
14 import gamut.io;
15 import gamut.plugin;
16 import gamut.image;
17 import gamut.internals.errors;
18 import gamut.internals.types;
19 
20 version(decodeGIF) import gamut.codecs.gif;
21 version(encodeGIF) import gamut.codecs.msf_gif;
22 
23 
24 ImageFormatPlugin makeGIFPlugin()
25 {
26     ImageFormatPlugin p;
27     p.format = "GIF";
28     p.extensionList = "gif";
29     p.mimeTypes = "image/gif";
30     version(decodeGIF)
31         p.loadProc = &loadGIF;
32     else
33         p.loadProc = null;
34     version(encodeGIF)
35         p.saveProc = &saveGIF;
36     else
37         p.saveProc = null;
38     p.detectProc = &detectGIF;
39     return p;
40 }
41 
42 bool detectGIF(IOStream *io, IOHandle handle) @trusted
43 {
44     static immutable ubyte[6] gif87Signature = [0x47, 0x49, 0x46, 0x38, 0x37, 0x61];
45     if (fileIsStartingWithSignature(io, handle, gif87Signature))
46         return true;
47 
48     static immutable ubyte[6] gif89Signature = [0x47, 0x49, 0x46, 0x38, 0x39, 0x61];
49     if (fileIsStartingWithSignature(io, handle, gif89Signature))
50         return true;
51 
52     return false;
53 }
54 
55 
56 version(decodeGIF)
57 void loadGIF(ref Image image, IOStream *io, IOHandle handle, int page, int flags, void *data) @trusted
58 { 
59     {
60         // Note: in that first opening, the whole file is scanned to get the number of frames.
61         GIFDecoder decoder;
62         bool err;
63         decoder.open(io, handle, &err);
64         if (err)
65         {
66             image.error(kStrImageDecodingFailed);
67             return;
68         }
69 
70         image._resolutionY = GAMUT_UNKNOWN_RESOLUTION;
71         image._pixelAspectRatio = decoder.getPixelAspectRatio();
72 
73         // TODO: store decoder.FPS() somewhere, need metadata
74 
75         // Create destination image.
76         // MAYDO: could use scanline conversion to create directly in l8/la8/rgb8, but limited value.
77         image.createLayeredNoInit(decoder.width,
78                                   decoder.height,
79                                   decoder.numLayers,
80                                   PixelType.rgba8,
81                                   cast(LayoutConstraints) flags);
82 
83         for (int layerIndex = 0; layerIndex < decoder.numLayers; ++layerIndex)
84         {
85             bool gotOneFrame;
86             int dummy;
87             Image layerN = image.layer(layerIndex);
88             decoder.decodeNextFrame(&gotOneFrame, 
89                                     &layerN,
90                                     &dummy,
91                                     &err);
92             if (err || !gotOneFrame)
93             {
94                 image.error(kStrImageDecodingFailed);
95                 return;
96             }
97         }
98 
99     } // clear decoder
100 
101     // Convert to target type and constraints.
102     image.convertTo(applyLoadFlags(image._type, flags), cast(LayoutConstraints) flags);
103 }
104 
105 version(encodeGIF)
106 bool saveGIF(ref const(Image) image, IOStream *io, IOHandle handle, int page, int flags, void *data) @trusted
107 {
108     if (page != 0)
109         return false;
110 
111     MsfGifState gifState;    
112     if (!msf_gif_begin(&gifState, image.width, image.height))
113         return false;
114 
115     // 0-frames .gif not supported (not sure if possible)
116     if (image.layers == 0)
117         return false;
118 
119     // Only pixelType rgba8 is supported!
120     PixelType type = image.type();
121     if (type != PixelType.rgba8)
122         return false;
123 
124     // MAYDO: could support more types by converting scanlines on-the-fly?
125  
126     for (int layerIndex = 0; layerIndex < image.layers; ++layerIndex)
127     {
128         const(Image) layer = image.layer(layerIndex);
129 
130         const(ubyte)* pixelData = cast(const(ubyte)*)layer.scanptr(0);
131         int centiSecondsPerFame = 7;
132         int maxBitDepth = 16;
133         int pitchInBytes = image.pitchInBytes();
134         if (!msf_gif_frame(&gifState, pixelData, centiSecondsPerFame, maxBitDepth, pitchInBytes))
135         {
136             return false;
137         }
138     }
139     MsfGifResult result = msf_gif_end(&gifState);
140 
141     // PERF: pass IO directly, use that to write data
142     if (1 != io.write(result.data, result.dataSize, 1, handle))
143         return false;
144 
145     msf_gif_free(result);
146     return true;
147 }