The OpenD Programming Language

1 /**
2 JPEG support.
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.plugins.jpeg;
8 
9 nothrow @nogc @safe:
10 
11 import core.stdc.stdlib: malloc, free, realloc;
12 import gamut.types;
13 import gamut.image;
14 import gamut.io;
15 import gamut.plugin;
16 import gamut.codecs.jpegload;
17 import gamut.codecs.stb_image_write;
18 import gamut.internals.errors;
19 import gamut.internals.types;
20 
21 
22 ImageFormatPlugin makeJPEGPlugin()
23 {
24     ImageFormatPlugin p;
25     p.format = "JPEG";
26     p.extensionList = "jpg,jpeg,jif,jfif";
27     p.mimeTypes = "image/jpeg";
28     version(decodeJPEG)
29         p.loadProc = &loadJPEG;
30     else
31         p.loadProc = null;
32     version(encodeJPEG)
33         p.saveProc = &saveJPEG;
34     else
35         p.saveProc = null;
36     p.detectProc = &detectJPEG;
37     return p;
38 }
39 
40 
41 version(decodeJPEG)
42 void loadJPEG(ref Image image, IOStream *io, IOHandle handle, int page, int flags, void *data) @trusted
43 {
44     JPEGIOHandle jio;
45     jio.wrapped = io;
46     jio.handle = handle;
47 
48     int requestedComp = computeRequestedImageComponents(flags);
49     if (requestedComp == 0)
50     {
51         image.error(kStrInvalidFlags);
52         return;
53     }
54 
55     if (requestedComp == 2) // JPEG reader doesn't convert to greyscale+alpha on the fly 
56         requestedComp = -1;
57 
58     int width, height, actualComp;
59     float pixelAspectRatio;
60     float dotsPerInchY;
61     ubyte[] decoded = decompress_jpeg_image_from_stream(&stream_read_jpeg, &jio, width, height, actualComp, pixelAspectRatio, dotsPerInchY, requestedComp);
62     if (decoded is null)
63     {
64         image.error(kStrImageDecodingFailed);
65         return;
66     }    
67 
68     if (actualComp != 1 && actualComp != 3 && actualComp != 4)
69     {
70         image.error(kStrImageWrongComponents);
71         free(decoded.ptr);
72         return;
73     }
74 
75     if (!imageIsValidSize(1, width, height))
76     {
77         image.error(kStrImageTooLarge);
78         free(decoded.ptr);
79         return;
80     }
81 
82     int decodedComp = (requestedComp == -1) ? actualComp : requestedComp;
83     switch (decodedComp)
84     {
85         case 1: image._type = PixelType.l8; break;
86         case 3: image._type = PixelType.rgb8; break;
87         case 4: image._type = PixelType.rgba8; break;
88         default:
89     }
90 
91     image._width = width;
92     image._height = height;
93     image._allocArea = decoded.ptr;
94     image._data = decoded.ptr;
95     image._pitch = width * decodedComp;
96     image._pixelAspectRatio = pixelAspectRatio == -1 ? GAMUT_UNKNOWN_ASPECT_RATIO : pixelAspectRatio;
97     image._resolutionY = dotsPerInchY == -1 ? GAMUT_UNKNOWN_RESOLUTION : dotsPerInchY;
98     image._layoutConstraints = LAYOUT_DEFAULT; // JPEG decoder follow no particular constraints (TODO?)   
99     image._layerCount = 1;
100     image._layerOffset = 0;
101 
102     // Convert to target type and constraints
103     image.convertTo(applyLoadFlags(image._type, flags), cast(LayoutConstraints) flags);
104 }
105 
106 bool detectJPEG(IOStream *io, IOHandle handle) @trusted
107 {
108     static immutable ubyte[2] jpegSignature = [0xFF, 0xD8];
109     return fileIsStartingWithSignature(io, handle, jpegSignature);
110 }
111 
112 version(encodeJPEG)
113 bool saveJPEG(ref const(Image) image, IOStream *io, IOHandle handle, int page, int flags, void *data) @trusted
114 {
115     if (page != 0)
116         return false;
117 
118     int components;
119 
120     switch (image._type)
121     {
122         case PixelType.l8:
123             components = 1; break;
124         case PixelType.rgb8:
125             components = 3; 
126             break;
127         case PixelType.rgba8:
128             return false; // stb would throw away alpha
129         default:
130             return false;
131     }
132 
133     JPEGIOHandle jio;
134     jio.wrapped = io;
135     jio.handle = handle;
136 
137     void* userPointer = cast(void*)&jio;
138 
139     int quality = 90; // TODO: option to choose that.
140 
141     int res = stbi_write_jpg_to_func(&stb_stream_write, userPointer, 
142                                         image._width, 
143                                         image._height, 
144                                         components, 
145                                         image._data, quality);
146 
147     return res == 1 && !jio.errored;
148 }
149 
150 private:
151 
152 
153 struct JPEGIOHandle
154 {
155     IOStream* wrapped;
156     IOHandle handle;
157 
158     // stb_image_write doesn't check errors for write, so keep a flag and start ignoring output if
159     // an I/O error occurs.
160     bool errored = false;
161 }
162 
163 /// This function is called when the internal input buffer is empty.
164 // userData must be a JPEGIOHandle*
165 int stream_read_jpeg(void* pBuf, int max_bytes_to_read, bool* pEOF_flag, void* userData) @system
166 {
167     JPEGIOHandle* jio = cast(JPEGIOHandle*) userData;
168     size_t read = jio.wrapped.read(pBuf, 1, max_bytes_to_read, jio.handle);
169     if (pEOF_flag)
170     {
171         *pEOF_flag = jio.wrapped.eof(jio.handle) != 0;
172     }
173     assert(read >= 0 && read <= 0x7fff_ffff);
174     return cast(int) read;
175 }
176 
177 // Note: context is a user pointer on a JPEGIOHandle.
178 void stb_stream_write(void *context, const(void)* data, int size) @system
179 {    
180     JPEGIOHandle* jio = cast(JPEGIOHandle*) context;
181 
182     if (jio.errored)
183         return;
184 
185     size_t written = jio.wrapped.write(data, 1, size, jio.handle);
186     if (written != size)
187         jio.errored = true; // poison the JPEGIOHandleB
188 }