1 /*
2 % Copyright (C) 2009 GraphicsMagick Group
3 % Copyright (C) 2005 Eskil Steenberg.  All rights reserved.
4 %
5 % This program is covered by multiple licenses, which are described in
6 % Copyright.txt. You should have received a copy of Copyright.txt with this
7 % package; otherwise see http://www.graphicsmagick.org/www/Copyright.html.
8 %
9 */
10 
11 /*
12   Include declarations.
13 */
14 #include "magick/studio.h"
15 #include "magick/hclut.h"
16 #include "magick/pixel_cache.h"
17 #include "magick/pixel_iterator.h"
18 #include "magick/monitor.h"
19 #include "magick/utility.h"
20 
21 /*
22 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
23 %                                                                             %
24 %                                                                             %
25 %                                                                             %
26 %     H a l d C l u t I m a g e                                               %
27 %                                                                             %
28 %                                                                             %
29 %                                                                             %
30 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
31 %
32 %  The HaldClutImage() method apply a color lookup table (Hald CLUT) to the
33 %  image.  The fundamental principle of the Hald CLUT algorithm is that
34 %  application of an identity CLUT causes no change to the input image,
35 %  but an identity CLUT image which has had its colors transformed in
36 %  some way (e.g. in Adobe Photoshop) may be used to implement an identical
37 %  transform on any other image.
38 %
39 %  The minimum CLUT level is 2, and the maximum depends on available memory
40 %  (largest successfully tested is 24).  A CLUT image is required to have equal
41 %  width and height. A CLUT of level 8 is an image of dimension 512x512, a CLUT
42 %  of level 16 is an image of dimension 4096x4096.  Interpolation is used so
43 %  extremely large CLUT images are not required.
44 %
45 %  GraphicsMagick provides an 'identity' coder which may be used to generate
46 %  identity HLUTs.  For example, reading from "identity:8" creates an identity
47 %  CLUT of order 8.
48 %
49 %  The Hald CLUT algorithm has been developed by Eskil Steenberg as described
50 %  at http://www.quelsolaar.com/technology/clut.html, and was adapted for
51 %  GraphicsMagick by Clément Follet with support from Cédric Lejeune of
52 %  Workflowers.
53 %
54 %  The format of the HaldClutImage method is:
55 %
56 %      MagickPassFail HaldClutImage(Image *image,const Image *clut)
57 %
58 %  A description of each parameter follows:
59 %
60 %    o image: The image.
61 %
62 %    o clut: The color lookup table image
63 %
64 %
65 */
66 
67 typedef struct _HaldClutImageParameters_t
68 {
69   unsigned int
70     level;
71 
72   const PixelPacket
73     *ppcl;
74 
75 } HaldClutImageParameters_t;
76 
77 static MagickPassFail
HaldClutImagePixels(void * mutable_data,const void * immutable_data,Image * restrict image,PixelPacket * restrict pixels,IndexPacket * restrict indexes,const long npixels,ExceptionInfo * exception)78 HaldClutImagePixels(void *mutable_data,         /* User provided mutable data */
79                     const void *immutable_data, /* User provided immutable data */
80                     Image * restrict image,               /* Modify image */
81                     PixelPacket * restrict pixels,        /* Pixel row */
82                     IndexPacket * restrict indexes,       /* Pixel row indexes */
83                     const long npixels,         /* Number of pixels in row */
84                     ExceptionInfo *exception)   /* Exception report */
85 {
86 
87   const HaldClutImageParameters_t *
88      param = (const HaldClutImageParameters_t *) immutable_data;
89 
90   unsigned int
91     level = param->level;
92 
93   const PixelPacket
94     * restrict clut = param->ppcl;
95 
96   unsigned int
97     color;
98 
99   unsigned int
100     blueaxis,
101     greenaxis,
102     redaxis;
103 
104   register long
105     i,
106     k;
107 
108   double
109     sums[9],
110     r,
111     g,
112     b,
113     value;
114 
115   ARG_NOT_USED(mutable_data);
116   ARG_NOT_USED(image);
117   ARG_NOT_USED(indexes);
118   ARG_NOT_USED(exception);
119 
120   level *= level;
121 
122   for(k = 0; k < npixels ; k++)
123     {
124       /*
125         Calculate the position of each 3D axis pixel level.
126       */
127       redaxis = (unsigned int) (((double) pixels[k].red/MaxRGBDouble) * (level-1));
128       if (redaxis > level - 2)
129         redaxis = level - 2;
130       greenaxis = (unsigned int) (((double) pixels[k].green/MaxRGBDouble) * (level-1));
131       if(greenaxis > level - 2)
132         greenaxis = level - 2;
133       blueaxis = (unsigned int) (((double) pixels[k].blue/MaxRGBDouble) * (level-1));
134       if(blueaxis > level - 2)
135         blueaxis = level - 2;
136 
137       /*
138         Convert between the value and the equivalent value position.
139       */
140       r = ((double) pixels[k].red/MaxRGBDouble) * (level - 1) - redaxis;
141       g = ((double) pixels[k].green/MaxRGBDouble) * (level - 1) - greenaxis;
142       b = ((double) pixels[k].blue/MaxRGBDouble) * (level - 1) - blueaxis;
143 
144       color = redaxis + greenaxis * level + blueaxis * level * level;
145 
146       i = color;
147       sums[0] = ((double) clut[i].red) * (1 - r);
148       sums[1] = ((double) clut[i].green) * (1 - r);
149       sums[2] = ((double) clut[i].blue) * (1 - r);
150       i++;
151       sums[0] += ((double) clut[i].red) * r;
152       sums[1] += ((double) clut[i].green) * r;
153       sums[2] += ((double) clut[i].blue) * r;
154 
155       i = (color + level);
156       sums[3] = ((double) clut[i].red) * (1 - r);
157       sums[4] = ((double) clut[i].green) * (1 - r);
158       sums[5] = ((double) clut[i].blue) * (1 - r);
159       i++;
160       sums[3] += ((double) clut[i].red) * r;
161       sums[4] += ((double) clut[i].green) * r;
162       sums[5] += ((double) clut[i].blue) * r;
163 
164       sums[6] = sums[0] * (1 - g) + sums[3] * g;
165       sums[7] = sums[1] * (1 - g) + sums[4] * g;
166       sums[8] = sums[2] * (1 - g) + sums[5] * g;
167 
168       i = (color + level * level);
169       sums[0] = ((double) clut[i].red) * (1 - r);
170       sums[1] = ((double) clut[i].green) * (1 - r);
171       sums[2] = ((double) clut[i].blue) * (1 - r);
172       i++;
173       sums[0] += ((double) clut[i].red) * r;
174       sums[1] += ((double) clut[i].green) * r;
175       sums[2] += ((double) clut[i].blue) * r;
176 
177       i = (color + level * level + level);
178       sums[3] = ((double) clut[i].red) * (1 - r);
179       sums[4] = ((double) clut[i].green) * (1 - r);
180       sums[5] = ((double) clut[i].blue) * (1 - r);
181       i++;
182       sums[3] += ((double) clut[i].red) * r;
183       sums[4] += ((double) clut[i].green) * r;
184       sums[5] += ((double) clut[i].blue) * r;
185 
186       sums[0] = sums[0] * (1 - g) + sums[3] * g;
187       sums[1] = sums[1] * (1 - g) + sums[4] * g;
188       sums[2] = sums[2] * (1 - g) + sums[5] * g;
189 
190       value=(sums[6] * (1 - b) + sums[0] * b);
191       pixels[k].red = RoundDoubleToQuantum(value);
192 
193       value=(sums[7] * (1 - b) + sums[1] * b);
194       pixels[k].green = RoundDoubleToQuantum(value);
195 
196       value=(sums[8] * (1 - b) + sums[2] * b);
197       pixels[k].blue = RoundDoubleToQuantum(value);
198     }
199 
200   return MagickPass;
201 }
202 
203 
204 MagickExport MagickPassFail
HaldClutImage(Image * image,const Image * clut)205 HaldClutImage(Image *image, const Image *clut)
206 {
207   unsigned int
208     level;
209 
210   char
211     progress_message[MaxTextExtent];
212 
213   HaldClutImageParameters_t
214     param;
215 
216   MagickPassFail
217     status=MagickPass;
218 
219   assert(image != (Image *) NULL);
220   assert(image->signature == MagickSignature);
221 
222   /*
223     Hald CLUT images are square.
224   */
225   if(clut->rows != clut->columns)
226     {
227       ThrowBinaryException(OptionError,HaldClutImageDimensionsInvalid,
228                            clut->filename);
229     }
230 
231   /*
232     Calculate the level of the Hald CLUT
233   */
234   for(level = 1; level * level * level < clut->rows; level++);
235   if((level * level * level > clut->rows) || (level < 2))
236     {
237       ThrowBinaryException(OptionError,HaldClutImageDimensionsInvalid,
238                            clut->filename);
239     }
240 
241   param.level = level;
242 
243   /*
244     We acquire all of the pixels at once, which is the limiting factor
245     on maximum Hald CLUT size.
246   */
247   param.ppcl=AcquireImagePixels(clut,0,0,clut->columns,clut->rows,&image->exception);
248   if (param.ppcl == (const PixelPacket *) NULL)
249     return MagickFail;
250 
251   FormatString(progress_message,
252                "[%%s] Applying Hald CLUT level %u (%lux%lu) ...",
253                param.level,clut->columns,clut->rows);
254 
255   if (!IsRGBCompatibleColorspace(image->colorspace))
256     TransformColorspace(image,RGBColorspace);
257   if (image->storage_class == PseudoClass)
258     {
259       (void) HaldClutImagePixels(NULL,&param,image,image->colormap,
260                                  (IndexPacket *) NULL,image->colors,
261                                  &image->exception);
262       status=SyncImage(image);
263     }
264   else
265     {
266       status=PixelIterateMonoModify(HaldClutImagePixels,NULL,progress_message,
267                                     NULL,&param,0,0,image->columns,image->rows,
268                                     image,&image->exception);
269     }
270 
271   return(status);
272 }
273