Vuo  2.4.0
VuoImageGet.cc
Go to the documentation of this file.
1
10#include "VuoImageGet.h"
11#include "VuoUrlFetch.h"
12
13#include <string.h>
14
15#pragma clang diagnostic push
16#pragma clang diagnostic ignored "-Wdocumentation"
17#include <FreeImage.h>
18#pragma clang diagnostic pop
19
20#include <OpenGL/CGLMacro.h>
21
22#include "module.h"
23
24extern "C"
25{
26#ifdef VUO_COMPILER
28 "title" : "VuoImageGet",
29 "dependencies" : [
30 "VuoImage",
31 "VuoUrlFetch",
32 "freeimage"
33 ]
34 });
35#endif
36}
37
38
42__attribute__((constructor)) static void VuoImageGet_init(void)
43{
44 FreeImage_Initialise(true);
45}
46
50static void FreeImageErrorHandler(FREE_IMAGE_FORMAT fif, const char *message)
51{
52 if (fif != FIF_UNKNOWN)
53 VUserLog("Error: %s (%s format)", message, FreeImage_GetFormatFromFIF(fif));
54 else
55 VUserLog("Error: %s", message);
56}
57
65float VuoImage_getScaleFactor(const char *url)
66{
68 if (!resolvedUrl)
69 return 1;
70 VuoLocal(resolvedUrl);
71
72 VuoText path;
73 if (!VuoUrl_getParts(resolvedUrl, NULL, NULL, NULL, NULL, &path, NULL, NULL))
74 return 1;
75 VuoLocal(path);
76
77 size_t lastDot = VuoText_findLastOccurrence(path, ".");
78 if (lastDot < 6)
79 return 1;
80
81 // `@` is URL-escaped as `%40`.
82 if (path[lastDot-6] == '%' && path[lastDot-5] == '4' && path[lastDot-4] == '0' && path[lastDot-2] == 'x')
83 {
84 char c = path[lastDot-3];
85 if (c >= '1' && c <= '9')
86 return c - '0';
87 }
88
89 return 1;
90}
91
98VuoImage VuoImage_get(const char *imageURL)
99{
100 if (!imageURL || !strlen(imageURL))
101 return NULL;
102
103 void *data;
104 unsigned int dataLength;
105 if (!VuoUrl_fetch(imageURL, &data, &dataLength))
106 return NULL;
107
108 // Decode the memory buffer into a straightforward array of BGRA pixels
109 FreeImage_SetOutputMessage(FreeImageErrorHandler);
110 FIBITMAP *dib;
111 GLuint format;
112 VuoImageColorDepth colorDepth;
113 unsigned char *pixels;
114 unsigned long pixelsWide;
115 unsigned long pixelsHigh;
116 {
117 FIMEMORY *hmem = FreeImage_OpenMemory((BYTE *)data, dataLength);
118
119 FREE_IMAGE_FORMAT fif = FreeImage_GetFileTypeFromMemory(hmem, 0);
120 if (fif == FIF_UNKNOWN)
121 {
122 VUserLog("Error: '%s': Couldn't determine image type.", imageURL);
123 return NULL;
124 }
125 if (!FreeImage_FIFSupportsReading(fif))
126 {
127 VUserLog("Error: '%s': This image type doesn't support reading.", imageURL);
128 return NULL;
129 }
130
131 dib = FreeImage_LoadFromMemory(fif, hmem, JPEG_EXIFROTATE);
132 FreeImage_CloseMemory(hmem);
133
134 if (!dib)
135 {
136 VUserLog("Error: '%s': Failed to read image.", imageURL);
137 return NULL;
138 }
139
140 pixelsWide = FreeImage_GetWidth(dib);
141 pixelsHigh = FreeImage_GetHeight(dib);
142
143 const FREE_IMAGE_TYPE type = FreeImage_GetImageType(dib);
144 const unsigned int bpp = FreeImage_GetBPP(dib);
145 const FREE_IMAGE_COLOR_TYPE colorType = FreeImage_GetColorType(dib);
146 const FIICCPROFILE *colorProfile = FreeImage_GetICCProfile(dib);
147 FITAG *exifColorSpace = NULL;
148 FreeImage_GetMetadata(FIMD_EXIF_EXIF, dib, "ColorSpace", &exifColorSpace);
149 VDebugLog("ImageFormat=%d ImageType=%d BPP=%d colorType=%s Profile=%s(%d) EXIF_ColorSpace=%s", fif, type, bpp,
150 colorType == FIC_MINISWHITE ? "minIsWhite" :
151 (colorType == FIC_MINISBLACK ? "minIsBlack" :
152 (colorType == FIC_RGB ? "RGB" :
153 (colorType == FIC_PALETTE ? "indexed" :
154 (colorType == FIC_RGBALPHA ? "RGBA" :
155 (colorType == FIC_CMYK ? "CMYK" : "unknown"))))),
156 colorProfile->flags & FIICC_COLOR_IS_CMYK ? "CMYK" : "RGB",
157 colorProfile->size,
158 FreeImage_TagToString(FIMD_EXIF_EXIF, exifColorSpace));
159
160 if (type == FIT_FLOAT
161 || type == FIT_DOUBLE
162 || type == FIT_UINT16
163 || type == FIT_INT16
164 || type == FIT_UINT32
165 || type == FIT_INT32)
166 {
167 // If it is > 8bpc greyscale, convert to float.
168 colorDepth = VuoImageColorDepth_32;
169 format = GL_LUMINANCE;
170
171 FIBITMAP *dibFloat = FreeImage_ConvertToFloat(dib);
172 FreeImage_Unload(dib);
173 dib = dibFloat;
174 }
175 else if (type == FIT_RGB16
176 || type == FIT_RGBF)
177 {
178 // If it is > 8bpc WITHOUT an alpha channel, convert to float.
179 colorDepth = VuoImageColorDepth_32;
180 format = GL_RGB;
181
182 FIBITMAP *dibFloat = FreeImage_ConvertToRGBF(dib);
183 FreeImage_Unload(dib);
184 dib = dibFloat;
185 }
186 else if (type == FIT_RGBA16
187 || type == FIT_RGBAF)
188 {
189 // If it is > 8bpc WITH an alpha channel, convert to float.
190 colorDepth = VuoImageColorDepth_32;
191 format = GL_RGBA;
192
193 FIBITMAP *dibFloat = FreeImage_ConvertToRGBAF(dib);
194 FreeImage_Unload(dib);
195 dib = dibFloat;
196
197 // FreeImage_PreMultiplyWithAlpha() only works on 8bpc images, so do it ourself.
198 float *pixels = (float *)FreeImage_GetBits(dib);
199 if (!pixels)
200 {
201 VUserLog("Error: '%s': Couldn't get pixels from image.", imageURL);
202 FreeImage_Unload(dib);
203 return NULL;
204 }
205 for (int y = 0; y < pixelsHigh; ++y)
206 for (int x = 0; x < pixelsWide; ++x)
207 {
208 float alpha = pixels[(y*pixelsWide + x)*4 + 3];
209 pixels[(y*pixelsWide + x)*4 + 0] *= alpha;
210 pixels[(y*pixelsWide + x)*4 + 1] *= alpha;
211 pixels[(y*pixelsWide + x)*4 + 2] *= alpha;
212 }
213 }
214 else
215 {
216 // Upload other images as 8bpc.
217 colorDepth = VuoImageColorDepth_8;
218
219 if (colorType == FIC_MINISWHITE
220 || colorType == FIC_MINISBLACK)
221 {
222 format = GL_LUMINANCE;
223
224 FIBITMAP *dibConverted = FreeImage_ConvertTo8Bits(dib);
225 FreeImage_Unload(dib);
226 dib = dibConverted;
227 }
228 else if (colorType == FIC_RGB
229 || (colorType == FIC_PALETTE && !FreeImage_IsTransparent(dib)))
230 {
231 format = GL_BGR;
232
233 FIBITMAP *dibConverted = FreeImage_ConvertTo24Bits(dib);
234 FreeImage_Unload(dib);
235 dib = dibConverted;
236 }
237 else if (colorType == FIC_RGBALPHA
238 || (colorType == FIC_PALETTE && FreeImage_IsTransparent(dib)))
239 {
240 format = GL_BGRA;
241
242 FIBITMAP *dibConverted = FreeImage_ConvertTo32Bits(dib);
243 FreeImage_Unload(dib);
244 dib = dibConverted;
245
246 if (!FreeImage_PreMultiplyWithAlpha(dib))
247 VUserLog("Warning: Premultiplication failed.");
248 }
249 else
250 {
251 VUserLog("Error: '%s': Unknown colorType %d.", imageURL, colorType);
252 FreeImage_Unload(dib);
253 return NULL;
254 }
255 }
256
257 pixels = FreeImage_GetBits(dib);
258 if (!pixels)
259 {
260 VUserLog("Error: '%s': Couldn't get pixels from image.", imageURL);
261 FreeImage_Unload(dib);
262 return NULL;
263 }
264 }
265
266 // FreeImage's documentation says "Every scanline is DWORD-aligned."
267 // …so round the row stride up to the nearest 4 bytes.
268 // See @ref TestVuoImage::testFetchOddStride.
269 int bytesPerRow = pixelsWide * VuoGlTexture_getBytesPerPixelForInternalFormat(VuoImageColorDepth_getGlInternalFormat(format, colorDepth));
270 bytesPerRow = (bytesPerRow + 3) & ~0x3;
271
272 VuoImage vuoImage = VuoImage_makeFromBufferWithStride(pixels, format, pixelsWide, pixelsHigh, bytesPerRow, colorDepth, ^(void *buffer){
273 FreeImage_Unload(dib);
274 free(data);
275 });
276 if (!vuoImage)
277 return NULL;
278
279 vuoImage->scaleFactor = VuoImage_getScaleFactor(imageURL);
280
281 return vuoImage;
282}