1 /*******************************************************************************
2  * encoding.cpp
3  *
4  * ---------------------------------------------------------------------------
5  * Persistence of Vision Ray Tracer ('POV-Ray') version 3.7.
6  * Copyright 1991-2013 Persistence of Vision Raytracer Pty. Ltd.
7  *
8  * POV-Ray is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU Affero General Public License as
10  * published by the Free Software Foundation, either version 3 of the
11  * License, or (at your option) any later version.
12  *
13  * POV-Ray is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU Affero General Public License for more details.
17  *
18  * You should have received a copy of the GNU Affero General Public License
19  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  * ---------------------------------------------------------------------------
21  * POV-Ray is based on the popular DKB raytracer version 2.12.
22  * DKBTrace was originally written by David K. Buck.
23  * DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
24  * ---------------------------------------------------------------------------
25  * $File: //depot/public/povray/3.x/source/base/image/encoding.cpp $
26  * $Revision: #1 $
27  * $Change: 6069 $
28  * $DateTime: 2013/11/06 11:59:40 $
29  * $Author: chrisc $
30  *******************************************************************************/
31 
32 // configbase.h must always be the first POV file included within base *.cpp files
33 #include "base/configbase.h"
34 #include "base/image/encoding.h"
35 #include "base/image/image.h"
36 #include "base/povmsgid.h"
37 
38 // this must be the last file included
39 #include "base/povdebug.h"
40 
41 namespace pov_base
42 {
43 
44 /*******************************************************************************/
45 
46 #define ALPHA_EPSILON 1.0e-6
47 
48 static const unsigned int MaxBayerMatrixSize = 4;
49 typedef float BayerMatrix[MaxBayerMatrixSize][MaxBayerMatrixSize];
50 
51 static const BayerMatrix BayerMatrices[MaxBayerMatrixSize+1] =
52 {
53 	// dummy for 0x0
54 	{ { 0 } },
55 	// 1x1 (of little use, but here it is)
56 	{ { 1/2.0-0.5 } },
57 	// 2x2
58 	{ { 1/4.0-0.5, 3/4.0-0.5 },
59 	  { 4/4.0-0.5, 2/4.0-0.5 } },
60 	// 3x3
61 	{ { 3/9.0-0.5, 7/9.0-0.5, 4/9.0-0.5 },
62 	  { 6/9.0-0.5, 1/9.0-0.5, 9/9.0-0.5 },
63 	  { 2/9.0-0.5, 8/9.0-0.5, 5/9.0-0.5 } },
64 	// 4x4
65 	{ {  1/16.0-0.5,  9/16.0-0.5,  3/16.0-0.5, 11/16.0-0.5 },
66 	  { 13/16.0-0.5,  5/16.0-0.5, 15/16.0-0.5,  7/16.0-0.5 },
67 	  {  4/16.0-0.5, 12/16.0-0.5,  2/16.0-0.5, 10/16.0-0.5 },
68 	  { 16/16.0-0.5,  8/16.0-0.5, 14/16.0-0.5,  6/16.0-0.5 } }
69 };
70 
71 /*******************************************************************************/
72 
73 /// Class representing "no-op" dithering rules.
74 class NoDither : public DitherHandler
75 {
76 	public:
77 		virtual void getOffset(unsigned int x, unsigned int y, OffsetInfo& offLin, OffsetInfo& offQnt);
78 };
79 
80 /// Class representing bayer dithering rules, generating a regular pattern.
81 class BayerDither : public DitherHandler
82 {
83 	public:
84 		BayerDither(unsigned int mxSize);
85 		virtual void getOffset(unsigned int x, unsigned int y, OffsetInfo& offLin, OffsetInfo& offQnt);
getOffset(unsigned int x,unsigned int y,unsigned int ms)86 		static inline float getOffset(unsigned int x, unsigned int y, unsigned int ms) { return BayerMatrices[ms][x%ms][y%ms]; }
87 	protected:
88 		OffsetInfo  lastErr;
89 		int         matrixSize;
90 };
91 
92 /// Class representing simple 1D error diffusion dithering rules, carrying over the error from one pixel to the next.
93 class DiffusionDither1D : public DitherHandler
94 {
95 	public:
96 		virtual void getOffset(unsigned int x, unsigned int y, OffsetInfo& offLin, OffsetInfo& offQnt);
97 		virtual void setError(unsigned int x, unsigned int y, const OffsetInfo& err);
98 	protected:
99 		OffsetInfo lastErr;
100 };
101 
102 /// Class representing simple 2D error diffusion dithering rules, carrying over the error from one pixel to the right, as well as the two pixels below.
103 /// @note   This implementation uses an additional 1-line pixel buffer to avoid manipulating the original image.
104 class DiffusionDither2D : public DitherHandler
105 {
106 	public:
107 		DiffusionDither2D(unsigned int width);
108 		virtual ~DiffusionDither2D();
109 		virtual void getOffset(unsigned int x, unsigned int y, OffsetInfo& offLin, OffsetInfo& offQnt);
110 		virtual void setError(unsigned int x, unsigned int y, const OffsetInfo& err);
111 	protected:
112 		unsigned int imageWidth;
113 		OffsetInfo* nextRowOffset;
114 		OffsetInfo* thisRowOffset;
115 };
116 
117 /// Class representing Floyd-Steinberg dithering rules, carrying over the error from one pixel to the right, as well as the three pixels below.
118 /// @note   This implementation uses an additional 1-line pixel buffer to avoid manipulating the original image.
119 class FloydSteinbergDither : public DitherHandler
120 {
121 	public:
122 		FloydSteinbergDither(unsigned int width);
123 		virtual ~FloydSteinbergDither();
124 		virtual void getOffset(unsigned int x, unsigned int y, OffsetInfo& offLin, OffsetInfo& offQnt);
125 		virtual void setError(unsigned int x, unsigned int y, const OffsetInfo& err);
126 	protected:
127 		unsigned int imageWidth;
128 		OffsetInfo* nextRowOffset;
129 		OffsetInfo* thisRowOffset;
130 };
131 
132 /*******************************************************************************/
133 
getOffset(unsigned int x,unsigned int y,OffsetInfo & offLin,OffsetInfo & offQnt)134 void NoDither::getOffset(unsigned int x, unsigned int y, OffsetInfo& offLin, OffsetInfo& offQnt)
135 {
136 	offLin.clear();
137 	offQnt.clear();
138 }
139 
140 /*******************************************************************************/
141 
BayerDither(unsigned int mxSize)142 BayerDither::BayerDither(unsigned int mxSize) :
143 	matrixSize(min(mxSize,MaxBayerMatrixSize))
144 {
145 	;
146 }
147 
getOffset(unsigned int x,unsigned int y,OffsetInfo & offLin,OffsetInfo & offQnt)148 void BayerDither::getOffset(unsigned int x, unsigned int y, OffsetInfo& offLin, OffsetInfo& offQnt)
149 {
150 	offLin.clear();
151 	offQnt.setAll(getOffset(x, y, matrixSize));
152 }
153 
154 /*******************************************************************************/
155 
getOffset(unsigned int x,unsigned int y,OffsetInfo & offLin,OffsetInfo & offQnt)156 void DiffusionDither1D::getOffset(unsigned int x, unsigned int y, OffsetInfo& offLin, OffsetInfo& offQnt)
157 {
158 	offLin = lastErr; lastErr.clear(); offQnt.clear();
159 }
160 
setError(unsigned int x,unsigned int y,const OffsetInfo & err)161 void DiffusionDither1D::setError(unsigned int x, unsigned int y, const OffsetInfo& err)
162 {
163 	lastErr = err;
164 }
165 
166 /*******************************************************************************/
167 
DiffusionDither2D(unsigned int width)168 DiffusionDither2D::DiffusionDither2D(unsigned int width) :
169 	imageWidth(width),
170 	thisRowOffset(new OffsetInfo[width+1]),
171 	nextRowOffset(new OffsetInfo[width+1])
172 {
173 	;
174 }
175 
~DiffusionDither2D()176 DiffusionDither2D::~DiffusionDither2D()
177 {
178 	delete[] thisRowOffset;
179 	delete[] nextRowOffset;
180 }
181 
getOffset(unsigned int x,unsigned int y,OffsetInfo & offLin,OffsetInfo & offQnt)182 void DiffusionDither2D::getOffset(unsigned int x, unsigned int y, OffsetInfo& offLin, OffsetInfo& offQnt)
183 {
184 	offLin = thisRowOffset[x];
185 	offQnt.clear();
186 }
187 
setError(unsigned int x,unsigned int y,const OffsetInfo & err)188 void DiffusionDither2D::setError(unsigned int x, unsigned int y, const OffsetInfo& err)
189 {
190 	if (x == 0)
191 	{
192 		OffsetInfo* tmp = nextRowOffset;
193 		nextRowOffset = thisRowOffset;
194 		thisRowOffset = tmp;
195 		for (unsigned int i = 0; i < imageWidth+1; i ++)
196 			nextRowOffset[i].clear();
197 	}
198 	thisRowOffset[x+1] += err * (2/4.0); // pixel to the right
199 	nextRowOffset[x]   += err * (1/4.0); // pixel below
200 	nextRowOffset[x+1] += err * (1/4.0); // pixel below right
201 }
202 
203 /*******************************************************************************/
204 
FloydSteinbergDither(unsigned int width)205 FloydSteinbergDither::FloydSteinbergDither(unsigned int width) :
206 	imageWidth(width),
207 	thisRowOffset(new OffsetInfo[width+2]),
208 	nextRowOffset(new OffsetInfo[width+2])
209 {
210 	;
211 }
212 
~FloydSteinbergDither()213 FloydSteinbergDither::~FloydSteinbergDither()
214 {
215 	delete[] thisRowOffset;
216 	delete[] nextRowOffset;
217 }
218 
getOffset(unsigned int x,unsigned int y,OffsetInfo & offLin,OffsetInfo & offQnt)219 void FloydSteinbergDither::getOffset(unsigned int x, unsigned int y, OffsetInfo& offLin, OffsetInfo& offQnt)
220 {
221 	offLin = thisRowOffset[x+1];
222 	offQnt.clear();
223 }
224 
setError(unsigned int x,unsigned int y,const OffsetInfo & err)225 void FloydSteinbergDither::setError(unsigned int x, unsigned int y, const OffsetInfo& err)
226 {
227 	if (x == 0)
228 	{
229 		OffsetInfo* tmp = nextRowOffset;
230 		nextRowOffset = thisRowOffset;
231 		thisRowOffset = tmp;
232 		for (unsigned int i = 0; i < imageWidth+2; i ++)
233 			nextRowOffset[i].clear();
234 	}
235 	thisRowOffset[x+2] += err * (7/16.0); // pixel to the right
236 	nextRowOffset[x]   += err * (3/16.0); // pixel below left
237 	nextRowOffset[x+1] += err * (5/16.0); // pixel below
238 	nextRowOffset[x+2] += err * (1/16.0); // pixel below right
239 }
240 
241 /*******************************************************************************/
242 
GetDitherHandler(int method,unsigned int imageWidth)243 DitherHandlerPtr GetDitherHandler(int method, unsigned int imageWidth)
244 {
245 	switch (method)
246 	{
247 		case kPOVList_DitherMethod_None:            return DitherHandlerPtr(new NoDither());
248 		case kPOVList_DitherMethod_Diffusion1D:     return DitherHandlerPtr(new DiffusionDither1D());
249 		case kPOVList_DitherMethod_Diffusion2D:     return DitherHandlerPtr(new DiffusionDither2D(imageWidth));
250 		case kPOVList_DitherMethod_FloydSteinberg:  return DitherHandlerPtr(new FloydSteinbergDither(imageWidth));
251 		case kPOVList_DitherMethod_Bayer2x2:        return DitherHandlerPtr(new BayerDither(2));
252 		case kPOVList_DitherMethod_Bayer3x3:        return DitherHandlerPtr(new BayerDither(3));
253 		case kPOVList_DitherMethod_Bayer4x4:        return DitherHandlerPtr(new BayerDither(4));
254 		default:                                    throw POV_EXCEPTION_STRING("Invalid dither method for output");
255 	}
256 }
257 
GetNoOpDitherHandler()258 DitherHandlerPtr GetNoOpDitherHandler()
259 {
260 	return DitherHandlerPtr(new NoDither());
261 }
262 
263 /*******************************************************************************/
264 
GetDitherOffset(unsigned int x,unsigned int y)265 float GetDitherOffset(unsigned int x, unsigned int y)
266 {
267 	return BayerDither::getOffset(x,y,4);
268 }
269 
270 /*******************************************************************************/
271 
AlphaPremultiply(float & fGray,float fAlpha)272 inline void AlphaPremultiply(float& fGray, float fAlpha)
273 {
274 	fGray *= fAlpha;
275 }
AlphaPremultiply(float & fRed,float & fGreen,float & fBlue,float fAlpha)276 inline void AlphaPremultiply(float& fRed, float& fGreen, float& fBlue, float fAlpha)
277 {
278 	fRed   *= fAlpha;
279 	fGreen *= fAlpha;
280 	fBlue  *= fAlpha;
281 }
AlphaUnPremultiply(float & fGray,float fAlpha)282 inline void AlphaUnPremultiply(float& fGray, float fAlpha)
283 {
284 	if (fAlpha == 0)
285 		// This special case has no perfectly sane solution. We'll just pretend that fAlpha is very, very small but non-zero.
286 		fAlpha = ALPHA_EPSILON;
287 	fGray /= fAlpha;
288 }
AlphaUnPremultiply(float & fRed,float & fGreen,float & fBlue,float fAlpha)289 inline void AlphaUnPremultiply(float& fRed, float& fGreen, float& fBlue, float fAlpha)
290 {
291 	if (fAlpha == 0)
292 		// This special case has no perfectly sane solution. We'll just pretend that fAlpha is very, very small but non-zero.
293 		fAlpha = ALPHA_EPSILON;
294 	fRed   /= fAlpha;
295 	fGreen /= fAlpha;
296 	fBlue  /= fAlpha;
297 }
298 
SetEncodedGrayValue(Image * img,unsigned int x,unsigned int y,const GammaCurvePtr & g,unsigned int max,unsigned int gray)299 void SetEncodedGrayValue(Image* img, unsigned int x, unsigned int y, const GammaCurvePtr& g, unsigned int max, unsigned int gray)
300 {
301 	if (!img->IsIndexed() && img->GetMaxIntValue() == max && GammaCurve::IsNeutral(g))
302 		// avoid potential re-quantization in case we have a pretty match between encoded data and container
303 		img->SetGrayValue(x, y, gray);
304 	else
305 		img->SetGrayValue(x, y, IntDecode(g,gray,max));
306 }
SetEncodedGrayAValue(Image * img,unsigned int x,unsigned int y,const GammaCurvePtr & g,unsigned int max,unsigned int gray,unsigned int alpha,bool premul)307 void SetEncodedGrayAValue(Image* img, unsigned int x, unsigned int y, const GammaCurvePtr& g, unsigned int max, unsigned int gray, unsigned int alpha, bool premul)
308 {
309 	bool doPremultiply   = (alpha != max) && !premul && (img->IsPremultiplied() || !img->HasTransparency()); // need to apply premultiplication if encoded data isn't PM'ed but container content should be
310 	bool doUnPremultiply = (alpha != max) && premul && !img->IsPremultiplied() && img->HasTransparency(); // need to undo premultiplication if other way round
311 	if (!doPremultiply && !doUnPremultiply && !img->IsIndexed() && img->GetMaxIntValue() == max && GammaCurve::IsNeutral(g))
312 		// avoid potential re-quantization in case we have a pretty match between encoded data and container
313 		img->SetGrayAValue(x, y, gray, alpha);
314 	else
315 	{
316 		float fAlpha = IntDecode(alpha,max);
317 		float fGray  = IntDecode(g,gray,max);
318 		if (doPremultiply)
319 			AlphaPremultiply(fGray, fAlpha);
320 		else if (doUnPremultiply)
321 			AlphaUnPremultiply(fGray, fAlpha);
322 		// else no need to worry about premultiplication
323 		img->SetGrayAValue(x, y, fGray, fAlpha);
324 	}
325 }
SetEncodedRGBValue(Image * img,unsigned int x,unsigned int y,const GammaCurvePtr & g,unsigned int max,unsigned int red,unsigned int green,unsigned int blue)326 void SetEncodedRGBValue(Image* img, unsigned int x, unsigned int y, const GammaCurvePtr& g, unsigned int max, unsigned int red, unsigned int green, unsigned int blue)
327 {
328 	if (!img->IsIndexed() && img->GetMaxIntValue() == max && GammaCurve::IsNeutral(g))
329 		// avoid potential re-quantization in case we have a pretty match between encoded data and container
330 		img->SetRGBValue(x, y, red, green, blue);
331 	else
332 		img->SetRGBValue(x, y, IntDecode(g,red,max), IntDecode(g,green,max), IntDecode(g,blue,max));
333 }
SetEncodedRGBAValue(Image * img,unsigned int x,unsigned int y,const GammaCurvePtr & g,unsigned int max,unsigned int red,unsigned int green,unsigned int blue,unsigned int alpha,bool premul)334 void SetEncodedRGBAValue(Image* img, unsigned int x, unsigned int y, const GammaCurvePtr& g, unsigned int max, unsigned int red, unsigned int green, unsigned int blue, unsigned int alpha, bool premul)
335 {
336 	bool doPremultiply   = (alpha != max) && !premul && (img->IsPremultiplied() || !img->HasTransparency()); // need to apply premultiplication if encoded data isn't PM'ed but container content should be
337 	bool doUnPremultiply = (alpha != max) && premul && !img->IsPremultiplied() && img->HasTransparency(); // need to undo premultiplication if other way round
338 	if (!doPremultiply && !doUnPremultiply && !img->IsIndexed() && img->GetMaxIntValue() == max && GammaCurve::IsNeutral(g))
339 		// avoid potential re-quantization in case we have a pretty match between encoded data and container
340 		img->SetRGBAValue(x, y, red, green, blue, alpha);
341 	else
342 	{
343 		float fAlpha = IntDecode(alpha,  max);
344 		float fRed   = IntDecode(g,red,  max);
345 		float fGreen = IntDecode(g,green,max);
346 		float fBlue  = IntDecode(g,blue, max);
347 		if (doPremultiply)
348 			AlphaPremultiply(fRed, fGreen, fBlue, fAlpha);
349 		else if (doUnPremultiply)
350 			AlphaUnPremultiply(fRed, fGreen, fBlue, fAlpha);
351 		// else no need to worry about premultiplication
352 		img->SetRGBAValue(x, y, fRed, fGreen, fBlue, fAlpha);
353 	}
354 }
SetEncodedGrayValue(Image * img,unsigned int x,unsigned int y,const GammaCurvePtr & g,float fGray)355 void SetEncodedGrayValue(Image* img, unsigned int x, unsigned int y, const GammaCurvePtr& g, float fGray)
356 {
357 	img->SetGrayValue(x, y, GammaCurve::Decode(g,fGray));
358 }
SetEncodedGrayAValue(Image * img,unsigned int x,unsigned int y,const GammaCurvePtr & g,float fGray,float fAlpha,bool premul)359 void SetEncodedGrayAValue(Image* img, unsigned int x, unsigned int y, const GammaCurvePtr& g, float fGray, float fAlpha, bool premul)
360 {
361 	bool doPremultiply   = !premul && (img->IsPremultiplied() || !img->HasTransparency()); // need to apply premultiplication if encoded data isn't PM'ed but container content should be
362 	bool doUnPremultiply = premul && !img->IsPremultiplied() && img->HasTransparency(); // need to undo premultiplication if other way round
363 	fGray = GammaCurve::Decode(g,fGray);
364 	if (doPremultiply)
365 		AlphaPremultiply(fGray, fAlpha);
366 	else if (doUnPremultiply)
367 		AlphaUnPremultiply(fGray, fAlpha);
368 	// else no need to worry about premultiplication
369 	img->SetGrayAValue(x, y, fGray, fAlpha);
370 }
SetEncodedRGBValue(Image * img,unsigned int x,unsigned int y,const GammaCurvePtr & g,float red,float green,float blue)371 void SetEncodedRGBValue(Image* img, unsigned int x, unsigned int y, const GammaCurvePtr& g, float red, float green, float blue)
372 {
373 	img->SetRGBValue(x, y, GammaCurve::Decode(g,red), GammaCurve::Decode(g,green), GammaCurve::Decode(g,blue));
374 }
SetEncodedRGBAValue(Image * img,unsigned int x,unsigned int y,const GammaCurvePtr & g,float fRed,float fGreen,float fBlue,float fAlpha,bool premul)375 void SetEncodedRGBAValue(Image* img, unsigned int x, unsigned int y, const GammaCurvePtr& g, float fRed, float fGreen, float fBlue, float fAlpha, bool premul)
376 {
377 	bool doPremultiply   = !premul && (img->IsPremultiplied() || !img->HasTransparency()); // need to apply premultiplication if encoded data isn't PM'ed but container content should be
378 	bool doUnPremultiply = premul && !img->IsPremultiplied() && img->HasTransparency(); // need to undo premultiplication if other way round
379 	fRed   = GammaCurve::Decode(g,fRed);
380 	fGreen = GammaCurve::Decode(g,fGreen);
381 	fBlue  = GammaCurve::Decode(g,fBlue);
382 	if (doPremultiply)
383 		AlphaPremultiply(fRed, fGreen, fBlue, fAlpha);
384 	else if (doUnPremultiply)
385 		AlphaUnPremultiply(fRed, fGreen, fBlue, fAlpha);
386 	// else no need to worry about premultiplication
387 	img->SetRGBAValue(x, y, fRed, fGreen, fBlue, fAlpha);
388 }
389 
GetEncodedGrayValue(const Image * img,unsigned int x,unsigned int y,const GammaCurvePtr & g,unsigned int max,DitherHandler & dh)390 unsigned int GetEncodedGrayValue(const Image* img, unsigned int x, unsigned int y, const GammaCurvePtr& g, unsigned int max, DitherHandler& dh)
391 {
392 	float fGray;
393 	if (!img->IsPremultiplied() && img->HasTransparency())
394 	{
395 		// data has transparency and is stored non-premultiplied; precompose against a black background
396 		float fAlpha;
397 		img->GetGrayAValue(x, y, fGray, fAlpha);
398 		AlphaPremultiply(fGray, fAlpha);
399 	}
400 	else
401 	{
402 		// no need to worry about premultiplication
403 		fGray = img->GetGrayValue(x, y);
404 	}
405 	DitherHandler::OffsetInfo linOff, encOff;
406 	dh.getOffset(x,y,linOff,encOff);
407 	unsigned int iGray = IntEncode(g,fGray+linOff.gray,max,encOff.gray,linOff.gray);
408 	dh.setError(x,y,linOff);
409 	return iGray;
410 }
GetEncodedGrayAValue(const Image * img,unsigned int x,unsigned int y,const GammaCurvePtr & g,unsigned int max,unsigned int & gray,unsigned int & alpha,DitherHandler & dh,bool premul)411 void GetEncodedGrayAValue(const Image* img, unsigned int x, unsigned int y, const GammaCurvePtr& g, unsigned int max, unsigned int& gray, unsigned int& alpha, DitherHandler& dh, bool premul)
412 {
413 	bool doPremultiply   = premul && !img->IsPremultiplied() && img->HasTransparency(); // need to apply premultiplication if encoded data should be premul'ed but container content isn't
414 	bool doUnPremultiply = !premul && img->IsPremultiplied() && img->HasTransparency(); // need to undo premultiplication if other way round
415 	float fGray, fAlpha;
416 	img->GetGrayAValue(x, y, fGray, fAlpha);
417 	if (doPremultiply)
418 	{
419 		AlphaPremultiply(fGray, fAlpha);
420 	}
421 	else if (doUnPremultiply)
422 	{
423 		// Data has been stored premultiplied, but should be encoded non-premultiplied.
424 		// Clipping will happen /before/ re-multiplying with alpha (because the latter is done in the viewer), which is equivalent to clipping
425 		// pre-multiplied components to be no greater than alpha, thereby "killing" highlights on transparent objects;
426 		// compensate for this by boosting opacity of any exceptionally bright pixels.
427 		if (fGray > fAlpha)
428 			fAlpha = min(1.0f, fGray);
429 		// Need to convert from premultiplied to non-premultiplied encoding.
430 		AlphaUnPremultiply(fGray, fAlpha);
431 	}
432 	else if (!premul)
433 	{
434 		// Data has been stored un-premultiplied and should be encoded that way.
435 		// Clipping will happen /before/ multiplying with alpha (because the latter is done in the viewer), which is equivalent to clipping
436 		// pre-multiplied components to be no greater than alpha, thereby "killing" highlights on transparent objects;
437 		// compensate for this by boosting opacity of any exceptionally bright pixels.
438 		if (fGray > 1.0)
439 		{
440 			float fFactor = fGray;
441 			if (fFactor * fAlpha > 1.0)
442 				fFactor = 1.0/fAlpha;
443 			// this keeps the product of alpha*color constant
444 			fAlpha *= fFactor;
445 			fGray  /= fFactor;
446 		}
447 		// No need for converting between premultiplied and un-premultiplied encoding.
448 	}
449 	// else no need to worry about premultiplication
450 	DitherHandler::OffsetInfo linOff, encOff;
451 	dh.getOffset(x,y,linOff,encOff);
452 	gray  = IntEncode(g, fGray + linOff.gray,  max, encOff.gray,  linOff.gray);
453 	alpha = IntEncode(fAlpha   + linOff.alpha, max, encOff.alpha, linOff.alpha);
454 	dh.setError(x,y,linOff);
455 }
GetEncodedRGBValue(const Image * img,unsigned int x,unsigned int y,const GammaCurvePtr & g,unsigned int max,unsigned int & red,unsigned int & green,unsigned int & blue,DitherHandler & dh)456 void GetEncodedRGBValue(const Image* img, unsigned int x, unsigned int y, const GammaCurvePtr& g, unsigned int max, unsigned int& red, unsigned int& green, unsigned int& blue, DitherHandler& dh)
457 {
458 	float fRed, fGreen, fBlue;
459 	if (!img->IsPremultiplied() && img->HasTransparency())
460 	{
461 		float fAlpha;
462 		// data has transparency and is stored non-premultiplied; precompose against a black background
463 		img->GetRGBAValue(x, y, fRed, fGreen, fBlue, fAlpha);
464 		AlphaPremultiply(fRed, fGreen, fBlue, fAlpha);
465 	}
466 	else
467 	{
468 		// no need to worry about premultiplication
469 		img->GetRGBValue(x, y, fRed, fGreen, fBlue);
470 	}
471 	DitherHandler::OffsetInfo linOff, encOff;
472 	dh.getOffset(x,y,linOff,encOff);
473 	red   = IntEncode(g,fRed   + linOff.red,   max, encOff.red,   linOff.red);
474 	green = IntEncode(g,fGreen + linOff.green, max, encOff.green, linOff.green);
475 	blue  = IntEncode(g,fBlue  + linOff.blue,  max, encOff.blue,  linOff.blue);
476 	dh.setError(x,y,linOff);
477 }
GetEncodedRGBAValue(const Image * img,unsigned int x,unsigned int y,const GammaCurvePtr & g,unsigned int max,unsigned int & red,unsigned int & green,unsigned int & blue,unsigned int & alpha,DitherHandler & dh,bool premul)478 void GetEncodedRGBAValue(const Image* img, unsigned int x, unsigned int y, const GammaCurvePtr& g, unsigned int max, unsigned int& red, unsigned int& green, unsigned int& blue, unsigned int& alpha, DitherHandler& dh, bool premul)
479 {
480 	bool doPremultiply   = premul && !img->IsPremultiplied() && img->HasTransparency(); // need to apply premultiplication if encoded data should be premul'ed but container content isn't
481 	bool doUnPremultiply = !premul && img->IsPremultiplied() && img->HasTransparency(); // need to undo premultiplication if other way round
482 	float fRed, fGreen, fBlue, fAlpha;
483 	img->GetRGBAValue(x, y, fRed, fGreen, fBlue, fAlpha);
484 	if (doPremultiply)
485 	{
486 		// Data has been stored premultiplied, but should be encoded non-premultiplied.
487 		// No need for special handling of color components greater than alpha.
488 		// Need to convert from premultiplied to non-premultiplied encoding.
489 		AlphaPremultiply(fRed, fGreen, fBlue, fAlpha);
490 	}
491 	else if (doUnPremultiply)
492 	{
493 		// Data has been stored premultiplied, but should be encoded non-premultiplied.
494 		// Clipping will happen /before/ re-multiplying with alpha (because the latter is done in the viewer), which is equivalent to clipping
495 		// pre-multiplied components to be no greater than alpha, thereby "killing" highlights on transparent objects;
496 		// compensate for this by boosting opacity of any exceptionally bright pixels.
497 		float fBright = RGBColour(fRed, fGreen, fBlue).greyscale();
498 		if (fBright > fAlpha)
499 			fAlpha = min(1.0f, fBright);
500 		// Need to convert from premultiplied to non-premultiplied encoding.
501 		AlphaUnPremultiply(fRed, fGreen, fBlue, fAlpha);
502 	}
503 	else if (!premul)
504 	{
505 		// Data has been stored un-premultiplied and should be encoded that way.
506 		// Clipping will happen /before/ multiplying with alpha (because the latter is done in the viewer), which is equivalent to clipping
507 		// pre-multiplied components to be no greater than alpha, thereby "killing" highlights on transparent objects;
508 		// compensate for this by boosting opacity of any exceptionally bright pixels.
509 		float fBright = RGBColour(fRed, fGreen, fBlue).greyscale();
510 		if (fBright > 1.0)
511 		{
512 			float fFactor = fBright;
513 			if (fFactor * fAlpha > 1.0)
514 				fFactor = 1.0/fAlpha;
515 			// this keeps the product of alpha*color constant
516 			fAlpha *= fFactor;
517 			fRed   /= fFactor;
518 			fGreen /= fFactor;
519 			fBlue  /= fFactor;
520 		}
521 		// No need for converting between premultiplied and un-premultiplied encoding.
522 	}
523 	// else no need to worry about premultiplication
524 	DitherHandler::OffsetInfo linOff, encOff;
525 	dh.getOffset(x,y,linOff,encOff);
526 	red   = IntEncode(g,fRed   + linOff.red,   max, encOff.red,   linOff.red);
527 	green = IntEncode(g,fGreen + linOff.green, max, encOff.green, linOff.green);
528 	blue  = IntEncode(g,fBlue  + linOff.blue,  max, encOff.blue,  linOff.blue);
529 	alpha = IntEncode(fAlpha   + linOff.alpha, max, encOff.alpha, linOff.alpha);
530 	dh.setError(x,y,linOff);
531 }
532 
GetEncodedGrayValue(const Image * img,unsigned int x,unsigned int y,const GammaCurvePtr & g)533 float GetEncodedGrayValue(const Image* img, unsigned int x, unsigned int y, const GammaCurvePtr& g)
534 {
535 	float fGray;
536 	if (!img->IsPremultiplied() && img->HasTransparency())
537 	{
538 		// data has transparency and is stored non-premultiplied; precompose against a black background
539 		float fAlpha;
540 		img->GetGrayAValue(x, y, fGray, fAlpha);
541 		AlphaPremultiply(fGray, fAlpha);
542 	}
543 	else
544 	{
545 		// no need to worry about premultiplication
546 		fGray = img->GetGrayValue(x, y);
547 	}
548 	return GammaCurve::Encode(g,fGray);
549 }
GetEncodedGrayAValue(const Image * img,unsigned int x,unsigned int y,const GammaCurvePtr & g,float & fGray,float & fAlpha,bool premul)550 void GetEncodedGrayAValue(const Image* img, unsigned int x, unsigned int y, const GammaCurvePtr& g, float& fGray, float& fAlpha, bool premul)
551 {
552 	bool doPremultiply   = premul && !img->IsPremultiplied() && img->HasTransparency(); // need to apply premultiplication if encoded data should be premul'ed but container content isn't
553 	bool doUnPremultiply = !premul && img->IsPremultiplied() && img->HasTransparency(); // need to undo premultiplication if other way round
554 	img->GetGrayAValue(x, y, fGray, fAlpha);
555 	if (doPremultiply)
556 		AlphaPremultiply(fGray, fAlpha);
557 	else if (doUnPremultiply)
558 		AlphaUnPremultiply(fGray, fAlpha);
559 	// else no need to worry about premultiplication
560 	fGray = GammaCurve::Encode(g,fGray);
561 }
GetEncodedRGBValue(const Image * img,unsigned int x,unsigned int y,const GammaCurvePtr & g,float & fRed,float & fGreen,float & fBlue)562 void GetEncodedRGBValue(const Image* img, unsigned int x, unsigned int y, const GammaCurvePtr& g, float& fRed, float& fGreen, float& fBlue)
563 {
564 	if (!img->IsPremultiplied() && img->HasTransparency())
565 	{
566 		// data has transparency and is stored non-premultiplied; precompose against a black background
567 		float fAlpha;
568 		img->GetRGBAValue(x, y, fRed, fGreen, fBlue, fAlpha);
569 		AlphaPremultiply(fRed, fGreen, fBlue, fAlpha);
570 	}
571 	else
572 	{
573 		// no need to worry about premultiplication
574 		img->GetRGBValue(x, y, fRed, fGreen, fBlue);
575 	}
576 	fRed   = GammaCurve::Encode(g,fRed);
577 	fGreen = GammaCurve::Encode(g,fGreen);
578 	fBlue  = GammaCurve::Encode(g,fBlue);
579 }
GetEncodedRGBAValue(const Image * img,unsigned int x,unsigned int y,const GammaCurvePtr & g,float & fRed,float & fGreen,float & fBlue,float & fAlpha,bool premul)580 void GetEncodedRGBAValue(const Image* img, unsigned int x, unsigned int y, const GammaCurvePtr& g, float& fRed, float& fGreen, float& fBlue, float& fAlpha, bool premul)
581 {
582 	bool doPremultiply   = premul && !img->IsPremultiplied() && img->HasTransparency(); // need to apply premultiplication if encoded data should be premul'ed but container content isn't
583 	bool doUnPremultiply = !premul && img->IsPremultiplied() && img->HasTransparency(); // need to undo premultiplication if other way round
584 	img->GetRGBAValue(x, y, fRed, fGreen, fBlue, fAlpha);
585 	if (doPremultiply)
586 		AlphaPremultiply(fRed, fGreen, fBlue, fAlpha);
587 	else if (doUnPremultiply)
588 		AlphaUnPremultiply(fRed, fGreen, fBlue, fAlpha);
589 	// else no need to worry about premultiplication
590 	fRed   = GammaCurve::Encode(g,fRed);
591 	fGreen = GammaCurve::Encode(g,fGreen);
592 	fBlue  = GammaCurve::Encode(g,fBlue);
593 }
594 
595 }
596