/* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % CCCC U U BBBB EEEEE % % C U U B B E % % C U U BBBB EEE % % C U U B B E % % CCCC UUU BBBB EEEEE % % % % % % Cube LUT Image Format % % % % Software Design % % Cristy % % July 2018 % % % % % % Copyright 1999-2020 ImageMagick Studio LLC, a non-profit organization % % dedicated to making software imaging solutions freely available. % % % % You may not use this file except in compliance with the License. You may % % obtain a copy of the License at % % % % https://imagemagick.org/script/license.php % % % % Unless required by applicable law or agreed to in writing, software % % distributed under the License is distributed on an "AS IS" BASIS, % % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. % % See the License for the specific language governing permissions and % % limitations under the License. % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % See Cube LUT specification 1.0 @ % https://wwwimages2.adobe.com/content/dam/acom/en/products/speedgrade/cc/pdfs/cube-lut-specification-1.0.pdf % */ /* Include declarations. */ #include "MagickCore/studio.h" #include "MagickCore/blob.h" #include "MagickCore/blob-private.h" #include "MagickCore/cache.h" #include "MagickCore/colorspace.h" #include "MagickCore/exception.h" #include "MagickCore/exception-private.h" #include "MagickCore/image.h" #include "MagickCore/image-private.h" #include "MagickCore/list.h" #include "MagickCore/magick.h" #include "MagickCore/memory_.h" #include "MagickCore/module.h" #include "MagickCore/monitor.h" #include "MagickCore/monitor-private.h" #include "MagickCore/pixel-accessor.h" #include "MagickCore/property.h" #include "MagickCore/quantum-private.h" #include "MagickCore/resource_.h" #include "MagickCore/static.h" #include "MagickCore/string_.h" #include "MagickCore/string-private.h" #include "MagickCore/thread-private.h" #include "MagickCore/token.h" /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % R e a d C U B E I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % ReadCUBEImage() creates a Cube color lookup table image and returns it. It % allocates the memory necessary for the new Image structure and returns a % pointer to the new image. % % The format of the ReadCUBEImage method is: % % Image *ReadCUBEImage(const ImageInfo *image_info, % ExceptionInfo *exception) % % A description of each parameter follows: % % o image_info: the image info. % % o exception: return any errors or warnings in this structure. % */ static Image *ReadCUBEImage(const ImageInfo *image_info, ExceptionInfo *exception) { #define FlattenCube(level,b,g,r) \ ((ssize_t) ((b)*(level)*(level)+(g)*(level)+(r))) typedef struct _CubePixel { float r, g, b; } CubePixel; char *buffer, token[MagickPathExtent], value[MagickPathExtent]; CubePixel *cube; Image *image; MagickBooleanType status; MemoryInfo *cube_info; register char *p; size_t cube_level, hald_level; ssize_t b, i, n; /* Read CUBE color lookup table. */ assert(image_info != (const ImageInfo *) NULL); assert(image_info->signature == MagickCoreSignature); if (image_info->debug != MagickFalse) (void) LogMagickEvent(TraceEvent,GetMagickModule(),"%s", image_info->filename); assert(exception != (ExceptionInfo *) NULL); assert(exception->signature == MagickCoreSignature); image=AcquireImage(image_info,exception); status=OpenBlob(image_info,image,ReadBinaryBlobMode,exception); if (status == MagickFalse) { image=DestroyImageList(image); return((Image *) NULL); } cube_level=0; cube_info=(MemoryInfo *) NULL; cube=(CubePixel *) NULL; n=0; buffer=AcquireString(""); *buffer='\0'; p=buffer; while (ReadBlobString(image,p) != (char *) NULL) { const char *q; q=p; (void) GetNextToken(q,&q,MagickPathExtent,token); if ((*token == '#') || (*token == '\0')) continue; if (((LocaleCompare(token,"LUT_1D_SIZE") == 0) || (LocaleCompare(token,"LUT_3D_SIZE") == 0)) && (cube_info == (MemoryInfo *) NULL)) { (void) GetNextToken(q,&q,MagickPathExtent,value); cube_level=(size_t) StringToLong(value); if (LocaleCompare(token,"LUT_1D_SIZE") == 0) cube_level=(size_t) ceil(pow((double) cube_level,1.0/3.0)); if ((cube_level < 2) || (cube_level > 256)) { buffer=DestroyString(buffer); ThrowReaderException(CorruptImageError,"ImproperImageHeader"); } cube_info=AcquireVirtualMemory(cube_level*cube_level,cube_level* sizeof(*cube)); if (cube_info == (MemoryInfo *) NULL) { buffer=DestroyString(buffer); ThrowReaderException(ResourceLimitError,"MemoryAllocationFailed"); } cube=(CubePixel *) GetVirtualMemoryBlob(cube_info); (void) memset(cube,0,cube_level*cube_level*cube_level*sizeof(*cube)); } else if (LocaleCompare(token,"TITLE ") == 0) { (void) GetNextToken(q,&q,MagickPathExtent,value); (void) SetImageProperty(image,"title",value,exception); } else if (cube_level != 0) { char *q; if (n >= (ssize_t) (cube_level*cube_level*cube_level)) break; q=buffer; cube[n].r=StringToDouble(q,&q); cube[n].g=StringToDouble(q,&q); cube[n].b=StringToDouble(q,&q); n++; } else if (('+' < *buffer) && (*buffer < ':')) break; } buffer=DestroyString(buffer); if (cube_level == 0) { if (cube_info != (MemoryInfo *) NULL) cube_info=RelinquishVirtualMemory(cube_info); ThrowReaderException(CorruptImageError,"ImproperImageHeader"); } /* Convert CUBE image to HALD. */ status=MagickTrue; hald_level=image_info->scene; if ((hald_level < 2) || (hald_level > 256)) hald_level=8; image->columns=(size_t) (hald_level*hald_level*hald_level); image->rows=(size_t) (hald_level*hald_level*hald_level); status=SetImageExtent(image,image->columns,image->rows,exception); if (status == MagickFalse) { cube_info=RelinquishVirtualMemory(cube_info); return(DestroyImageList(image)); } for (b=0; b < (ssize_t) (hald_level*hald_level); b++) { register ssize_t g; if (status == MagickFalse) continue; for (g=0; g < (ssize_t) (hald_level*hald_level); g++) { register Quantum *magick_restrict q; register ssize_t r; if (status == MagickFalse) continue; q=QueueAuthenticPixels(image,(g % hald_level)*(hald_level*hald_level), (b*hald_level)+((g/hald_level) % (hald_level*hald_level)),hald_level* hald_level,1,exception); if (q == (Quantum *) NULL) { status=MagickFalse; continue; } for (r=0; r < (ssize_t) (hald_level*hald_level); r++) { CubePixel index, next, offset, scale; offset.r=(PerceptibleReciprocal((double) (hald_level*hald_level)-1.0)* r)*(cube_level-1.0); index.r=floor(offset.r); scale.r=offset.r-index.r; next.r=index.r+1; if ((size_t) index.r == (cube_level-1)) next.r=index.r; offset.g=(PerceptibleReciprocal(((double) hald_level*hald_level)-1.0)* g)*(cube_level-1.0); index.g=floor(offset.g); scale.g=offset.g-index.g; next.g=index.g+1; if ((size_t) index.g == (cube_level-1)) next.g=index.g; offset.b=(PerceptibleReciprocal(((double) hald_level*hald_level)-1.0)* b)*(cube_level-1.0); index.b=floor(offset.b); scale.b=offset.b-index.b; next.b=index.b+1; if ((size_t) index.b == (cube_level-1)) next.b=index.b; SetPixelRed(image,ClampToQuantum(QuantumRange*( cube[FlattenCube(cube_level,index.b,index.g,index.r)].r+scale.r*( cube[FlattenCube(cube_level,index.b,index.g,next.r)].r- cube[FlattenCube(cube_level,index.b,index.g,index.r)].r))),q); SetPixelGreen(image,ClampToQuantum(QuantumRange*( cube[FlattenCube(cube_level,index.b,index.g,index.r)].g+scale.g*( cube[FlattenCube(cube_level,index.b,next.g,index.r)].g- cube[FlattenCube(cube_level,index.b,index.g,index.r)].g))),q); SetPixelBlue(image,ClampToQuantum(QuantumRange*( cube[FlattenCube(cube_level,index.b,index.g,index.r)].b+scale.b*( cube[FlattenCube(cube_level,next.b,index.g,index.r)].b- cube[FlattenCube(cube_level,index.b,index.g,index.r)].b))),q); q+=GetPixelChannels(image); } if (SyncAuthenticPixels(image,exception) == MagickFalse) status=MagickFalse; } } cube_info=RelinquishVirtualMemory(cube_info); (void) CloseBlob(image); if (status == MagickFalse) return(DestroyImageList(image)); if (image_info->scene != 0) for (i=0; i < (ssize_t) image_info->scene; i++) AppendImageToList(&image,CloneImage(image,0,0,MagickTrue,exception)); return(GetFirstImageInList(image)); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % R e g i s t e r H A L D I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % RegisterCUBEImage() adds attributes for the Hald color lookup table image % format to the list of supported formats. The attributes include the image % format tag, a method to read and/or write the format, whether the format % supports the saving of more than one frame to the same file or blob, whether % the format supports native in-memory I/O, and a brief description of the % format. % % The format of the RegisterCUBEImage method is: % % size_t RegisterCUBEImage(void) % */ ModuleExport size_t RegisterCUBEImage(void) { MagickInfo *entry; entry=AcquireMagickInfo("CUBE","CUBE","Cube LUT"); entry->decoder=(DecodeImageHandler *) ReadCUBEImage; entry->flags^=CoderAdjoinFlag; entry->format_type=ImplicitFormatType; entry->flags|=CoderRawSupportFlag; entry->flags|=CoderEndianSupportFlag; (void) RegisterMagickInfo(entry); return(MagickImageCoderSignature); } /* %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % % % % % % U n r e g i s t e r H A L D I m a g e % % % % % % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % UnregisterCUBEImage() removes format registrations made by the % CUBE module from the list of supported formats. % % The format of the UnregisterCUBEImage method is: % % UnregisterCUBEImage(void) % */ ModuleExport void UnregisterCUBEImage(void) { (void) UnregisterMagickInfo("CUBE"); }