1 /*
2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3 % %
4 % %
5 % %
6 % CCCC U U BBBB EEEEE %
7 % C U U B B E %
8 % C U U BBBB EEE %
9 % C U U B B E %
10 % CCCC UUU BBBB EEEEE %
11 % %
12 % %
13 % Cube LUT Image Format %
14 % %
15 % Software Design %
16 % Cristy %
17 % July 2018 %
18 % %
19 % %
20 % Copyright 1999-2019 ImageMagick Studio LLC, a non-profit organization %
21 % dedicated to making software imaging solutions freely available. %
22 % %
23 % You may not use this file except in compliance with the License. You may %
24 % obtain a copy of the License at %
25 % %
26 % https://imagemagick.org/script/license.php %
27 % %
28 % Unless required by applicable law or agreed to in writing, software %
29 % distributed under the License is distributed on an "AS IS" BASIS, %
30 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
31 % See the License for the specific language governing permissions and %
32 % limitations under the License. %
33 % %
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35 %
36 % See Cube LUT specification 1.0 @
37 % https://wwwimages2.adobe.com/content/dam/acom/en/products/speedgrade/cc/pdfs/cube-lut-specification-1.0.pdf
38 %
39 */
40
41 /*
42 Include declarations.
43 */
44 #include "MagickCore/studio.h"
45 #include "MagickCore/blob.h"
46 #include "MagickCore/blob-private.h"
47 #include "MagickCore/cache.h"
48 #include "MagickCore/colorspace.h"
49 #include "MagickCore/exception.h"
50 #include "MagickCore/exception-private.h"
51 #include "MagickCore/image.h"
52 #include "MagickCore/image-private.h"
53 #include "MagickCore/list.h"
54 #include "MagickCore/magick.h"
55 #include "MagickCore/memory_.h"
56 #include "MagickCore/module.h"
57 #include "MagickCore/monitor.h"
58 #include "MagickCore/monitor-private.h"
59 #include "MagickCore/pixel-accessor.h"
60 #include "MagickCore/property.h"
61 #include "MagickCore/quantum-private.h"
62 #include "MagickCore/resource_.h"
63 #include "MagickCore/static.h"
64 #include "MagickCore/string_.h"
65 #include "MagickCore/string-private.h"
66 #include "MagickCore/thread-private.h"
67 #include "MagickCore/token.h"
68
69 /*
70 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
71 % %
72 % %
73 % %
74 % R e a d C U B E I m a g e %
75 % %
76 % %
77 % %
78 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
79 %
80 % ReadCUBEImage() creates a Cube color lookup table image and returns it. It
81 % allocates the memory necessary for the new Image structure and returns a
82 % pointer to the new image.
83 %
84 % The format of the ReadCUBEImage method is:
85 %
86 % Image *ReadCUBEImage(const ImageInfo *image_info,
87 % ExceptionInfo *exception)
88 %
89 % A description of each parameter follows:
90 %
91 % o image_info: the image info.
92 %
93 % o exception: return any errors or warnings in this structure.
94 %
95 */
ReadCUBEImage(const ImageInfo * image_info,ExceptionInfo * exception)96 static Image *ReadCUBEImage(const ImageInfo *image_info,
97 ExceptionInfo *exception)
98 {
99 #define FlattenCube(level,b,g,r) ((ssize_t) ((b)*(level)*(level)+(g)*(level)+(r)))
100
101 typedef struct _CubePixel
102 {
103 float
104 r,
105 g,
106 b;
107 } CubePixel;
108
109 char
110 *buffer,
111 token[MagickPathExtent],
112 value[MagickPathExtent];
113
114 CubePixel
115 *cube;
116
117 Image
118 *image;
119
120 MagickBooleanType
121 status;
122
123 MemoryInfo
124 *cube_info;
125
126 register char
127 *p;
128
129 size_t
130 cube_level,
131 hald_level;
132
133 ssize_t
134 b,
135 i,
136 n;
137
138 /*
139 Read CUBE color lookup table.
140 */
141 assert(image_info != (const ImageInfo *) NULL);
142 assert(image_info->signature == MagickCoreSignature);
143 if (image_info->debug != MagickFalse)
144 (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s",
145 image_info->filename);
146 assert(exception != (ExceptionInfo *) NULL);
147 assert(exception->signature == MagickCoreSignature);
148 image=AcquireImage(image_info,exception);
149 status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception);
150 if (status == MagickFalse)
151 {
152 image=DestroyImageList(image);
153 return((Image *) NULL);
154 }
155 cube_level=0;
156 cube_info=(MemoryInfo *) NULL;
157 cube=(CubePixel *) NULL;
158 n=0;
159 buffer=AcquireString("");
160 *buffer='\0';
161 p=buffer;
162 while (ReadBlobString(image,p) != (char *) NULL)
163 {
164 const char
165 *q;
166
167 q=p;
168 GetNextToken(q,&q,MagickPathExtent,token);
169 if ((*token == '#') || (*token == '\0'))
170 continue;
171 if ((LocaleCompare(token,"LUT_1D_SIZE") == 0) ||
172 (LocaleCompare(token,"LUT_3D_SIZE") == 0))
173 {
174 if (cube_info != (MemoryInfo *) NULL)
175 cube_info=RelinquishVirtualMemory(cube_info);
176 GetNextToken(q,&q,MagickPathExtent,value);
177 cube_level=(size_t) StringToLong(value);
178 if (LocaleCompare(token,"LUT_1D_SIZE") == 0)
179 cube_level=(size_t) ceil(pow((double) cube_level,1.0/3.0));
180 if ((cube_level < 2) || (cube_level > 256))
181 {
182 buffer=DestroyString(buffer);
183 ThrowReaderException(CorruptImageError,"ImproperImageHeader");
184 }
185 cube_info=AcquireVirtualMemory(cube_level*cube_level,cube_level*
186 sizeof(*cube));
187 if (cube_info == (MemoryInfo *) NULL)
188 {
189 buffer=DestroyString(buffer);
190 ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed");
191 }
192 cube=(CubePixel *) GetVirtualMemoryBlob(cube_info);
193 (void) memset(cube,0,cube_level*cube_level*cube_level*sizeof(*cube));
194 }
195 else
196 if (LocaleCompare(token,"TITLE ") == 0)
197 {
198 GetNextToken(q,&q,MagickPathExtent,value);
199 (void) SetImageProperty(image,"title",value,exception);
200 }
201 else
202 if (cube_level != 0)
203 {
204 char
205 *q;
206
207 if (n >= (ssize_t) (cube_level*cube_level*cube_level))
208 break;
209 q=buffer;
210 cube[n].r=StringToDouble(q,&q);
211 cube[n].g=StringToDouble(q,&q);
212 cube[n].b=StringToDouble(q,&q);
213 n++;
214 }
215 else
216 if (('+' < *buffer) && (*buffer < ':'))
217 break;
218 }
219 buffer=DestroyString(buffer);
220 if (cube_level == 0)
221 {
222 if (cube_info != (MemoryInfo *) NULL)
223 cube_info=RelinquishVirtualMemory(cube_info);
224 ThrowReaderException(CorruptImageError,"ImproperImageHeader");
225 }
226 /*
227 Write HALD image.
228 */
229 status=MagickTrue;
230 hald_level=image_info->scene;
231 if ((hald_level < 2) || (hald_level > 256))
232 hald_level=8;
233 image->columns=(size_t) (hald_level*hald_level*hald_level);
234 image->rows=(size_t) (hald_level*hald_level*hald_level);
235 status=SetImageExtent(image,image->columns,image->rows,exception);
236 if (status == MagickFalse)
237 {
238 cube_info=RelinquishVirtualMemory(cube_info);
239 return(DestroyImageList(image));
240 }
241 for (b=0; b < (ssize_t) (hald_level*hald_level); b++)
242 {
243 register ssize_t
244 g;
245
246 if (status == MagickFalse)
247 continue;
248 for (g=0; g < (ssize_t) (hald_level*hald_level); g++)
249 {
250 register Quantum
251 *magick_restrict q;
252
253 register ssize_t
254 r;
255
256 if (status == MagickFalse)
257 continue;
258 q=QueueAuthenticPixels(image,(g % hald_level)*(hald_level*hald_level),
259 (b*hald_level)+((g/hald_level) % (hald_level*hald_level)),hald_level*
260 hald_level,1,exception);
261 if (q == (Quantum *) NULL)
262 {
263 status=MagickFalse;
264 continue;
265 }
266 for (r=0; r < (ssize_t) (hald_level*hald_level); r++)
267 {
268 CubePixel
269 index,
270 next,
271 offset,
272 scale;
273
274 offset.r=(PerceptibleReciprocal((double) (hald_level*hald_level)-1.0)*
275 r)*(cube_level-1.0);
276 index.r=floor(offset.r);
277 scale.r=offset.r-index.r;
278 next.r=index.r+1;
279 if ((size_t) index.r == (cube_level-1))
280 next.r=index.r;
281 offset.g=(PerceptibleReciprocal(((double) hald_level*hald_level)-1.0)*
282 g)*(cube_level-1.0);
283 index.g=floor(offset.g);
284 scale.g=offset.g-index.g;
285 next.g=index.g+1;
286 if ((size_t) index.g == (cube_level-1))
287 next.g=index.g;
288 offset.b=(PerceptibleReciprocal(((double) hald_level*hald_level)-1.0)*
289 b)*(cube_level-1.0);
290 index.b=floor(offset.b);
291 scale.b=offset.b-index.b;
292 next.b=index.b+1;
293 if ((size_t) index.b == (cube_level-1))
294 next.b=index.b;
295 SetPixelRed(image,ClampToQuantum(QuantumRange*(
296 cube[FlattenCube(cube_level,index.b,index.g,index.r)].r+scale.r*(
297 cube[FlattenCube(cube_level,index.b,index.g,next.r)].r-
298 cube[FlattenCube(cube_level,index.b,index.g,index.r)].r))),q);
299 SetPixelGreen(image,ClampToQuantum(QuantumRange*(
300 cube[FlattenCube(cube_level,index.b,index.g,index.r)].g+scale.g*(
301 cube[FlattenCube(cube_level,index.b,next.g,index.r)].g-
302 cube[FlattenCube(cube_level,index.b,index.g,index.r)].g))),q);
303 SetPixelBlue(image,ClampToQuantum(QuantumRange*(
304 cube[FlattenCube(cube_level,index.b,index.g,index.r)].b+scale.b*(
305 cube[FlattenCube(cube_level,next.b,index.g,index.r)].b-
306 cube[FlattenCube(cube_level,index.b,index.g,index.r)].b))),q);
307 q+=GetPixelChannels(image);
308 }
309 if (SyncAuthenticPixels(image,exception) == MagickFalse)
310 status=MagickFalse;
311 }
312 }
313 cube_info=RelinquishVirtualMemory(cube_info);
314 (void) CloseBlob(image);
315 if (status == MagickFalse)
316 return(DestroyImageList(image));
317 if (image_info->scene != 0)
318 for (i=0; i < (ssize_t) image_info->scene; i++)
319 AppendImageToList(&image,CloneImage(image,0,0,MagickTrue,exception));
320 return(GetFirstImageInList(image));
321 }
322
323 /*
324 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
325 % %
326 % %
327 % %
328 % R e g i s t e r H A L D I m a g e %
329 % %
330 % %
331 % %
332 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
333 %
334 % RegisterCUBEImage() adds attributes for the Hald color lookup table image
335 % format to the list of supported formats. The attributes include the image
336 % format tag, a method to read and/or write the format, whether the format
337 % supports the saving of more than one frame to the same file or blob, whether
338 % the format supports native in-memory I/O, and a brief description of the
339 % format.
340 %
341 % The format of the RegisterCUBEImage method is:
342 %
343 % size_t RegisterCUBEImage(void)
344 %
345 */
RegisterCUBEImage(void)346 ModuleExport size_t RegisterCUBEImage(void)
347 {
348 MagickInfo
349 *entry;
350
351 entry=AcquireMagickInfo("CUBE","CUBE","Cube LUT");
352 entry->decoder=(DecodeImageHandler *) ReadCUBEImage;
353 entry->flags^=CoderAdjoinFlag;
354 entry->format_type=ImplicitFormatType;
355 entry->flags|=CoderRawSupportFlag;
356 entry->flags|=CoderEndianSupportFlag;
357 (void) RegisterMagickInfo(entry);
358 return(MagickImageCoderSignature);
359 }
360
361 /*
362 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
363 % %
364 % %
365 % %
366 % U n r e g i s t e r H A L D I m a g e %
367 % %
368 % %
369 % %
370 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
371 %
372 % UnregisterCUBEImage() removes format registrations made by the
373 % CUBE module from the list of supported formats.
374 %
375 % The format of the UnregisterCUBEImage method is:
376 %
377 % UnregisterCUBEImage(void)
378 %
379 */
UnregisterCUBEImage(void)380 ModuleExport void UnregisterCUBEImage(void)
381 {
382 (void) UnregisterMagickInfo("CUBE");
383 }
384