1 //******************************************************************************
2 ///
3 /// @file base/image/image.h
4 ///
5 /// Declarations related to image containers.
6 ///
7 /// @copyright
8 /// @parblock
9 ///
10 /// Persistence of Vision Ray Tracer ('POV-Ray') version 3.8.
11 /// Copyright 1991-2018 Persistence of Vision Raytracer Pty. Ltd.
12 ///
13 /// POV-Ray is free software: you can redistribute it and/or modify
14 /// it under the terms of the GNU Affero General Public License as
15 /// published by the Free Software Foundation, either version 3 of the
16 /// License, or (at your option) any later version.
17 ///
18 /// POV-Ray is distributed in the hope that it will be useful,
19 /// but WITHOUT ANY WARRANTY; without even the implied warranty of
20 /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 /// GNU Affero General Public License for more details.
22 ///
23 /// You should have received a copy of the GNU Affero General Public License
24 /// along with this program.  If not, see <http://www.gnu.org/licenses/>.
25 ///
26 /// ----------------------------------------------------------------------------
27 ///
28 /// POV-Ray is based on the popular DKB raytracer version 2.12.
29 /// DKBTrace was originally written by David K. Buck.
30 /// DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
31 ///
32 /// @endparblock
33 ///
34 //******************************************************************************
35 
36 #ifndef POVRAY_BASE_IMAGE_H
37 #define POVRAY_BASE_IMAGE_H
38 
39 // Module config header file must be the first file included within POV-Ray unit header files
40 #include "base/configbase.h"
41 
42 // POV-Ray base header files
43 #include "base/fileinputoutput.h"
44 #include "base/pov_err.h"
45 #include "base/image/colourspace.h"
46 #include "base/image/encoding.h"
47 
48 namespace pov_base
49 {
50 
51 class DitherStrategy;
52 using DitherStrategySPtr = shared_ptr<DitherStrategy>;
53 
54 //##############################################################################
55 ///
56 /// @defgroup PovBaseImage Image Handling
57 /// @ingroup PovBase
58 ///
59 /// @{
60 
61 /**
62  *  Generic image data container.
63  *
64  *  @note   Except for access functions having a `premul` parameter as well as those named
65  *          `GetEncodedSomethingValue` or `SetEncodedSomethingValue`, all other access functions are unaware of
66  *          premultiplied vs. non-premultiplied alpha issues, and will access the data in whatever format
67  *          it is stored (as far as alpha handling goes).
68  *
69  *  @note   When backed by a gamma-encoded data container, unsigned int access methods are presumed
70  *          to read/write raw encoded data, while float access methods will read/write logical
71  *          linear values.
72  *
73  *  @note   Image coordinates increase from left (x=0) to right (x>0) and from top (y=0) to bottom (y>0).
74  */
75 class Image
76 {
77     public:
78         struct RGBMapEntry
79         {
80             float red;
81             float green;
82             float blue;
83 
RGBMapEntryRGBMapEntry84             RGBMapEntry() : red(0.0f), green(0.0f), blue(0.0f) { }
RGBMapEntryRGBMapEntry85             RGBMapEntry(float r, float g, float b) : red(r), green(g), blue(b) { }
86         };
87 
88         struct RGBAMapEntry
89         {
90             float red;
91             float green;
92             float blue;
93             float alpha;
94 
RGBAMapEntryRGBAMapEntry95             RGBAMapEntry() : red(0.0f), green(0.0f), blue(0.0f), alpha(0.0f) { }
RGBAMapEntryRGBAMapEntry96             RGBAMapEntry(float r, float g, float b, float a) : red(r), green(g), blue(b), alpha(a) { }
97         };
98 
99         struct RGBFTMapEntry
100         {
101             float red;
102             float green;
103             float blue;
104             float filter;
105             float transm;
106 
RGBFTMapEntryRGBFTMapEntry107             RGBFTMapEntry() : red(0.0f), green(0.0f), blue(0.0f), filter(0.0f), transm(0.0f) { }
RGBFTMapEntryRGBFTMapEntry108             RGBFTMapEntry(float r, float g, float b, float f, float t) : red(r), green(g), blue(b), filter(f), transm(t) { }
109         };
110 
111         enum ColourMapType
112         {
113             NoColourMap,
114             RGBColourMap,
115             RGBAColourMap,
116             RGBFTColourMap
117         };
118 
119         enum ImageChannelDataType
120         {
121             kImageChannelDataType_Int8,
122             kImageChannelDataType_Int16,
123             kImageChannelDataType_Gamma8,
124             kImageChannelDataType_Gamma16,
125         };
126 
127         enum ImageChannelLayout
128         {
129             kImageChannelLayout_Gray,
130             kImageChannelLayout_GrayA,
131             kImageChannelLayout_RGB,
132             kImageChannelLayout_RGBA,
133         };
134 
135         enum ImageDataType
136         {
137             /// Value used to indicate that image decoder is free to pick the most fitting type.
138             Undefined,
139             /// Palette-based image with 2 palette entries.
140             Bit_Map,
141             /// Palette-based image with up to 256 palette entries.
142             Colour_Map,
143             /// Single-channel (grayscale) image using 8-bit linear encoding.
144             Gray_Int8,
145             /// Single-channel (grayscale) image using 16-bit linear encoding.
146             Gray_Int16,
147             /// Dual-channel (grayscale and alpha) image using 8-bit linear encoding.
148             GrayA_Int8,
149             /// Dual-channel (grayscale and alpha) image using 16-bit linear encoding.
150             GrayA_Int16,
151             /// 3-channel (colour) image using 8-bit linear encoding.
152             RGB_Int8,
153             /// 3-channel (colour) image using 16-bit linear encoding.
154             RGB_Int16,
155             /// 4-channel (colour and alpha) image using 8-bit linear encoding.
156             RGBA_Int8,
157             /// 4-channel (colour and alpha) image using 16-bit linear encoding.
158             RGBA_Int16,
159             /// 5-channel (colour, filter and transmit) image using single-precision floating-point encoding.
160             RGBFT_Float,
161             /// 3-channel (colour) image using 8-bit gamma encoding.
162             RGB_Gamma8,
163             /// 3-channel (colour) image using 16-bit gamma encoding.
164             RGB_Gamma16,
165             /// 4-channel (colour and alpha) image using 8-bit gamma colour encoding and 8-bit linear alpha encoding.
166             RGBA_Gamma8,
167             /// 4-channel (colour and alpha) image using 16-bit gamma colour encoding and 16-bit linear alpha encoding.
168             RGBA_Gamma16,
169             /// Single-channel (grayscale) image using 8-bit gamma encoding.
170             Gray_Gamma8,
171             /// Single-channel (grayscale) image using 16-bit gamma encoding.
172             Gray_Gamma16,
173             /// Dual-channel (grayscale and alpha) image using 8-bit gamma greyscale encoding and 8-bit linear alpha encoding.
174             GrayA_Gamma8,
175             /// Dual-channel (grayscale and alpha) image using 16-bit gamma greyscale encoding and 16-bit linear alpha encoding.
176             GrayA_Gamma16
177         };
178 
179         /// Image file type identifier.
180         enum ImageFileType
181         {
182             GIF,
183             POT,
184             SYS,
185             IFF,
186             TGA,
187             PGM,
188             PPM,
189             PNG,
190             JPEG,
191             TIFF,
192             BMP,
193             EXR,
194             HDR
195         };
196 
197         /// The mode to use for alpha handling.
198         enum AlphaMode
199         {
200             kAlphaMode_None,            ///< Disable alpha channel. @note Not a valid setting for input files.
201             kAlphaMode_Default,         ///< Use auto-detection or file format specific default.
202             kAlphaMode_Premultiplied,   ///< Enforce premultiplied mode, aka associated alpha.
203             kAlphaMode_Straight,        ///< Enforce straight mode, aka unassociated alpha.
204         };
205 
206         struct ReadOptions
207         {
208             ImageDataType itype;
209             SimpleGammaCurvePtr defaultGamma;   // the gamma curve to use by default for converting to linear colour space
210             SimpleGammaCurvePtr workingGamma;   // the working colour space gamma
211             bool gammaOverride;                 // whether to apply defaultGamma even if the file indicates a different gamma
212             bool gammacorrect;                  // whether to do any gamma correction at all; if false, raw encoded values are used
213             bool premultipliedOverride;         // whether to override file-format default for alpha premultiplication
214             bool premultiplied;                 // whether to expect premultiplied ("associated") alpha or not ("straight alpha")
215             mutable vector<string> warnings;
216 
ReadOptionsReadOptions217             ReadOptions() : itype(Undefined), gammaOverride(false), gammacorrect(false), premultipliedOverride(false), premultiplied(false) { }
218         };
219 
220         struct WriteOptions
221         {
222             //------------------------------------------------------------------------------
223             /// @name Gamma Handling
224             /// @{
225 
226             /// Gamma to encode for.
227             /// Set this to `nullptr` to use the file format specific default.
228             /// @note
229             ///     This setting is ignored with file formats that mandate linear encoding or a
230             ///     specific encoding gamma.
231             SimpleGammaCurvePtr encodingGamma;
232 
233             /// Working colour space gamma to encode from.
234             /// Set to `nullptr` or a neutral gamma curve to indicate linear working colour space.
235             SimpleGammaCurvePtr workingGamma;
236 
237             /// @}
238             //------------------------------------------------------------------------------
239 
240             /// Dithering algorithm.
241             /// Leave this at the default to disable dithering.
242             /// @note
243             ///     This setting is ignored with file formats that are not prone to colour banding
244             ///     artifacts (such as OpenEXR) or do not benefit from dithering (such as JPEG).
245             DitherStrategySPtr ditherStrategy;
246 
247             unsigned int offset_x; ///< Currently not actively set.
248             unsigned int offset_y; ///< Currently not actively set.
249 
250             /// How to handle image transparency.
251             /// Set this to @ref kAlphaMode_None to disable creation of an alpha channel,
252             /// @ref kAlphaMode_Default to write an alpha channel using a file format specific
253             /// default mode, @ref kAlphaMode_Premultiplied to write an alpha channel using
254             /// premultiplied mode (aka associated alpha), or @ref kAlphaMode_Straight to write an
255             /// alpha channel using straight mode (aka unassociated alpha).
256             /// @note
257             ///     This setting is ignored with file formats that do not support transparency, or
258             ///     for which transparency support has not been implemented in POV-Ray.
259             AlphaMode alphaMode;
260 
261             /// Bits per colour channel.
262             /// Set this to `0` to use the file format specific default.
263             /// @note
264             ///     This setting is ignored with file formats that mandate a particular bit depth,
265             ///     for which POV-Ray only supports a particular bit depth, or for which bit depth
266             ///     is not applicable (such as JPEG).
267             /// @note
268             ///     The actual bit depth may differ if the file format or POV-Ray's implementation
269             ///     thereof does not support the requested bit depth. In that case, the next higher
270             ///     supported bit depth is used if possible, or the highest supported bit depth
271             ///     otherwise.
272             unsigned char bitsPerChannel;
273 
274             /// Whether to use compression.
275             /// Set this to a negative value to use the file format specific default setting, `0`
276             /// to disable, or any higher value to enable. Depending on the file format, such a
277             /// setting may be interpreted as a format specific compression parameter.
278             /// @note
279             ///     Whether a positive value indicates a mode, compression level or quality level
280             ///     is specific to the file format.
281             /// @note
282             ///     This setting is ignored with file formats that never use compression, always
283             ///     use compression, or for which POV-Ray's implementation leaves no choice.
284             signed short compression;
285 
286             /// Whether to write a greyscale image.
287             /// @note
288             ///     This setting is ignored with file formats that do not support a dedicated
289             ///     greyscale mode, or for which support of such a mode has not been implemented
290             ///     in POV-Ray.
291             bool grayscale : 1;
292 
293             WriteOptions();
294 
AlphaIsEnabledWriteOptions295             inline bool AlphaIsEnabled() const
296             {
297                 return (alphaMode != Image::kAlphaMode_None);
298             }
299 
AlphaIsPremultipliedWriteOptions300             inline bool AlphaIsPremultiplied(bool defaultToPremultiplied) const
301             {
302                 if (defaultToPremultiplied)
303                     return (alphaMode != Image::kAlphaMode_Straight);
304                 else
305                     return (alphaMode == Image::kAlphaMode_Premultiplied);
306             }
307 
GetTranscodingGammaCurveWriteOptions308             GammaCurvePtr GetTranscodingGammaCurve(GammaCurvePtr defaultEncodingGamma) const
309             {
310                 if (encodingGamma)
311                     return TranscodingGammaCurve::Get(workingGamma, encodingGamma);
312                 else
313                     return TranscodingGammaCurve::Get(workingGamma, defaultEncodingGamma);
314             }
315         };
316 
~Image()317         virtual ~Image() { }
318 
319         static ImageDataType GetImageDataType (ImageChannelDataType channelType, ImageChannelLayout layout);
320 
321         static Image *Create(unsigned int w, unsigned int h, ImageDataType t, unsigned int maxRAMmbHint, unsigned int pixelsPerBlockHint);
322         static Image *Create(unsigned int w, unsigned int h, ImageDataType t, bool allowFileBacking = false);
323         static Image *Create(unsigned int w, unsigned int h, ImageDataType t, const vector<RGBMapEntry>& m, bool allowFileBacking = false);
324         static Image *Create(unsigned int w, unsigned int h, ImageDataType t, const vector<RGBAMapEntry>& m, bool allowFileBacking = false);
325         static Image *Create(unsigned int w, unsigned int h, ImageDataType t, const vector<RGBFTMapEntry>& m, bool allowFileBacking = false);
326 
327         // ftype = use this image type, if "Undefined" use best match
328         static Image *Read(ImageFileType ftype, IStream *file, const ReadOptions& options = ReadOptions());
329 
330         static void Write(ImageFileType ftype, OStream *file, const Image *image, const WriteOptions& options = WriteOptions());
331 
GetWidth()332         unsigned int GetWidth() const { return width; }
GetHeight()333         unsigned int GetHeight() const { return height; }
GetImageDataType()334         ImageDataType GetImageDataType() const { return type; }
335 
336         /// Returns true if image is fully opaque.
337         virtual bool IsOpaque() const = 0;
338 
339         virtual bool IsGrayscale() const = 0;
340         virtual bool IsColour() const = 0;
341         virtual bool IsFloat() const = 0;
342         virtual bool IsInt() const = 0;
343 
344         /// Returns true if backed by a palette-based container.
345         virtual bool IsIndexed() const = 0;
346 
347         /// Returns true if backed by a gamma-encoded data container.
348         virtual bool IsGammaEncoded() const = 0;
349 
350         /// Returns true if container features a genuine alpha channel.
351         virtual bool HasAlphaChannel() const = 0;
352 
353         /// Returns true if container features genuine filter & transmit channels.
354         virtual bool HasFilterTransmit() const = 0;
355 
356         /// Returns true if container features any way of storing transparency information.
HasTransparency()357         virtual bool HasTransparency() const { return (HasAlphaChannel() || HasFilterTransmit()); }
358 
359         /// Returns the maximum value supported by int access methods or for palette indices (1, 255 or 65535).
360         virtual unsigned int GetMaxIntValue() const = 0;
361 
362         /// Specifies whether container holds color data premultiplied with alpha
SetPremultiplied(bool b)363         void SetPremultiplied(bool b) { premultiplied = b; } // TODO - mechanism not fully functional yet for image reading
364 
365         /// Returns true if container holds data premultiplied with alpha
IsPremultiplied()366         bool IsPremultiplied() const { return premultiplied; }
367 
368         /**
369          *  Requests the image container to perform deferred decoding of integer values.
370          *  In order for the request to be honored, the requested value range must match the container's native
371          *  bit depth, and the container must have a neutral encoding gamma curve set at present.
372          *  @note           If the request is honored, this will also affect subsequent unsigned int read accesses.
373          *  @param[in,out]  gamma   Gamma encoding curve of the encoded material. Set to empty by the function
374          *                          if the request is accepted.
375          *  @param[in]      max     Maximum encoded value. In order for the request to be honored, this must match
376          *                          the image container's native bit depth.
377          *  @return                 true it the request is accepted, false otherwise.
378          */
379         virtual bool TryDeferDecoding(GammaCurvePtr& gamma, unsigned int max) = 0;
380 
381         void GetRGBIndexedValue(unsigned char index, float& red, float& green, float& blue) const;
382         void GetRGBAIndexedValue(unsigned char index, float& red, float& green, float& blue, float& alpha) const;
383         void GetRGBTIndexedValue(unsigned char index, float& red, float& green, float& blue, float& transm) const;
384         void GetRGBFTIndexedValue(unsigned char index, float& red, float& green, float& blue, float& filter, float& transm) const;
385 
386         void SetRGBIndexedValue(unsigned char index, float red, float green, float blue);
387         void SetRGBAIndexedValue(unsigned char index, float red, float green, float blue, float alpha);
388         void SetRGBTIndexedValue(unsigned char index, float red, float green, float blue, float transm);
389         void SetRGBFTIndexedValue(unsigned char index, float red, float green, float blue, float filter, float transm);
390         void SetRGBFTIndexedValue(unsigned char index, const RGBFTColour& colour);
391 
392         virtual bool GetBitValue(unsigned int x, unsigned int y) const = 0;
393         virtual float GetGrayValue(unsigned int x, unsigned int y) const = 0;
394         virtual void GetGrayAValue(unsigned int x, unsigned int y, float& gray, float& alpha) const = 0;
395         virtual void GetRGBValue(unsigned int x, unsigned int y, float& red, float& green, float& blue) const = 0;
396         virtual void GetRGBAValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& alpha) const = 0;
397         virtual void GetRGBTValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& transm) const = 0;
398         virtual void GetRGBFTValue(unsigned int x, unsigned int y, float& red, float& green, float& blue, float& filter, float& transm) const = 0;
399         virtual unsigned char GetIndexedValue(unsigned int x, unsigned int y);
400 
401         virtual void SetBitValue(unsigned int x, unsigned int y, bool bit) = 0;
402         virtual void SetGrayValue(unsigned int x, unsigned int y, float gray) = 0;
403         virtual void SetGrayValue(unsigned int x, unsigned int y, unsigned int gray) = 0;
404         virtual void SetGrayAValue(unsigned int x, unsigned int y, float gray, float alpha) = 0;
405         virtual void SetGrayAValue(unsigned int x, unsigned int y, unsigned int gray, unsigned int alpha) = 0;
406         virtual void SetRGBValue(unsigned int x, unsigned int y, float red, float green, float blue) = 0;
407         virtual void SetRGBValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue) = 0;
408         virtual void SetRGBAValue(unsigned int x, unsigned int y, float red, float green, float blue, float alpha) = 0;
409         virtual void SetRGBAValue(unsigned int x, unsigned int y, unsigned int red, unsigned int green, unsigned int blue, unsigned int alpha) = 0;
410         virtual void SetRGBTValue(unsigned int x, unsigned int y, float red, float green, float blue, float transm) = 0;
411         virtual void SetRGBTValue(unsigned int x, unsigned int y, const RGBTColour& col) = 0;
412         virtual void SetRGBFTValue(unsigned int x, unsigned int y, float red, float green, float blue, float filter, float transm) = 0;
413         virtual void SetRGBFTValue(unsigned int x, unsigned int y, const RGBFTColour& col) = 0;
414         virtual void SetIndexedValue(unsigned int x, unsigned int y, unsigned char index);
415 
416         // convenience functions for image evaluation
417         void GetRGBValue(unsigned int x, unsigned int y, RGBColour& colour, bool premul = false) const;
418         void GetRGBTValue(unsigned int x, unsigned int y, RGBTColour& colour, bool premul = false) const;
419         void GetRGBFTValue(unsigned int x, unsigned int y, RGBFTColour& colour, bool premul = false) const;
420 
421         virtual void FillBitValue(bool bit) = 0;
422         virtual void FillGrayValue(float gray) = 0;
423         virtual void FillGrayValue(unsigned int gray) = 0;
424         virtual void FillGrayAValue(float gray, float alpha) = 0;
425         virtual void FillGrayAValue(unsigned int gray, unsigned int alpha) = 0;
426         virtual void FillRGBValue(float red, float green, float blue) = 0;
427         virtual void FillRGBValue(unsigned int red, unsigned int green, unsigned int blue) = 0;
428         virtual void FillRGBAValue(float red, float green, float blue, float alpha) = 0;
429         virtual void FillRGBAValue(unsigned int red, unsigned int green, unsigned int blue, unsigned int alpha) = 0;
430         virtual void FillRGBTValue(float red, float green, float blue, float transm) = 0;
431         virtual void FillRGBFTValue(float red, float green, float blue, float filter, float transm) = 0;
432 
433         unsigned int GetColourMapSize() const;
434 
435         void GetColourMap(vector<RGBMapEntry>& m) const;
436         void GetColourMap(vector<RGBAMapEntry>& m) const;
437         void GetColourMap(vector<RGBFTMapEntry>& m) const;
438 
439         void SetColourMap(const vector<RGBMapEntry>& m);
440         void SetColourMap(const vector<RGBAMapEntry>& m);
441         void SetColourMap(const vector<RGBFTMapEntry>& m);
442 /*
443         void CopyTo(unsigned int x, unsigned int y, const Image& srcimage)
444         {
445             // TODO
446         }
447         void CopyToScaled(unsigned int x, unsigned int y, unsigned int w, unsigned int h, const Image& srcimage, bool smooth = false)
448         {
449             // TODO
450         }*/
451     protected:
452         struct MapEntry
453         {
454             float red;
455             float green;
456             float blue;
457             float filter; // alpha = filter
458             float transm;
459 
MapEntryMapEntry460             MapEntry() : red(0.0f), green(0.0f), blue(0.0f), filter(0.0f), transm(0.0f) { }
MapEntryMapEntry461             MapEntry(float r, float g, float b, float f, float t) : red(r), green(g), blue(b), filter(f), transm(t) { }
MapEntryMapEntry462             MapEntry(const RGBMapEntry& e) : red(e.red), green(e.green), blue(e.blue), filter(0.0f), transm(0.0f) { }
MapEntryMapEntry463             MapEntry(const RGBAMapEntry& e) : red(e.red), green(e.green), blue(e.blue), filter(e.alpha), transm(0.0f) { }
MapEntryMapEntry464             MapEntry(const RGBFTMapEntry& e) : red(e.red), green(e.green), blue(e.blue), filter(e.filter), transm(e.transm) { }
465         };
466 
467         vector<MapEntry> colormap;
468         ColourMapType colormaptype;
469         unsigned int width;
470         unsigned int height;
471         ImageDataType type;
472         bool premultiplied;
473 
Image(unsigned int w,unsigned int h,ImageDataType t)474         Image(unsigned int w, unsigned int h, ImageDataType t) :
475             width(w), height(h), type(t), colormaptype(NoColourMap), premultiplied(false) { }
476 
Image(unsigned int w,unsigned int h,ImageDataType t,const vector<RGBMapEntry> & m)477         Image(unsigned int w, unsigned int h, ImageDataType t, const vector<RGBMapEntry>& m) :
478             width(w), height(h), type(t), colormaptype(RGBColourMap), premultiplied(false) { colormap.resize(max(m.size(), sizeof(unsigned char) * 256)); colormap.assign(m.begin(), m.end()); }
479 
Image(unsigned int w,unsigned int h,ImageDataType t,const vector<RGBAMapEntry> & m)480         Image(unsigned int w, unsigned int h, ImageDataType t, const vector<RGBAMapEntry>& m) :
481             width(w), height(h), type(t), colormaptype(RGBAColourMap), premultiplied(false) { colormap.resize(max(m.size(), sizeof(unsigned char) * 256)); colormap.assign(m.begin(), m.end()); }
482 
Image(unsigned int w,unsigned int h,ImageDataType t,const vector<RGBFTMapEntry> & m)483         Image(unsigned int w, unsigned int h, ImageDataType t, const vector<RGBFTMapEntry>& m) :
484             width(w), height(h), type(t), colormaptype(RGBFTColourMap), premultiplied(false) { colormap.resize(max(m.size(), sizeof(unsigned char) * 256)); colormap.assign(m.begin(), m.end()); }
485 
RGB2Gray(float red,float green,float blue)486         float RGB2Gray(float red, float green, float blue) const
487         {
488             return RGBColour(red, green, blue).Greyscale();
489         }
490     private:
491         /// not available
492         Image();
493         /// not available
494         Image(const Image&);
495         /// not available
496         Image& operator=(Image&);
497 };
498 
499 /// @}
500 ///
501 //##############################################################################
502 
503 }
504 
505 #endif // POVRAY_BASE_IMAGE_H
506