Vuo  2.0.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 
24 extern "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 
50 static 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 
65 float 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 
98 VuoImage 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 }