You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2974 lines
89 KiB
2974 lines
89 KiB
/**************************************************************************\
|
|
*
|
|
* Copyright (c) 1998 Microsoft Corporation
|
|
*
|
|
* Module Name:
|
|
*
|
|
* GradientFill.cpp
|
|
*
|
|
* Abstract:
|
|
*
|
|
* gradient fill routines.
|
|
*
|
|
* Revision History:
|
|
*
|
|
* 01/21/1999 ikkof
|
|
* Created it.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
#include "precomp.hpp"
|
|
|
|
#define CLAMP_COLOR_CHANNEL(a, b) \
|
|
if(a < 0) \
|
|
{ \
|
|
a = 0; \
|
|
} \
|
|
if(a > b) \
|
|
{ \
|
|
a = b; \
|
|
}
|
|
|
|
// 10bit inverse gamma 2.2 look up table.
|
|
|
|
static const BYTE TenBitInvGamma2_2 [] = {
|
|
0, 11, 15, 18, 21, 23, 25, 26,
|
|
28, 30, 31, 32, 34, 35, 36, 37,
|
|
39, 40, 41, 42, 43, 44, 45, 45,
|
|
46, 47, 48, 49, 50, 50, 51, 52,
|
|
53, 54, 54, 55, 56, 56, 57, 58,
|
|
58, 59, 60, 60, 61, 62, 62, 63,
|
|
63, 64, 65, 65, 66, 66, 67, 68,
|
|
68, 69, 69, 70, 70, 71, 71, 72,
|
|
72, 73, 73, 74, 74, 75, 75, 76,
|
|
76, 77, 77, 78, 78, 79, 79, 80,
|
|
80, 81, 81, 81, 82, 82, 83, 83,
|
|
84, 84, 84, 85, 85, 86, 86, 87,
|
|
87, 87, 88, 88, 89, 89, 89, 90,
|
|
90, 91, 91, 91, 92, 92, 93, 93,
|
|
93, 94, 94, 94, 95, 95, 96, 96,
|
|
96, 97, 97, 97, 98, 98, 98, 99,
|
|
99, 99, 100, 100, 101, 101, 101, 102,
|
|
102, 102, 103, 103, 103, 104, 104, 104,
|
|
105, 105, 105, 106, 106, 106, 107, 107,
|
|
107, 108, 108, 108, 108, 109, 109, 109,
|
|
110, 110, 110, 111, 111, 111, 112, 112,
|
|
112, 112, 113, 113, 113, 114, 114, 114,
|
|
115, 115, 115, 115, 116, 116, 116, 117,
|
|
117, 117, 117, 118, 118, 118, 119, 119,
|
|
119, 119, 120, 120, 120, 121, 121, 121,
|
|
121, 122, 122, 122, 123, 123, 123, 123,
|
|
124, 124, 124, 124, 125, 125, 125, 125,
|
|
126, 126, 126, 127, 127, 127, 127, 128,
|
|
128, 128, 128, 129, 129, 129, 129, 130,
|
|
130, 130, 130, 131, 131, 131, 131, 132,
|
|
132, 132, 132, 133, 133, 133, 133, 134,
|
|
134, 134, 134, 135, 135, 135, 135, 136,
|
|
136, 136, 136, 137, 137, 137, 137, 138,
|
|
138, 138, 138, 138, 139, 139, 139, 139,
|
|
140, 140, 140, 140, 141, 141, 141, 141,
|
|
142, 142, 142, 142, 142, 143, 143, 143,
|
|
143, 144, 144, 144, 144, 144, 145, 145,
|
|
145, 145, 146, 146, 146, 146, 146, 147,
|
|
147, 147, 147, 148, 148, 148, 148, 148,
|
|
149, 149, 149, 149, 149, 150, 150, 150,
|
|
150, 151, 151, 151, 151, 151, 152, 152,
|
|
152, 152, 152, 153, 153, 153, 153, 154,
|
|
154, 154, 154, 154, 155, 155, 155, 155,
|
|
155, 156, 156, 156, 156, 156, 157, 157,
|
|
157, 157, 157, 158, 158, 158, 158, 158,
|
|
159, 159, 159, 159, 159, 160, 160, 160,
|
|
160, 160, 161, 161, 161, 161, 161, 162,
|
|
162, 162, 162, 162, 163, 163, 163, 163,
|
|
163, 164, 164, 164, 164, 164, 165, 165,
|
|
165, 165, 165, 165, 166, 166, 166, 166,
|
|
166, 167, 167, 167, 167, 167, 168, 168,
|
|
168, 168, 168, 168, 169, 169, 169, 169,
|
|
169, 170, 170, 170, 170, 170, 171, 171,
|
|
171, 171, 171, 171, 172, 172, 172, 172,
|
|
172, 173, 173, 173, 173, 173, 173, 174,
|
|
174, 174, 174, 174, 174, 175, 175, 175,
|
|
175, 175, 176, 176, 176, 176, 176, 176,
|
|
177, 177, 177, 177, 177, 177, 178, 178,
|
|
178, 178, 178, 179, 179, 179, 179, 179,
|
|
179, 180, 180, 180, 180, 180, 180, 181,
|
|
181, 181, 181, 181, 181, 182, 182, 182,
|
|
182, 182, 182, 183, 183, 183, 183, 183,
|
|
183, 184, 184, 184, 184, 184, 185, 185,
|
|
185, 185, 185, 185, 186, 186, 186, 186,
|
|
186, 186, 186, 187, 187, 187, 187, 187,
|
|
187, 188, 188, 188, 188, 188, 188, 189,
|
|
189, 189, 189, 189, 189, 190, 190, 190,
|
|
190, 190, 190, 191, 191, 191, 191, 191,
|
|
191, 192, 192, 192, 192, 192, 192, 192,
|
|
193, 193, 193, 193, 193, 193, 194, 194,
|
|
194, 194, 194, 194, 195, 195, 195, 195,
|
|
195, 195, 195, 196, 196, 196, 196, 196,
|
|
196, 197, 197, 197, 197, 197, 197, 197,
|
|
198, 198, 198, 198, 198, 198, 199, 199,
|
|
199, 199, 199, 199, 199, 200, 200, 200,
|
|
200, 200, 200, 201, 201, 201, 201, 201,
|
|
201, 201, 202, 202, 202, 202, 202, 202,
|
|
202, 203, 203, 203, 203, 203, 203, 204,
|
|
204, 204, 204, 204, 204, 204, 205, 205,
|
|
205, 205, 205, 205, 205, 206, 206, 206,
|
|
206, 206, 206, 206, 207, 207, 207, 207,
|
|
207, 207, 207, 208, 208, 208, 208, 208,
|
|
208, 209, 209, 209, 209, 209, 209, 209,
|
|
210, 210, 210, 210, 210, 210, 210, 211,
|
|
211, 211, 211, 211, 211, 211, 212, 212,
|
|
212, 212, 212, 212, 212, 213, 213, 213,
|
|
213, 213, 213, 213, 213, 214, 214, 214,
|
|
214, 214, 214, 214, 215, 215, 215, 215,
|
|
215, 215, 215, 216, 216, 216, 216, 216,
|
|
216, 216, 217, 217, 217, 217, 217, 217,
|
|
217, 218, 218, 218, 218, 218, 218, 218,
|
|
218, 219, 219, 219, 219, 219, 219, 219,
|
|
220, 220, 220, 220, 220, 220, 220, 221,
|
|
221, 221, 221, 221, 221, 221, 221, 222,
|
|
222, 222, 222, 222, 222, 222, 223, 223,
|
|
223, 223, 223, 223, 223, 223, 224, 224,
|
|
224, 224, 224, 224, 224, 225, 225, 225,
|
|
225, 225, 225, 225, 225, 226, 226, 226,
|
|
226, 226, 226, 226, 226, 227, 227, 227,
|
|
227, 227, 227, 227, 228, 228, 228, 228,
|
|
228, 228, 228, 228, 229, 229, 229, 229,
|
|
229, 229, 229, 229, 230, 230, 230, 230,
|
|
230, 230, 230, 230, 231, 231, 231, 231,
|
|
231, 231, 231, 232, 232, 232, 232, 232,
|
|
232, 232, 232, 233, 233, 233, 233, 233,
|
|
233, 233, 233, 234, 234, 234, 234, 234,
|
|
234, 234, 234, 235, 235, 235, 235, 235,
|
|
235, 235, 235, 236, 236, 236, 236, 236,
|
|
236, 236, 236, 237, 237, 237, 237, 237,
|
|
237, 237, 237, 238, 238, 238, 238, 238,
|
|
238, 238, 238, 238, 239, 239, 239, 239,
|
|
239, 239, 239, 239, 240, 240, 240, 240,
|
|
240, 240, 240, 240, 241, 241, 241, 241,
|
|
241, 241, 241, 241, 242, 242, 242, 242,
|
|
242, 242, 242, 242, 243, 243, 243, 243,
|
|
243, 243, 243, 243, 243, 244, 244, 244,
|
|
244, 244, 244, 244, 244, 245, 245, 245,
|
|
245, 245, 245, 245, 245, 245, 246, 246,
|
|
246, 246, 246, 246, 246, 246, 247, 247,
|
|
247, 247, 247, 247, 247, 247, 248, 248,
|
|
248, 248, 248, 248, 248, 248, 248, 249,
|
|
249, 249, 249, 249, 249, 249, 249, 249,
|
|
250, 250, 250, 250, 250, 250, 250, 250,
|
|
251, 251, 251, 251, 251, 251, 251, 251,
|
|
251, 252, 252, 252, 252, 252, 252, 252,
|
|
252, 252, 253, 253, 253, 253, 253, 253,
|
|
253, 253, 254, 254, 254, 254, 254, 254,
|
|
254, 254, 254, 255, 255, 255, 255, 255
|
|
};
|
|
|
|
// 8bit to float gamma 2.2 LUT.
|
|
|
|
static const REAL Gamma2_2LUT[] = {
|
|
0.000000000f, 0.001294648f, 0.005948641f, 0.014515050f,
|
|
0.027332777f, 0.044656614f, 0.066693657f, 0.093619749f,
|
|
0.125588466f, 0.162736625f, 0.205187917f, 0.253055448f,
|
|
0.306443578f, 0.365449320f, 0.430163406f, 0.500671134f,
|
|
0.577053056f, 0.659385527f, 0.747741173f, 0.842189273f,
|
|
0.942796093f, 1.049625159f, 1.162737505f, 1.282191881f,
|
|
1.408044937f, 1.540351382f, 1.679164133f, 1.824534436f,
|
|
1.976511986f, 2.135145025f, 2.300480434f, 2.472563819f,
|
|
2.651439585f, 2.837151004f, 3.029740281f, 3.229248608f,
|
|
3.435716220f, 3.649182441f, 3.869685731f, 4.097263727f,
|
|
4.331953283f, 4.573790502f, 4.822810773f, 5.079048802f,
|
|
5.342538638f, 5.613313704f, 5.891406820f, 6.176850227f,
|
|
6.469675611f, 6.769914121f, 7.077596394f, 7.392752570f,
|
|
7.715412307f, 8.045604807f, 8.383358822f, 8.728702674f,
|
|
9.081664270f, 9.442271111f, 9.810550312f, 10.18652861f,
|
|
10.57023236f, 10.96168759f, 11.36091997f, 11.76795482f,
|
|
12.18281716f, 12.60553168f, 13.03612276f, 13.47461451f,
|
|
13.92103071f, 14.37539488f, 14.83773026f, 15.30805982f,
|
|
15.78640628f, 16.27279209f, 16.76723947f, 17.26977037f,
|
|
17.78040653f, 18.29916946f, 18.82608041f, 19.36116046f,
|
|
19.90443044f, 20.45591098f, 21.01562250f, 21.58358523f,
|
|
22.15981921f, 22.74434425f, 23.33718001f, 23.93834596f,
|
|
24.54786138f, 25.16574537f, 25.79201687f, 26.42669465f,
|
|
27.06979729f, 27.72134324f, 28.38135078f, 29.04983802f,
|
|
29.72682293f, 30.41232332f, 31.10635686f, 31.80894107f,
|
|
32.52009334f, 33.23983090f, 33.96817086f, 34.70513018f,
|
|
35.45072570f, 36.20497412f, 36.96789203f, 37.73949586f,
|
|
38.51980195f, 39.30882651f, 40.10658561f, 40.91309523f,
|
|
41.72837123f, 42.55242933f, 43.38528517f, 44.22695426f,
|
|
45.07745202f, 45.93679373f, 46.80499461f, 47.68206973f,
|
|
48.56803410f, 49.46290260f, 50.36669002f, 51.27941105f,
|
|
52.20108030f, 53.13171227f, 54.07132136f, 55.01992190f,
|
|
55.97752811f, 56.94415413f, 57.91981400f, 58.90452170f,
|
|
59.89829110f, 60.90113599f, 61.91307008f, 62.93410700f,
|
|
63.96426029f, 65.00354342f, 66.05196978f, 67.10955268f,
|
|
68.17630535f, 69.25224094f, 70.33737253f, 71.43171314f,
|
|
72.53527570f, 73.64807306f, 74.77011803f, 75.90142331f,
|
|
77.04200157f, 78.19186538f, 79.35102726f, 80.51949965f,
|
|
81.69729494f, 82.88442544f, 84.08090341f, 85.28674102f,
|
|
86.50195041f, 87.72654363f, 88.96053269f, 90.20392952f,
|
|
91.45674601f, 92.71899397f, 93.99068516f, 95.27183128f,
|
|
96.56244399f, 97.86253485f, 99.17211542f, 100.4911972f,
|
|
101.8197915f, 103.1579098f, 104.5055633f, 105.8627634f,
|
|
107.2295212f, 108.6058479f, 109.9917545f, 111.3872522f,
|
|
112.7923519f, 114.2070647f, 115.6314012f, 117.0653726f,
|
|
118.5089894f, 119.9622626f, 121.4252027f, 122.8978204f,
|
|
124.3801265f, 125.8721313f, 127.3738455f, 128.8852796f,
|
|
130.4064438f, 131.9373487f, 133.4780046f, 135.0284217f,
|
|
136.5886104f, 138.1585808f, 139.7383431f, 141.3279074f,
|
|
142.9272838f, 144.5364824f, 146.1555131f, 147.7843860f,
|
|
149.4231109f, 151.0716977f, 152.7301563f, 154.3984965f,
|
|
156.0767280f, 157.7648605f, 159.4629038f, 161.1708675f,
|
|
162.8887612f, 164.6165945f, 166.3543769f, 168.1021179f,
|
|
169.8598270f, 171.6275137f, 173.4051873f, 175.1928571f,
|
|
176.9905325f, 178.7982229f, 180.6159374f, 182.4436852f,
|
|
184.2814757f, 186.1293178f, 187.9872208f, 189.8551937f,
|
|
191.7332455f, 193.6213854f, 195.5196223f, 197.4279651f,
|
|
199.3464228f, 201.2750043f, 203.2137184f, 205.1625740f,
|
|
207.1215799f, 209.0907449f, 211.0700776f, 213.0595868f,
|
|
215.0592813f, 217.0691695f, 219.0892603f, 221.1195621f,
|
|
223.1600835f, 225.2108331f, 227.2718194f, 229.3430508f,
|
|
231.4245359f, 233.5162830f, 235.6183005f, 237.7305968f,
|
|
239.8531803f, 241.9860592f, 244.1292419f, 246.2827366f,
|
|
248.4465516f, 250.6206950f, 252.8051751f, 255.0000000f,
|
|
};
|
|
|
|
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Arguments:
|
|
*
|
|
* Created:
|
|
*
|
|
* 04/26/1999 ikkof
|
|
*
|
|
\**************************************************************************/
|
|
|
|
DpOutputSpan *
|
|
DpOutputSpan::Create(
|
|
const DpBrush * dpBrush,
|
|
DpScanBuffer * scan,
|
|
DpContext *context,
|
|
const GpRect *drawBounds
|
|
)
|
|
{
|
|
const GpBrush * brush = GpBrush::GetBrush( (DpBrush *)(dpBrush));
|
|
|
|
if(brush)
|
|
{
|
|
return ((GpBrush*) brush)->CreateOutputSpan(scan, context, drawBounds);
|
|
}
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Gradient brush constructor.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* Created:
|
|
*
|
|
* 04/26/1999 ikkof
|
|
*
|
|
\**************************************************************************/
|
|
|
|
DpOutputGradientSpan::DpOutputGradientSpan(
|
|
const GpElementaryBrush *brush,
|
|
DpScanBuffer * scan,
|
|
DpContext* context
|
|
)
|
|
{
|
|
Scan = scan;
|
|
|
|
CompositingMode = context->CompositingMode;
|
|
|
|
Brush = brush;
|
|
BrushType = brush->GetBrushType();
|
|
brush->GetRect(BrushRect);
|
|
WrapMode = brush->GetWrapMode();
|
|
|
|
// Incorporate the brush's transform into the graphics context's
|
|
// current transform:
|
|
|
|
GpMatrix xForm;
|
|
brush->GetTransform(&xForm);
|
|
|
|
WorldToDevice = context->WorldToDevice;
|
|
WorldToDevice.Prepend(xForm);
|
|
|
|
// !!![andrewgo] garbage is left in DeviceToWorld if not invertible
|
|
|
|
if(WorldToDevice.IsInvertible())
|
|
{
|
|
DeviceToWorld = WorldToDevice;
|
|
DeviceToWorld.Invert();
|
|
}
|
|
|
|
InitDefaultColorArrays(brush);
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Converts the input value by using the blend factors and
|
|
* blend positions.
|
|
*
|
|
\**************************************************************************/
|
|
|
|
REAL
|
|
slowAdjustValue(
|
|
REAL x, INT count,
|
|
REAL falloff,
|
|
REAL* blendFactors,
|
|
REAL* blendPositions
|
|
)
|
|
{
|
|
REAL value = x;
|
|
if(count == 1 && falloff != 1 && falloff > 0)
|
|
{
|
|
if((x >= 0.0f) && (x <= 1.0f))
|
|
value = (REAL) pow(x, falloff);
|
|
}
|
|
else if(count >= 2 && blendFactors && blendPositions)
|
|
{
|
|
// This has to be an 'equality' test, because the
|
|
// DpOutputLinearGradientSpan fast-path samples only
|
|
// discretely, and it starts at exactly 0.0 and ends
|
|
// exactly at 1.0. We don't actually have to incorporate
|
|
// an epsilon in that case because it always gives us
|
|
// exactly 0.0 and 1.0 for the start and end.
|
|
|
|
if((x >= 0.0f) && (x <= 1.0f))
|
|
{
|
|
INT index = 1;
|
|
|
|
// Look for the interval.
|
|
|
|
while( ((x-blendPositions[index]) > REAL_EPSILON) &&
|
|
(index < count) )
|
|
{
|
|
index++;
|
|
}
|
|
|
|
// Interpolate.
|
|
|
|
if(index < count)
|
|
{
|
|
REAL d = blendPositions[index] - blendPositions[index - 1];
|
|
if(d > 0)
|
|
{
|
|
REAL t = (x - blendPositions[index - 1])/d;
|
|
value = blendFactors[index - 1]
|
|
+ t*(blendFactors[index] - blendFactors[index - 1]);
|
|
}
|
|
else
|
|
value = (blendFactors[index - 1] + blendFactors[index])/2;
|
|
}
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
// We make this routine inline because it's nice and small and very
|
|
// frequently we don't even have to call 'slowAdjustValue'.
|
|
|
|
inline
|
|
REAL
|
|
adjustValue(
|
|
REAL x, INT count,
|
|
REAL falloff,
|
|
REAL* blendFactors,
|
|
REAL* blendPositions
|
|
)
|
|
{
|
|
REAL value = x;
|
|
|
|
if(count != 1 || falloff != 1)
|
|
{
|
|
value = slowAdjustValue(x, count, falloff, blendFactors, blendPositions);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* GammaLinearizeAndPremultiply
|
|
*
|
|
* This function takes non-premultiplied ARGB input and emits a
|
|
* Gamma converted (2.2) output 128bit floating point premultiplied
|
|
* color value
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] ARGB - input premultiplied floating point color value
|
|
* [IN] gammaCorrect - turn on gamma correction logic
|
|
* [OUT] color - output color value. 128bit float color. premultiplied
|
|
*
|
|
* 10/31/2000 asecchia
|
|
* Created
|
|
*
|
|
\**************************************************************************/
|
|
VOID GammaLinearizeAndPremultiply(
|
|
ARGB argb, // Non-premultiplied input.
|
|
BOOL gammaCorrect,
|
|
GpFColor128 *color // pre-multiplied output.
|
|
)
|
|
{
|
|
// Alpha (opacity) shouldn't be gamma corrected.
|
|
|
|
color->a = (REAL)GpColor::GetAlphaARGB(argb);
|
|
|
|
// Alpha zero...
|
|
|
|
if(REALABS((color->a)) < REAL_EPSILON)
|
|
{
|
|
color->r = 0.0f;
|
|
color->g = 0.0f;
|
|
color->b = 0.0f;
|
|
|
|
// we're done.
|
|
return;
|
|
}
|
|
|
|
if(gammaCorrect)
|
|
{
|
|
|
|
// use the gamma 2.2 lookup table to convert r, g, b.
|
|
|
|
color->r = Gamma2_2LUT[GpColor::GetRedARGB(argb)];
|
|
color->g = Gamma2_2LUT[GpColor::GetGreenARGB(argb)];
|
|
color->b = Gamma2_2LUT[GpColor::GetBlueARGB(argb)];
|
|
|
|
}
|
|
else
|
|
{
|
|
color->r = (REAL)GpColor::GetRedARGB(argb);
|
|
color->g = (REAL)GpColor::GetGreenARGB(argb);
|
|
color->b = (REAL)GpColor::GetBlueARGB(argb);
|
|
}
|
|
|
|
// Alpha != 255
|
|
|
|
if(REALABS((color->a)-255.0f) >= REAL_EPSILON)
|
|
{
|
|
// Do the premultiplication.
|
|
|
|
color->r *= (color->a)/255.0f;
|
|
color->g *= (color->a)/255.0f;
|
|
color->b *= (color->a)/255.0f;
|
|
}
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* GammaUnlinearizePremultiplied128.
|
|
*
|
|
* This function takes a 128bit floating point premultiplied color and
|
|
* performs the inverse gamma correction step.
|
|
*
|
|
* First the color value is unpremultiplied - then the r,g,b channels are
|
|
* scaled into the range 0-1023 and rounded so that they match our 10bit
|
|
* gamma lookup table. We pass it through the 1/2.2 gamma LUT and
|
|
* premultiply the output.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] color - input premultiplied floating point color value
|
|
*
|
|
* Return:
|
|
*
|
|
* ARGB - output premultiplied 32bpp integer color (gamma corrected).
|
|
*
|
|
* 10/31/2000 asecchia
|
|
* Created
|
|
*
|
|
\**************************************************************************/
|
|
|
|
ARGB GammaUnlinearizePremultiplied128(
|
|
const GpFColor128 &color
|
|
)
|
|
{
|
|
// Do the gamma conversion thing. Ten bits is enough.
|
|
|
|
INT iA, iR, iG, iB;
|
|
|
|
// First unpremultiply. Don't do gamma conversion on the alpha channel.
|
|
|
|
iA = GpRound(color.a);
|
|
|
|
// make sure we're passed a valid input alpha channel.
|
|
|
|
ASSERT(iA >= 0);
|
|
ASSERT(iA <= 255);
|
|
|
|
// full transparency.
|
|
|
|
if(iA == 0)
|
|
{
|
|
iR = iG = iB = 0;
|
|
}
|
|
else
|
|
{
|
|
// full opacity.
|
|
|
|
if(iA == 255)
|
|
{
|
|
// Simply scale the color channels to 0-1023
|
|
|
|
iR = GpRound(color.r*(1023.0f/255.0f));
|
|
iG = GpRound(color.g*(1023.0f/255.0f));
|
|
iB = GpRound(color.b*(1023.0f/255.0f));
|
|
}
|
|
else
|
|
{
|
|
// Alpha Divide. Note that alpha already has a factor of 255 and
|
|
// so do all the color channels. Therefore when we divide the
|
|
// color channel by color.a, we implicitly cancel out the 255
|
|
// factor and all that's left is to scale up to 10bit --- hence
|
|
// the scale factor of 1023/a
|
|
|
|
REAL scale = 1023.0f/color.a;
|
|
iR = GpRound(color.r*scale);
|
|
iG = GpRound(color.g*scale);
|
|
iB = GpRound(color.b*scale);
|
|
}
|
|
}
|
|
|
|
// must be well formed color value otherwise we will AV accessing our
|
|
// gamma conversion table.
|
|
|
|
ASSERT(iB >= 0);
|
|
ASSERT(iB <= 1023);
|
|
ASSERT(iG >= 0);
|
|
ASSERT(iG <= 1023);
|
|
ASSERT(iR >= 0);
|
|
ASSERT(iR <= 1023);
|
|
|
|
// Apply Gamma using our 10bit inverse 2.2 power function table.
|
|
|
|
GpColorConverter colorConv;
|
|
colorConv.Channel.b = TenBitInvGamma2_2[iB];
|
|
colorConv.Channel.g = TenBitInvGamma2_2[iG];
|
|
colorConv.Channel.r = TenBitInvGamma2_2[iR];
|
|
colorConv.Channel.a = static_cast<BYTE>(iA); // alpha is already linear.
|
|
|
|
// Premultiply.
|
|
|
|
return GpColor::ConvertToPremultiplied(colorConv.argb);
|
|
}
|
|
|
|
|
|
VOID
|
|
interpolatePresetColors(
|
|
GpFColor128 *colorOut,
|
|
REAL x,
|
|
INT count,
|
|
ARGB* presetColors,
|
|
REAL* blendPositions,
|
|
BOOL gammaCorrect
|
|
)
|
|
{
|
|
REAL value = x;
|
|
|
|
if(count > 1 && presetColors && blendPositions)
|
|
{
|
|
if(x >= 0 && x <= 1)
|
|
{
|
|
INT index = 1;
|
|
|
|
// Look for the interval.
|
|
|
|
while(blendPositions[index] < x && index < count)
|
|
{
|
|
index++;
|
|
}
|
|
|
|
// Interpolate.
|
|
|
|
if(index < count)
|
|
{
|
|
GpFColor128 color[2];
|
|
|
|
GammaLinearizeAndPremultiply(
|
|
presetColors[index-1],
|
|
gammaCorrect,
|
|
&color[0]
|
|
);
|
|
|
|
GammaLinearizeAndPremultiply(
|
|
presetColors[index],
|
|
gammaCorrect,
|
|
&color[1]
|
|
);
|
|
|
|
REAL d = blendPositions[index] - blendPositions[index - 1];
|
|
if(d > 0)
|
|
{
|
|
REAL t = (x - blendPositions[index - 1])/d;
|
|
colorOut->a = t*(color[1].a - color[0].a) + color[0].a;
|
|
colorOut->r = t*(color[1].r - color[0].r) + color[0].r;
|
|
colorOut->g = t*(color[1].g - color[0].g) + color[0].g;
|
|
colorOut->b = t*(color[1].b - color[0].b) + color[0].b;
|
|
}
|
|
else
|
|
{
|
|
colorOut->a = (color[0].a + color[1].a)/2.0f;
|
|
colorOut->r = (color[0].r + color[1].r)/2.0f;
|
|
colorOut->g = (color[0].g + color[1].g)/2.0f;
|
|
colorOut->b = (color[0].b + color[1].b)/2.0f;
|
|
}
|
|
}
|
|
else // index == count
|
|
{
|
|
//!!! This case should not be happening if
|
|
// the blendPositions array is properly set.
|
|
// That means:
|
|
// blendPositions array is monotonically
|
|
// increasing and
|
|
// blendPositions[0] = 0
|
|
// blendPositions[count - 1] = 1.
|
|
|
|
GammaLinearizeAndPremultiply(
|
|
presetColors[count-1],
|
|
gammaCorrect,
|
|
colorOut
|
|
);
|
|
}
|
|
}
|
|
else if(x <= 0)
|
|
{
|
|
GammaLinearizeAndPremultiply(
|
|
presetColors[0],
|
|
gammaCorrect,
|
|
colorOut
|
|
);
|
|
}
|
|
else // x >= 1
|
|
{
|
|
GammaLinearizeAndPremultiply(
|
|
presetColors[count-1],
|
|
gammaCorrect,
|
|
colorOut
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
DpTriangleData::DpTriangleData(
|
|
VOID
|
|
)
|
|
{
|
|
SetValid(FALSE);
|
|
IsPolygonMode = FALSE;
|
|
GammaCorrect = FALSE;
|
|
Index[0] = 0;
|
|
Index[1] = 1;
|
|
Index[2] = 2;
|
|
GpMemset(&X[0], 0, 3*sizeof(REAL));
|
|
GpMemset(&Y[0], 0, 3*sizeof(REAL));
|
|
GpMemset(Color, 0, 3*sizeof(GpFColor128));
|
|
Xmin = Xmax = 0;
|
|
GpMemset(&M[0], 0, 3*sizeof(REAL));
|
|
GpMemset(&DeltaY[0], 0, 3*sizeof(REAL));
|
|
|
|
Falloff0 = 1;
|
|
Falloff1 = 1;
|
|
Falloff2 = 1;
|
|
BlendCount0 = 1;
|
|
BlendCount1 = 1;
|
|
BlendCount2 = 1;
|
|
BlendFactors0 = NULL;
|
|
BlendFactors1 = NULL;
|
|
BlendFactors2 = NULL;
|
|
BlendPositions0 = NULL;
|
|
BlendPositions1 = NULL;
|
|
BlendPositions2 = NULL;
|
|
|
|
XSpan[0] = 0.0f;
|
|
XSpan[1] = 0.0f;
|
|
}
|
|
|
|
VOID
|
|
DpTriangleData::SetTriangle(
|
|
GpPointF& pt0,
|
|
GpPointF& pt1,
|
|
GpPointF& pt2,
|
|
GpColor& color0,
|
|
GpColor& color1,
|
|
GpColor& color2,
|
|
BOOL isPolygonMode,
|
|
BOOL gammaCorrect
|
|
)
|
|
{
|
|
IsPolygonMode = isPolygonMode;
|
|
GammaCorrect = gammaCorrect;
|
|
|
|
// !!! [asecchia] Windows db #203480
|
|
// We're filtering the input points here because the rest of the
|
|
// gradient code is sloppy about handling the comparison between
|
|
// floating point coordinates. Basically no attempt is made to
|
|
// handle rounding error and therefore we can get random off-by-one
|
|
// scanline rendering errors based on coordinate differences
|
|
// on the order of FLT_EPSILON in size.
|
|
// Effectively we're applying a noise filter here by rounding to
|
|
// 4 bits of fractional precision. This was chosen to match our
|
|
// rasterizer rounding precision because we're in device space
|
|
// already.
|
|
|
|
X[0] = TOREAL(GpRealToFix4(pt0.X)) / 16.0f;
|
|
Y[0] = TOREAL(GpRealToFix4(pt0.Y)) / 16.0f;
|
|
X[1] = TOREAL(GpRealToFix4(pt1.X)) / 16.0f;
|
|
Y[1] = TOREAL(GpRealToFix4(pt1.Y)) / 16.0f;
|
|
X[2] = TOREAL(GpRealToFix4(pt2.X)) / 16.0f;
|
|
Y[2] = TOREAL(GpRealToFix4(pt2.Y)) / 16.0f;
|
|
|
|
GammaLinearizeAndPremultiply(
|
|
color0.GetValue(),
|
|
GammaCorrect,
|
|
&Color[0]
|
|
);
|
|
|
|
GammaLinearizeAndPremultiply(
|
|
color1.GetValue(),
|
|
GammaCorrect,
|
|
&Color[1]
|
|
);
|
|
|
|
GammaLinearizeAndPremultiply(
|
|
color2.GetValue(),
|
|
GammaCorrect,
|
|
&Color[2]
|
|
);
|
|
|
|
Xmin = Xmax = X[0];
|
|
Xmin = min(Xmin, X[1]);
|
|
Xmax = max(Xmax, X[1]);
|
|
Xmin = min(Xmin, X[2]);
|
|
Xmax = max(Xmax, X[2]);
|
|
|
|
INT i, j;
|
|
|
|
// Sort the points according to the ascending y order.
|
|
for(i = 0; i < 2; i++)
|
|
{
|
|
for(j = i; j < 3; j++)
|
|
{
|
|
if((Y[j] < Y[i]) ||
|
|
( (Y[j] == Y[i]) && (X[j] < X[i]) ))
|
|
{
|
|
REAL temp;
|
|
INT tempColor;
|
|
INT tempIndex;
|
|
|
|
tempIndex = Index[i];
|
|
Index[i] = Index[j];
|
|
Index[j] = tempIndex;
|
|
|
|
temp = X[i];
|
|
X[i] = X[j];
|
|
X[j] = temp;
|
|
temp = Y[i];
|
|
Y[i] = Y[j];
|
|
Y[j] = temp;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Calculate the gradients if possible.
|
|
|
|
if(Y[0] != Y[1])
|
|
{
|
|
// P0->P2
|
|
|
|
DeltaY[0] = TOREAL(1.0)/(Y[1] - Y[0]);
|
|
M[0] = (X[1] - X[0])*DeltaY[0];
|
|
}
|
|
if(Y[1] != Y[2])
|
|
{
|
|
// P2->P1
|
|
|
|
DeltaY[1] = TOREAL(1.0)/(Y[1] - Y[2]);
|
|
M[1] = (X[1] - X[2])*DeltaY[1];
|
|
}
|
|
if(Y[2] != Y[0])
|
|
{
|
|
// P0->P2
|
|
|
|
DeltaY[2] = TOREAL(1.0)/(Y[2] - Y[0]);
|
|
M[2] = (X[2] - X[0])*DeltaY[2];
|
|
}
|
|
|
|
SetValid(TRUE);
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Get the x span and st array values for this triangle for the scanline
|
|
* specified by y.
|
|
* NOTE: This must be called after SetXSpan for a particular value of y.
|
|
*
|
|
* Return Value:
|
|
*
|
|
* TRUE if retrieved successfully
|
|
*
|
|
* Created: peterost
|
|
*
|
|
\**************************************************************************/
|
|
|
|
BOOL
|
|
DpTriangleData::GetXSpan(REAL y, REAL xmin, REAL xmax, REAL* x, GpPointF* s)
|
|
{
|
|
// If SetXSpan did it's job correctly, we shouldn't need to do all this
|
|
// stuff. In fact we shouldn't even need to pass all these parameters.
|
|
// We simply retrieve the values for the span.
|
|
|
|
if(!IsValid() || y < Y[0] || y >= Y[2] || xmin > Xmax ||
|
|
xmax < Xmin || XSpan[0] == XSpan[1])
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
// Retrieve the span coordinates.
|
|
|
|
x[0] = XSpan[0];
|
|
x[1] = XSpan[1];
|
|
s[0].X = STGradient[0].X;
|
|
s[0].Y = STGradient[0].Y;
|
|
s[1].X = STGradient[1].X;
|
|
s[1].Y = STGradient[1].Y;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Set the x span and st array values for this triangle for the scanline
|
|
* specified by y.
|
|
* NOTE: This must be called before GetXSpan for a particular value of y.
|
|
*
|
|
* Return Value:
|
|
*
|
|
* TRUE if set successfully
|
|
*
|
|
* Created: peterost (factored out of GetXSpan created by ikkof)
|
|
*
|
|
\**************************************************************************/
|
|
|
|
BOOL
|
|
DpTriangleData::SetXSpan(REAL y, REAL xmin, REAL xmax, REAL* x)
|
|
{
|
|
if(!IsValid() || y < Y[0] || y >= Y[2] || xmin > Xmax || xmax < Xmin)
|
|
return FALSE;
|
|
|
|
REAL xSpan[2], dy;
|
|
REAL s1[2], t1[2];
|
|
|
|
if(y < Y[1]) // Y[0] <= y < Y[1]
|
|
{
|
|
dy = y - Y[0];
|
|
|
|
// P0->P1
|
|
xSpan[0] = X[0] + M[0]*dy;
|
|
s1[0] = DeltaY[0]*dy;
|
|
t1[0] = 0;
|
|
|
|
// P0->P2
|
|
xSpan[1] = X[0] + M[2]*dy;
|
|
s1[1] = 0;
|
|
t1[1] = DeltaY[2]*dy;
|
|
}
|
|
else // Y[1] <= y < Y[2]
|
|
{
|
|
// P2->P1
|
|
dy = y - Y[2];
|
|
xSpan[0] = X[2] + M[1]*dy;
|
|
s1[0] = DeltaY[1]*dy;
|
|
t1[0] = 1 - s1[0];
|
|
|
|
// P0->P2
|
|
dy = y - Y[0];
|
|
xSpan[1] = X[0] + M[2]*dy;
|
|
s1[1] = 0;
|
|
t1[1] = DeltaY[2]*dy;;
|
|
}
|
|
|
|
if(xSpan[0] == xSpan[1])
|
|
{
|
|
XSpan[0] = xSpan[0];
|
|
XSpan[1] = xSpan[1];
|
|
return FALSE;
|
|
}
|
|
|
|
// We must convert to the st values of the original
|
|
// triangle.
|
|
|
|
INT sIndex = 1, tIndex = 2;
|
|
|
|
for(INT i = 0; i < 3; i++)
|
|
{
|
|
if(Index[i] == 1)
|
|
sIndex = i;
|
|
if(Index[i] == 2)
|
|
tIndex = i;
|
|
}
|
|
|
|
REAL s[2], t[2];
|
|
|
|
switch(sIndex)
|
|
{
|
|
case 0:
|
|
s[0] = 1 - s1[0] - t1[0];
|
|
s[1] = 1 - s1[1] - t1[1];
|
|
break;
|
|
case 1:
|
|
s[0] = s1[0];
|
|
s[1] = s1[1];
|
|
break;
|
|
case 2:
|
|
s[0] = t1[0];
|
|
s[1] = t1[1];
|
|
break;
|
|
}
|
|
|
|
switch(tIndex)
|
|
{
|
|
case 0:
|
|
t[0] = 1 - s1[0] - t1[0];
|
|
t[1] = 1 - s1[1] - t1[1];
|
|
break;
|
|
case 1:
|
|
t[0] = s1[0];
|
|
t[1] = s1[1];
|
|
break;
|
|
case 2:
|
|
t[0] = t1[0];
|
|
t[1] = t1[1];
|
|
break;
|
|
}
|
|
|
|
INT k0, k1;
|
|
|
|
if(xSpan[0] < xSpan[1])
|
|
{
|
|
k0 = 0;
|
|
k1 = 1;
|
|
}
|
|
else
|
|
{
|
|
k0 = 1;
|
|
k1 = 0;
|
|
}
|
|
|
|
XSpan[k0] = xSpan[0];
|
|
XSpan[k1] = xSpan[1];
|
|
STGradient[k0].X = s[0];
|
|
STGradient[k1].X = s[1];
|
|
STGradient[k0].Y = t[0];
|
|
STGradient[k1].Y = t[1];
|
|
|
|
x[0] = XSpan[0];
|
|
x[1] = XSpan[1];
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
GpStatus
|
|
DpTriangleData::OutputSpan(
|
|
ARGB* buffer,
|
|
INT compositingMode,
|
|
INT y,
|
|
INT &xMin,
|
|
INT &xMax // xMax is exclusive
|
|
)
|
|
{
|
|
PointF st[2];
|
|
REAL xSpan[2];
|
|
|
|
// First grab the span for this y coordinate.
|
|
// Note that GetXSpan returns the xSpan coordinates and the (s, t) texture
|
|
// coordinates and that both are unclipped. We have to infer the clipping
|
|
// based on the difference between the xSpan coordinates and the
|
|
// input xMin and xMax coordinates and explicitly apply clipping to the
|
|
// texture space coordinates (s, t).
|
|
//
|
|
// ( Texture mapping and gradient filling are mathematically similar
|
|
// problems so we use 'texture space' and 'texture coordinates'
|
|
// to refer to the gradient interpolation. In this way, gradient fills
|
|
// can be thought of as procedurally-defined textures. )
|
|
|
|
if(!GetXSpan((REAL) y, (REAL) xMin, (REAL) xMax, xSpan, st))
|
|
{
|
|
return Ok;
|
|
}
|
|
|
|
// SetXSpan ensures a correct ordering of the xSpan coordinates.
|
|
// We rely on this, so we must ASSERT it.
|
|
|
|
ASSERT(xSpan[0] <= xSpan[1]);
|
|
|
|
// Round using our rasterizer rounding rules.
|
|
// This is not strictly true, though, our rasterizer uses GpFix4Ceiling
|
|
// See RasterizerCeiling
|
|
|
|
INT xLeft = GpFix4Round(GpRealToFix4(xSpan[0]));
|
|
INT xRight = GpFix4Round(GpRealToFix4(xSpan[1]));
|
|
|
|
// Clip the x values.
|
|
|
|
xLeft = max(xLeft, xMin);
|
|
xRight = min(xRight, xMax); // remember, xMax is exclusive.
|
|
|
|
// We're done. No pixels to emit.
|
|
|
|
if(xLeft >= xRight)
|
|
{
|
|
return Ok;
|
|
}
|
|
|
|
// Now compute the per pixel interpolation increments for the
|
|
// texture (s, t) coordinates.
|
|
|
|
// Here are our actual interpolation coordinates.
|
|
// Start them off at the left-hand edge of the span.
|
|
|
|
REAL s = st[0].X;
|
|
REAL t = st[0].Y;
|
|
|
|
// Left clipping.
|
|
|
|
// This is the amount to clip off the left edge of the span to reach
|
|
// the left-most pixel.
|
|
|
|
REAL clipLength = (REAL)xLeft - xSpan[0];
|
|
|
|
if(REALABS(clipLength) > REAL_EPSILON)
|
|
{
|
|
ASSERT((xSpan[1]-xSpan[0]) != 0.0f);
|
|
|
|
// Compute the proportion of the span that we're clipping off.
|
|
// This is in the range [0,1]
|
|
|
|
REAL u = clipLength/(xSpan[1]-xSpan[0]);
|
|
|
|
// Apply the proportion to texture space and then add to the left
|
|
// texture coordinate.
|
|
|
|
s += u*(st[1].X-st[0].X);
|
|
t += u*(st[1].Y-st[0].Y);
|
|
}
|
|
|
|
// Temporaries to store the right-hand texture endpoint for the span.
|
|
|
|
REAL s_right = st[1].X;
|
|
REAL t_right = st[1].Y;
|
|
|
|
// Right clipping.
|
|
|
|
// This is the amount to clip off the right edge of the span to reach
|
|
// the right-most pixel.
|
|
|
|
clipLength = xSpan[1] - xRight;
|
|
|
|
if(REALABS(clipLength) > REAL_EPSILON)
|
|
{
|
|
ASSERT((xSpan[1]-xSpan[0]) != 0.0f);
|
|
|
|
// Compute the proportion of the span that we're clipping off.
|
|
// This is in the range [0,1]
|
|
|
|
REAL u = clipLength/(xSpan[1]-xSpan[0]);
|
|
|
|
// Apply the proportion to texture space and then subtract from the
|
|
// right texture coordinate.
|
|
|
|
s_right -= u*(st[1].X-st[0].X);
|
|
t_right -= u*(st[1].Y-st[0].Y);
|
|
}
|
|
|
|
// Divide each texture coordinate interval by the number of pixels we're
|
|
// emitting. Note that xRight != xLeft. Also note that SetXSpan ensures a
|
|
// correct ordering of the xSpan coordinates. This gives us a set of
|
|
// per pixel delta values for the (s, t) coordinates.
|
|
// The next pixels texture coordinate is computed according
|
|
// to the following formula:
|
|
// (s', t') <-- (s, t) + (ds, dt)
|
|
|
|
ASSERT(xRight > xLeft);
|
|
|
|
REAL ds = (s_right - s)/(xRight - xLeft);
|
|
REAL dt = (t_right - t)/(xRight - xLeft);
|
|
|
|
GpFColor128 colorOut;
|
|
|
|
buffer += (xLeft - xMin);
|
|
|
|
for(INT x = xLeft; x < xRight; x++, buffer++)
|
|
{
|
|
if(!(UsesPresetColors && BlendPositions0 && BlendCount0 > 1))
|
|
{
|
|
if(BlendCount0 == 1 && Falloff0 == 1
|
|
&& BlendCount1 == 1 && Falloff1 == 1
|
|
&& BlendCount2 == 1 && Falloff2 == 1)
|
|
{
|
|
colorOut.a = Color[0].a + s*(Color[1].a - Color[0].a) + t*(Color[2].a - Color[0].a);
|
|
colorOut.r = Color[0].r + s*(Color[1].r - Color[0].r) + t*(Color[2].r - Color[0].r);
|
|
colorOut.g = Color[0].g + s*(Color[1].g - Color[0].g) + t*(Color[2].g - Color[0].g);
|
|
colorOut.b = Color[0].b + s*(Color[1].b - Color[0].b) + t*(Color[2].b - Color[0].b);
|
|
}
|
|
else
|
|
{
|
|
REAL u1, s1, t1;
|
|
|
|
u1 = ::adjustValue(1 - s - t, BlendCount0, Falloff0,
|
|
BlendFactors0, BlendPositions0);
|
|
s1 = ::adjustValue(s, BlendCount1, Falloff1,
|
|
BlendFactors1, BlendPositions1);
|
|
t1 = ::adjustValue(t, BlendCount2, Falloff2,
|
|
BlendFactors2, BlendPositions2);
|
|
|
|
REAL sum;
|
|
|
|
if(!IsPolygonMode)
|
|
{
|
|
sum = u1 + s1 + t1;
|
|
u1 = u1/sum;
|
|
s1 = s1/sum;
|
|
t1 = t1/sum;
|
|
}
|
|
else
|
|
{
|
|
// If it is the polygon gradient, treat u1 differently.
|
|
// This gives the similar behavior as RadialGradient.
|
|
|
|
sum = s1 + t1;
|
|
if(sum != 0)
|
|
{
|
|
sum = (1 - u1)/sum;
|
|
s1 *= sum;
|
|
t1 *= sum;
|
|
}
|
|
}
|
|
|
|
colorOut.a = Color[0].a + s1*(Color[1].a - Color[0].a) + t1*(Color[2].a - Color[0].a);
|
|
colorOut.r = Color[0].r + s1*(Color[1].r - Color[0].r) + t1*(Color[2].r - Color[0].r);
|
|
colorOut.g = Color[0].g + s1*(Color[1].g - Color[0].g) + t1*(Color[2].g - Color[0].g);
|
|
colorOut.b = Color[0].b + s1*(Color[1].b - Color[0].b) + t1*(Color[2].b - Color[0].b);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
interpolatePresetColors(
|
|
&colorOut,
|
|
1 - s - t,
|
|
BlendCount0,
|
|
PresetColors,
|
|
BlendPositions0,
|
|
GammaCorrect
|
|
);
|
|
}
|
|
|
|
s += ds;
|
|
t += dt;
|
|
|
|
if((REALABS(colorOut.a) >= REAL_EPSILON) ||
|
|
compositingMode == CompositingModeSourceCopy)
|
|
{
|
|
GpColorConverter colorConv;
|
|
|
|
// Make sure the colorOut is properly premultiplied.
|
|
|
|
CLAMP_COLOR_CHANNEL(colorOut.a, 255.0f)
|
|
CLAMP_COLOR_CHANNEL(colorOut.r, colorOut.a);
|
|
CLAMP_COLOR_CHANNEL(colorOut.g, colorOut.a);
|
|
CLAMP_COLOR_CHANNEL(colorOut.b, colorOut.a);
|
|
|
|
if(GammaCorrect)
|
|
{
|
|
colorConv.argb = GammaUnlinearizePremultiplied128(colorOut);
|
|
}
|
|
else
|
|
{
|
|
colorConv.Channel.a = static_cast<BYTE>(GpRound(colorOut.a));
|
|
colorConv.Channel.r = static_cast<BYTE>(GpRound(colorOut.r));
|
|
colorConv.Channel.g = static_cast<BYTE>(GpRound(colorOut.g));
|
|
colorConv.Channel.b = static_cast<BYTE>(GpRound(colorOut.b));
|
|
}
|
|
|
|
// Clamp to the alpha channel for the premultiplied alpha blender.
|
|
|
|
*buffer = colorConv.argb;
|
|
}
|
|
else
|
|
{
|
|
*buffer = 0; // case of CompositingModeSourceOver && alpha = 0
|
|
}
|
|
}
|
|
|
|
return Ok;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Outputs a single span within a raster with a gradient brush.
|
|
* Is called by the rasterizer.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] y - the Y value of the raster being output
|
|
* [IN] leftEdge - the DDA class of the left edge
|
|
* [IN] rightEdge - the DDA class of the right edge
|
|
*
|
|
* Return Value:
|
|
*
|
|
* GpStatus - Ok
|
|
*
|
|
* Created:
|
|
*
|
|
* 01/21/1999 ikkof
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
DpOutputGradientSpan::OutputSpan(
|
|
INT y,
|
|
INT xMin,
|
|
INT xMax // xMax is exclusive
|
|
)
|
|
{
|
|
ARGB argb;
|
|
INT width = xMax - xMin;
|
|
if(width <= 0)
|
|
return Ok;
|
|
|
|
ARGB * buffer = Scan->NextBuffer(xMin, y, width);
|
|
|
|
GpPointF pt1, pt2;
|
|
pt1.X = (REAL) xMin;
|
|
pt1.Y = pt2.Y = (REAL) y;
|
|
pt2.X = (REAL)xMax;
|
|
|
|
DeviceToWorld.Transform(&pt1);
|
|
DeviceToWorld.Transform(&pt2);
|
|
|
|
REAL u1, v1, u2, v2, du, dv;
|
|
|
|
u1 = (pt1.X - BrushRect.X)/BrushRect.Width;
|
|
v1 = (pt1.Y - BrushRect.Y)/BrushRect.Height;
|
|
u2 = (pt2.X - BrushRect.X)/BrushRect.Width;
|
|
v2 = (pt2.Y - BrushRect.Y)/BrushRect.Height;
|
|
du = (u2 - u1)/width;
|
|
dv = (v2 - v1)/width;
|
|
|
|
INT i;
|
|
REAL u0 = u1, v0 = v1;
|
|
REAL u, v;
|
|
|
|
REAL delta = min(BrushRect.Width, BrushRect.Height)/2;
|
|
|
|
if(REALABS(delta) < REAL_EPSILON)
|
|
{
|
|
delta = 1.0f;
|
|
}
|
|
|
|
REAL deltaInv = 1.0f/delta;
|
|
|
|
for(i = 0; i < width; i++, buffer++)
|
|
{
|
|
u = u0;
|
|
v = v0;
|
|
REAL alpha = 0, red = 0, green = 0, blue = 0;
|
|
|
|
// If this is the outside of the rectangle in Clamp mode,
|
|
// don't draw anything.
|
|
|
|
if(WrapMode == WrapModeClamp)
|
|
{
|
|
if(u < 0 || u > 1 || v < 0 || v > 1)
|
|
{
|
|
*buffer = 0;
|
|
|
|
goto NextUV;
|
|
}
|
|
}
|
|
|
|
// Remap the v-coordinate in Tile mode.
|
|
|
|
if(WrapMode == WrapModeTile || WrapMode == WrapModeTileFlipX)
|
|
{
|
|
// Get the fractional part of v.
|
|
v = GpModF(v, 1);
|
|
}
|
|
else if(WrapMode == WrapModeTileFlipY || WrapMode == WrapModeTileFlipXY)
|
|
{
|
|
INT nV;
|
|
|
|
nV = GpFloor(v);
|
|
v = GpModF(v, 1);
|
|
|
|
if(nV & 1)
|
|
v = 1 - v; // flip.
|
|
}
|
|
|
|
// Remap the u-coordinate in Tile mode.
|
|
|
|
if(WrapMode == WrapModeTile || WrapMode == WrapModeTileFlipY)
|
|
{
|
|
// Get the fractional part of u.
|
|
u = GpModF(u, 1);
|
|
}
|
|
else if(WrapMode == WrapModeTileFlipX || WrapMode == WrapModeTileFlipXY)
|
|
{
|
|
INT nU;
|
|
|
|
nU = GpFloor(u);
|
|
u = GpModF(u, 1);
|
|
|
|
if(nU & 1)
|
|
u = 1 - u; // flip.
|
|
}
|
|
|
|
if(/*BrushType == BrushRectGrad ||*/ BrushType == BrushTypeLinearGradient)
|
|
{
|
|
const GpRectGradient* rectGrad = static_cast<const GpRectGradient*> (Brush);
|
|
|
|
if(!(rectGrad->HasPresetColors() &&
|
|
rectGrad->DeviceBrush.PresetColors &&
|
|
rectGrad->DeviceBrush.BlendPositions[0] &&
|
|
rectGrad->DeviceBrush.BlendCounts[0] > 1))
|
|
{
|
|
u = ::adjustValue(
|
|
u,
|
|
rectGrad->DeviceBrush.BlendCounts[0],
|
|
rectGrad->DeviceBrush.Falloffs[0],
|
|
rectGrad->DeviceBrush.BlendFactors[0],
|
|
rectGrad->DeviceBrush.BlendPositions[0]
|
|
);
|
|
|
|
v = ::adjustValue(
|
|
v,
|
|
rectGrad->DeviceBrush.BlendCounts[1],
|
|
rectGrad->DeviceBrush.Falloffs[1],
|
|
rectGrad->DeviceBrush.BlendFactors[1],
|
|
rectGrad->DeviceBrush.BlendPositions[1]
|
|
);
|
|
|
|
REAL c[4];
|
|
|
|
c[0] = (1 - u)*(1 - v);
|
|
c[1] = u*(1 - v);
|
|
c[2] = (1 - u)*v;
|
|
c[3] = u*v;
|
|
|
|
// We must interpolate alpha.
|
|
alpha = c[0]*A[0] + c[1]*A[1]
|
|
+ c[2]*A[2] + c[3]*A[3];
|
|
red = c[0]*R[0] + c[1]*R[1]
|
|
+ c[2]*R[2] + c[3]*R[3];
|
|
green = c[0]*G[0] + c[1]*G[1]
|
|
+ c[2]*G[2] + c[3]*G[3];
|
|
blue = c[0]*B[0] + c[1]*B[1]
|
|
+ c[2]*B[2] + c[3]*B[3];
|
|
}
|
|
else
|
|
{
|
|
GpFColor128 color;
|
|
interpolatePresetColors(
|
|
&color,
|
|
u,
|
|
rectGrad->DeviceBrush.BlendCounts[0],
|
|
rectGrad->DeviceBrush.PresetColors,
|
|
rectGrad->DeviceBrush.BlendPositions[0],
|
|
FALSE
|
|
);
|
|
|
|
alpha = color.a;
|
|
red = color.r;
|
|
green = color.g;
|
|
blue = color.b;
|
|
}
|
|
}
|
|
|
|
if(alpha != 0 || CompositingMode == CompositingModeSourceCopy)
|
|
{
|
|
CLAMP_COLOR_CHANNEL(alpha, 255.0f);
|
|
CLAMP_COLOR_CHANNEL(red, alpha);
|
|
CLAMP_COLOR_CHANNEL(green, alpha);
|
|
CLAMP_COLOR_CHANNEL(blue, alpha);
|
|
|
|
*buffer = GpColor::MakeARGB(
|
|
static_cast<BYTE>(GpRound(alpha)),
|
|
static_cast<BYTE>(GpRound(red)),
|
|
static_cast<BYTE>(GpRound(green)),
|
|
static_cast<BYTE>(GpRound(blue)));
|
|
}
|
|
else
|
|
{
|
|
*buffer = 0; // case of CompositingModeSourceOver && alpha = 0
|
|
}
|
|
|
|
NextUV:
|
|
u0 += du;
|
|
v0 += dv;
|
|
}
|
|
|
|
return Ok;
|
|
}
|
|
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Constructor for One Dimentional Gradient.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] brush - brush
|
|
* [IN] scan - the scan buffer
|
|
* [IN] context - the context
|
|
* [IN] isHorizontal - TRUE if this is the horizontal gradient.
|
|
* Also TRUE for more complicated one-D gradient like
|
|
* Radial Gradient.
|
|
* [IN] isVertical - TRUE if this is the vertical gradient.
|
|
*
|
|
* Return Value:
|
|
*
|
|
* NONE
|
|
*
|
|
* Created:
|
|
*
|
|
* 12/21/1999 ikkof
|
|
*
|
|
\**************************************************************************/
|
|
|
|
DpOutputOneDGradientSpan::DpOutputOneDGradientSpan(
|
|
const GpElementaryBrush *brush,
|
|
DpScanBuffer * scan,
|
|
DpContext* context,
|
|
BOOL isHorizontal,
|
|
BOOL isVertical
|
|
) : DpOutputGradientSpan(brush, scan, context)
|
|
{
|
|
FPUStateSaver::AssertMode();
|
|
|
|
Initialize();
|
|
|
|
GpStatus status = AllocateOneDData(isHorizontal, isVertical);
|
|
|
|
if(status == Ok)
|
|
{
|
|
if(BrushType == BrushTypeLinearGradient)
|
|
{
|
|
SetupRectGradientOneDData();
|
|
}
|
|
}
|
|
|
|
if(status == Ok)
|
|
SetValid(TRUE);
|
|
}
|
|
|
|
GpStatus
|
|
DpOutputOneDGradientSpan::AllocateOneDData(
|
|
BOOL isHorizontal,
|
|
BOOL isVertical
|
|
)
|
|
{
|
|
if(!isHorizontal && !isVertical)
|
|
return InvalidParameter;
|
|
|
|
IsHorizontal = isHorizontal;
|
|
IsVertical = isVertical;
|
|
|
|
GpPointF axis[4];
|
|
|
|
axis[0].X = 0;
|
|
axis[0].Y = 0;
|
|
axis[1].X = BrushRect.Width;
|
|
axis[1].Y = 0;
|
|
axis[2].X = BrushRect.Width;
|
|
axis[2].Y = BrushRect.Height;
|
|
axis[3].X = 0;
|
|
axis[3].Y = BrushRect.Height;
|
|
|
|
WorldToDevice.VectorTransform(&axis[0], 4);
|
|
|
|
// Calculate the sum of the diagonals as the largest possible distance
|
|
// of interest and use this to size the OneD array of colors. This gets us
|
|
// to within 1 bit of the "true" gradient values for each A,R,G,B channel.
|
|
REAL d1 = REALSQRT(distance_squared(axis[0], axis[2]));
|
|
REAL d2 = REALSQRT(distance_squared(axis[1], axis[3]));
|
|
|
|
OneDDataMultiplier = max(1, GpCeiling(d1+d2));
|
|
OneDDataCount = OneDDataMultiplier + 2;
|
|
|
|
GpStatus status = Ok;
|
|
|
|
OneDData = (ARGB*) GpMalloc(OneDDataCount*sizeof(ARGB));
|
|
|
|
if(!OneDData)
|
|
status = OutOfMemory;
|
|
|
|
return status;
|
|
}
|
|
|
|
DpOutputOneDGradientSpan::~DpOutputOneDGradientSpan()
|
|
{
|
|
if(OneDData)
|
|
GpFree(OneDData);
|
|
}
|
|
|
|
VOID
|
|
DpOutputOneDGradientSpan::SetupRectGradientOneDData(
|
|
)
|
|
{
|
|
REAL u, u0, du;
|
|
|
|
u0 = 0;
|
|
du = 1.0f/OneDDataMultiplier;
|
|
ARGB* buffer = OneDData;
|
|
|
|
ASSERT(buffer);
|
|
if(!buffer)
|
|
return;
|
|
|
|
for(INT i = 0; i < OneDDataCount; i++, buffer++)
|
|
{
|
|
u = u0;
|
|
|
|
const GpRectGradient* rectGrad = static_cast<const GpRectGradient*> (Brush);
|
|
|
|
REAL alpha = 0, red = 0, green = 0, blue = 0;
|
|
|
|
if(!(rectGrad->HasPresetColors() &&
|
|
rectGrad->DeviceBrush.PresetColors &&
|
|
rectGrad->DeviceBrush.BlendPositions[0] &&
|
|
rectGrad->DeviceBrush.BlendCounts[0] > 1))
|
|
{
|
|
INT index, i0, i1;
|
|
REAL a0, r0, g0, b0, a1, r1, g1, b1;
|
|
|
|
if(IsHorizontal)
|
|
{
|
|
index = 0;
|
|
i0 = 0;
|
|
i1 = 1;
|
|
}
|
|
else
|
|
{
|
|
index = 1;
|
|
i0 = 0;
|
|
i1 = 2;
|
|
}
|
|
|
|
a0 = A[i0];
|
|
r0 = R[i0];
|
|
g0 = G[i0];
|
|
b0 = B[i0];
|
|
|
|
a1 = A[i1];
|
|
r1 = R[i1];
|
|
g1 = G[i1];
|
|
b1 = B[i1];
|
|
|
|
u = ::adjustValue(
|
|
u0,
|
|
rectGrad->DeviceBrush.BlendCounts[index],
|
|
rectGrad->DeviceBrush.Falloffs[index],
|
|
rectGrad->DeviceBrush.BlendFactors[index],
|
|
rectGrad->DeviceBrush.BlendPositions[index]
|
|
);
|
|
|
|
REAL c[2];
|
|
|
|
c[0] = (1 - u);
|
|
c[1] = u;
|
|
|
|
// We must interpolate alpha.
|
|
alpha = c[0]*a0 + c[1]*a1;
|
|
red = c[0]*r0 + c[1]*r1;
|
|
green = c[0]*g0 + c[1]*g1;
|
|
blue = c[0]*b0 + c[1]*b1;
|
|
}
|
|
else
|
|
{
|
|
GpFColor128 color;
|
|
|
|
interpolatePresetColors(
|
|
&color,
|
|
u0,
|
|
rectGrad->DeviceBrush.BlendCounts[0],
|
|
rectGrad->DeviceBrush.PresetColors,
|
|
rectGrad->DeviceBrush.BlendPositions[0],
|
|
FALSE
|
|
);
|
|
alpha = color.a;
|
|
red = color.r;
|
|
green = color.g;
|
|
blue = color.b;
|
|
}
|
|
|
|
if(alpha != 0 || CompositingMode == CompositingModeSourceCopy)
|
|
{
|
|
CLAMP_COLOR_CHANNEL(alpha, 255.0f);
|
|
CLAMP_COLOR_CHANNEL(red, alpha);
|
|
CLAMP_COLOR_CHANNEL(green, alpha);
|
|
CLAMP_COLOR_CHANNEL(blue, alpha);
|
|
|
|
*buffer = GpColor::MakeARGB(
|
|
static_cast<BYTE>(GpRound(alpha)),
|
|
static_cast<BYTE>(GpRound(red)),
|
|
static_cast<BYTE>(GpRound(green)),
|
|
static_cast<BYTE>(GpRound(blue)));
|
|
}
|
|
else
|
|
{
|
|
*buffer = 0; // case of CompositingModeSourceOver && alpha = 0
|
|
}
|
|
|
|
u0 += du;
|
|
}
|
|
}
|
|
|
|
|
|
VOID
|
|
DpOutputOneDGradientSpan::SetupRadialGradientOneDData()
|
|
{
|
|
ASSERT(FALSE);
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Outputs a single span within a raster with a gradient brush.
|
|
* Is called by the rasterizer.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] y - the Y value of the raster being output
|
|
* [IN] leftEdge - the DDA class of the left edge
|
|
* [IN] rightEdge - the DDA class of the right edge
|
|
*
|
|
* Return Value:
|
|
*
|
|
* GpStatus - Ok
|
|
*
|
|
* Created:
|
|
*
|
|
* 01/21/1999 ikkof
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
DpOutputOneDGradientSpan::OutputSpan(
|
|
INT y,
|
|
INT xMin,
|
|
INT xMax // xMax is exclusive
|
|
)
|
|
{
|
|
ARGB argb;
|
|
INT width = xMax - xMin;
|
|
if(width <= 0 || !OneDData)
|
|
return Ok;
|
|
|
|
ARGB * buffer = Scan->NextBuffer(xMin, y, width);
|
|
|
|
GpPointF pt1, pt2;
|
|
pt1.X = (REAL) xMin;
|
|
pt1.Y = pt2.Y = (REAL) y;
|
|
pt2.X = (REAL)xMax;
|
|
|
|
DeviceToWorld.Transform(&pt1);
|
|
DeviceToWorld.Transform(&pt2);
|
|
|
|
REAL u1, v1, u2, v2;
|
|
|
|
u1 = (pt1.X - BrushRect.X)/BrushRect.Width;
|
|
v1 = (pt1.Y - BrushRect.Y)/BrushRect.Height;
|
|
u2 = (pt2.X - BrushRect.X)/BrushRect.Width;
|
|
v2 = (pt2.Y - BrushRect.Y)/BrushRect.Height;
|
|
|
|
INT u1Major, u2Major, v1Major, v2Major;
|
|
INT u1Minor, u2Minor, v1Minor, v2Minor;
|
|
|
|
u1Major = GpFloor(u1);
|
|
u2Major = GpFloor(u2);
|
|
u1Minor = GpRound(OneDDataMultiplier*(u1 - u1Major));
|
|
u2Minor = GpRound(OneDDataMultiplier*(u2 - u2Major));
|
|
|
|
v1Major = GpFloor(v1);
|
|
v2Major = GpFloor(v2);
|
|
v1Minor = GpRound(OneDDataMultiplier*(v1 - v1Major));
|
|
v2Minor = GpRound(OneDDataMultiplier*(v2 - v2Major));
|
|
|
|
INT du, dv;
|
|
|
|
du = GpRound((u2 - u1)*OneDDataMultiplier/width);
|
|
dv = GpRound((v2 - v1)*OneDDataMultiplier/width);
|
|
|
|
INT i;
|
|
|
|
INT uMajor, uMinor, vMajor, vMinor;
|
|
|
|
uMajor = u1Major;
|
|
uMinor = u1Minor;
|
|
vMajor = v1Major;
|
|
vMinor = v1Minor;
|
|
|
|
if(BrushType == BrushTypeLinearGradient)
|
|
{
|
|
for(i = 0; i < width; i++, buffer++)
|
|
{
|
|
if(IsHorizontal)
|
|
{
|
|
if((WrapMode == WrapModeTileFlipX || WrapMode == WrapModeTileFlipXY)
|
|
&& (uMajor & 0x01) != 0)
|
|
*buffer = OneDData[OneDDataMultiplier - uMinor];
|
|
else
|
|
*buffer = OneDData[uMinor];
|
|
}
|
|
else if(IsVertical)
|
|
{
|
|
if((WrapMode == WrapModeTileFlipY || WrapMode == WrapModeTileFlipXY)
|
|
&& (vMajor & 0x01) != 0)
|
|
*buffer = OneDData[OneDDataMultiplier - vMinor];
|
|
else
|
|
*buffer = OneDData[vMinor];
|
|
}
|
|
|
|
if(WrapMode == WrapModeClamp)
|
|
{
|
|
if(uMajor != 0 || vMajor != 0)
|
|
*buffer = 0;
|
|
}
|
|
|
|
uMinor += du;
|
|
|
|
while(uMinor >= OneDDataMultiplier)
|
|
{
|
|
uMajor++;
|
|
uMinor -= OneDDataMultiplier;
|
|
}
|
|
|
|
while(uMinor < 0)
|
|
{
|
|
uMajor--;
|
|
uMinor += OneDDataMultiplier;
|
|
}
|
|
|
|
vMinor += dv;
|
|
|
|
while(vMinor >= OneDDataMultiplier)
|
|
{
|
|
vMajor++;
|
|
vMinor -= OneDDataMultiplier;
|
|
}
|
|
|
|
while(vMinor < 0)
|
|
{
|
|
vMajor--;
|
|
vMinor += OneDDataMultiplier;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Ok;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Constructor for linear gradient.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] brush - brush
|
|
* [IN] scan - the scan buffer
|
|
* [IN] context - the context
|
|
*
|
|
* Return Value:
|
|
*
|
|
* NONE
|
|
*
|
|
* Created:
|
|
*
|
|
* 1/13/2000 andrewgo
|
|
*
|
|
\**************************************************************************/
|
|
|
|
DpOutputLinearGradientSpan::DpOutputLinearGradientSpan(
|
|
const GpElementaryBrush *brush,
|
|
DpScanBuffer * scan,
|
|
DpContext* context
|
|
) : DpOutputGradientSpan(brush, scan, context)
|
|
{
|
|
SetValid(FALSE);
|
|
|
|
const GpRectGradient* gradient = static_cast<const GpRectGradient*>(brush);
|
|
|
|
// Copy some brush attributes to locals for speed:
|
|
|
|
GpRectF brushRect;
|
|
gradient->GetRect(brushRect);
|
|
|
|
BOOL doPresetColor = (gradient->HasPresetColors());
|
|
BOOL doAdjustValue = (gradient->DeviceBrush.BlendCounts[0] != 1) ||
|
|
(gradient->DeviceBrush.Falloffs[0] != 1);
|
|
|
|
// For now we just assume 32 pixels in the texture. In the future,
|
|
// we can change this to be inherited from the API.
|
|
|
|
UINT numberOfIntervalBits = 5;
|
|
UINT numberOfTexels = 32;
|
|
|
|
// If we're doing a fancy blend with multiple color points.
|
|
|
|
if(doPresetColor || doAdjustValue)
|
|
{
|
|
// Office specifies simple blends with 2-3 blend factors. If it's a
|
|
// simple blend, 32 texels is enough, but if it's complicated lets
|
|
// use more texels. Use in the range of the width + height, with a
|
|
// cap of 512. Using too many texels can result in overflow errors
|
|
// when calculating M11, M21 and Dx, and is wasted memory and
|
|
// processing power.
|
|
|
|
if(gradient->DeviceBrush.BlendCounts[0] > 3)
|
|
{
|
|
REAL widthHeight = brushRect.Width + brushRect.Height;
|
|
if (widthHeight > 512)
|
|
{
|
|
numberOfTexels = 512;
|
|
numberOfIntervalBits = 9;
|
|
}
|
|
else if (widthHeight > 128)
|
|
{
|
|
numberOfTexels = 128;
|
|
numberOfIntervalBits = 7;
|
|
}
|
|
}
|
|
}
|
|
|
|
const UINT halfNumberOfTexels = numberOfTexels / 2;
|
|
|
|
// The number of texels has to be a power of two:
|
|
|
|
ASSERT((numberOfTexels & (numberOfTexels - 1)) == 0);
|
|
|
|
// Remember the size:
|
|
|
|
IntervalMask = numberOfTexels - 1;
|
|
NumberOfIntervalBits = numberOfIntervalBits;
|
|
|
|
// We want to create a transform that takes us from any point in the
|
|
// device-space brush parallelogram to normalized texture coordinates.
|
|
// We're a bit tricky here and do the divide by 2 to handle TileFlipX:
|
|
|
|
REAL normalizedSize = (REAL)(numberOfTexels * (1 << ONEDNUMFRACTIONALBITS));
|
|
|
|
GpRectF normalizedRect(
|
|
0.0f, 0.0f,
|
|
normalizedSize / 2,
|
|
normalizedSize / 2
|
|
);
|
|
|
|
GpMatrix normalizeBrushRect;
|
|
if (normalizeBrushRect.InferAffineMatrix(brushRect, normalizedRect) == Ok)
|
|
{
|
|
DeviceToNormalized = WorldToDevice;
|
|
DeviceToNormalized.Prepend(normalizeBrushRect);
|
|
if (DeviceToNormalized.Invert() == Ok)
|
|
{
|
|
// Convert the transform to fixed point units:
|
|
|
|
M11 = GpRound(DeviceToNormalized.GetM11());
|
|
M21 = GpRound(DeviceToNormalized.GetM21());
|
|
Dx = GpRoundSat(DeviceToNormalized.GetDx());
|
|
|
|
// For every pixel that we step one to the right in device space,
|
|
// we need to know the corresponding x-increment in texture (err,
|
|
// I mean gradient) space. Take a (1, 0) device vector, pop-it
|
|
// through the device-to-normalized transform, and you get this
|
|
// as the xIncrement result:
|
|
|
|
XIncrement = M11;
|
|
|
|
ULONG i;
|
|
GpFColor128 color;
|
|
REAL w;
|
|
|
|
// should we perform gamma correction to gamma 2.2
|
|
|
|
BOOL doGammaConversion = brush->GetGammaCorrection();
|
|
|
|
// Store our real converted color channels.
|
|
|
|
GpFColor128 A, B;
|
|
|
|
// Convert the end colors to premultiplied form,
|
|
// Convert the end color components to REALs,
|
|
// ... and pre-gamma convert to 2.2 if necessary.
|
|
|
|
GammaLinearizeAndPremultiply(
|
|
gradient->DeviceBrush.Colors[0].GetValue(),
|
|
doGammaConversion,
|
|
&A
|
|
);
|
|
|
|
GammaLinearizeAndPremultiply(
|
|
gradient->DeviceBrush.Colors[1].GetValue(),
|
|
doGammaConversion,
|
|
&B
|
|
);
|
|
|
|
// Okay, now we simply have to load the texture:
|
|
|
|
ULONGLONG *startTexelArgb = &StartTexelArgb[0];
|
|
ULONGLONG *endTexelArgb = &EndTexelArgb[0];
|
|
|
|
AGRB64TEXEL *startTexelAgrb = &StartTexelAgrb[0];
|
|
AGRB64TEXEL *endTexelAgrb = &EndTexelAgrb[0];
|
|
|
|
REAL wIncrement = 1.0f / halfNumberOfTexels;
|
|
|
|
// Note that we're looping through ONEDREALTEXTUREWIDTH + 1
|
|
// elements!
|
|
|
|
for (w = 0, i = 0;
|
|
i <= halfNumberOfTexels;
|
|
w += wIncrement, i++)
|
|
{
|
|
// We sample the specified interpolators at our fixed
|
|
// frequency:
|
|
|
|
if (doPresetColor)
|
|
{
|
|
interpolatePresetColors(
|
|
&color, w,
|
|
gradient->DeviceBrush.BlendCounts[0],
|
|
gradient->DeviceBrush.PresetColors,
|
|
gradient->DeviceBrush.BlendPositions[0],
|
|
doGammaConversion
|
|
);
|
|
}
|
|
else
|
|
{
|
|
REAL multB = w;
|
|
if (doAdjustValue)
|
|
{
|
|
multB = slowAdjustValue(w,
|
|
gradient->DeviceBrush.BlendCounts[0],
|
|
gradient->DeviceBrush.Falloffs[0],
|
|
gradient->DeviceBrush.BlendFactors[0],
|
|
gradient->DeviceBrush.BlendPositions[0]);
|
|
|
|
// !!![andrewgo] This can produce out-of-range numbers
|
|
}
|
|
|
|
REAL multA = 1.0f - multB;
|
|
|
|
color.a = (A.a * multA) + (B.a * multB);
|
|
color.r = (A.r * multA) + (B.r * multB);
|
|
color.g = (A.g * multA) + (B.g * multB);
|
|
color.b = (A.b * multA) + (B.b * multB);
|
|
}
|
|
|
|
// Note that we're actually touching ONEDREALTEXTUREWIDTH + 1
|
|
// elements in the array here!
|
|
|
|
if(doGammaConversion)
|
|
{
|
|
GpColorConverter colorConv;
|
|
colorConv.argb = GammaUnlinearizePremultiplied128(color);
|
|
|
|
startTexelAgrb[i].A00aa00gg =
|
|
(colorConv.Channel.a << 16) | colorConv.Channel.g;
|
|
startTexelAgrb[i].A00rr00bb =
|
|
(colorConv.Channel.r << 16) | colorConv.Channel.b;
|
|
}
|
|
else
|
|
{
|
|
startTexelAgrb[i].A00aa00gg =
|
|
(GpRound(color.a) << 16) | GpRound(color.g);
|
|
startTexelAgrb[i].A00rr00bb =
|
|
(GpRound(color.r) << 16) | GpRound(color.b);
|
|
}
|
|
|
|
ASSERT((startTexelAgrb[i].A00aa00gg & 0xff00ff00) == 0);
|
|
ASSERT((startTexelAgrb[i].A00rr00bb & 0xff00ff00) == 0);
|
|
}
|
|
|
|
// Replicate the interval start colors to the end colors (note
|
|
// again that we actually reference ONEDREALTEXTUREWIDTH + 1
|
|
// elements):
|
|
|
|
for (i = 0; i < halfNumberOfTexels; i++)
|
|
{
|
|
endTexelArgb[i] = startTexelArgb[i + 1];
|
|
}
|
|
|
|
// Here's why we've only filled up half the texture so far.
|
|
// If FlipX is set, we make the second half an inverted
|
|
// copy of the first; if not, we make it a straight copy:
|
|
|
|
if ((gradient->GetWrapMode() != WrapModeTileFlipX) &&
|
|
(gradient->GetWrapMode() != WrapModeTileFlipXY))
|
|
{
|
|
memcpy(&startTexelArgb[halfNumberOfTexels],
|
|
&startTexelArgb[0],
|
|
halfNumberOfTexels * sizeof(startTexelArgb[0]));
|
|
memcpy(&endTexelArgb[halfNumberOfTexels],
|
|
&endTexelArgb[0],
|
|
halfNumberOfTexels * sizeof(endTexelArgb[0]));
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < halfNumberOfTexels; i++)
|
|
{
|
|
startTexelArgb[halfNumberOfTexels + i]
|
|
= endTexelArgb[halfNumberOfTexels - i - 1];
|
|
|
|
endTexelArgb[halfNumberOfTexels + i]
|
|
= startTexelArgb[halfNumberOfTexels - i - 1];
|
|
}
|
|
}
|
|
|
|
// We're done! We're set!
|
|
|
|
SetValid(TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Constructor for MMX linear gradient.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] brush - brush
|
|
* [IN] scan - the scan buffer
|
|
* [IN] context - the context
|
|
*
|
|
* Return Value:
|
|
*
|
|
* NONE
|
|
*
|
|
* Created:
|
|
*
|
|
* 1/13/2000 andrewgo
|
|
*
|
|
\**************************************************************************/
|
|
|
|
DpOutputLinearGradientSpan_MMX::DpOutputLinearGradientSpan_MMX(
|
|
const GpElementaryBrush *brush,
|
|
DpScanBuffer * scan,
|
|
DpContext* context
|
|
) : DpOutputLinearGradientSpan(brush, scan, context)
|
|
{
|
|
ASSERT(OSInfo::HasMMX);
|
|
|
|
// Here we do some additional stuff for our MMX routine, beyond
|
|
// what the base constructor did.
|
|
|
|
#if defined(_X86_)
|
|
|
|
UINT32 numberOfTexels = IntervalMask + 1;
|
|
ULONGLONG *startTexelArgb = &StartTexelArgb[0];
|
|
ULONGLONG *endTexelArgb = &EndTexelArgb[0];
|
|
static ULONGLONG OneHalf8dot8 = 0x0080008000800080;
|
|
|
|
// The C constructor creates the colors in AGRB order, but we
|
|
// want them in ARGB order, so swap R and G for every pixel:
|
|
|
|
USHORT *p = reinterpret_cast<USHORT*>(startTexelArgb);
|
|
for (UINT i = 0; i < numberOfTexels; i++, p += 4)
|
|
{
|
|
USHORT tmp = *(p + 1);
|
|
*(p + 1) = *(p + 2);
|
|
*(p + 2) = tmp;
|
|
}
|
|
|
|
p = reinterpret_cast<USHORT*>(endTexelArgb);
|
|
for (UINT i = 0; i < numberOfTexels; i++, p += 4)
|
|
{
|
|
USHORT tmp = *(p + 1);
|
|
*(p + 1) = *(p + 2);
|
|
*(p + 2) = tmp;
|
|
}
|
|
|
|
// Make some more adjustments for our MMX routine:
|
|
//
|
|
// EndTexelArgb[i] -= StartTexelArgb[i]
|
|
// StartTexelArgb[i] = 256 * StartTexelArgb[i] + OneHalf
|
|
|
|
_asm
|
|
{
|
|
mov ecx, numberOfTexels
|
|
mov esi, startTexelArgb
|
|
mov edi, endTexelArgb
|
|
|
|
MoreTexels:
|
|
movq mm0, [esi]
|
|
movq mm1, [edi]
|
|
psubw mm1, mm0
|
|
psllw mm0, 8
|
|
paddw mm0, OneHalf8dot8
|
|
movq [esi], mm0
|
|
movq [edi], mm1
|
|
add esi, 8
|
|
add edi, 8
|
|
dec ecx
|
|
jnz MoreTexels
|
|
|
|
emms
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Outputs a single span within a raster with a gradient brush.
|
|
* Uses linear interpolation from a small one dimensional texture
|
|
* that effectively creates a piecewise-linear approximation to
|
|
* the blend curve.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] y - the Y value of the raster being output
|
|
* [IN] leftEdge - the DDA class of the left edge
|
|
* [IN] rightEdge - the DDA class of the right edge
|
|
*
|
|
* Return Value:
|
|
*
|
|
* GpStatus - Ok
|
|
*
|
|
* Created:
|
|
*
|
|
* 1/13/2000 andrewgo
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
DpOutputLinearGradientSpan::OutputSpan(
|
|
INT y,
|
|
INT xMin,
|
|
INT xMax // xMax is exclusive
|
|
)
|
|
{
|
|
ASSERT((BrushType == BrushTypeLinearGradient) /*|| (BrushType == BrushRectGrad)*/);
|
|
ASSERT(xMax > xMin);
|
|
|
|
// Copy some class stuff to local variables for faster access in
|
|
// our inner loop:
|
|
|
|
INT32 xIncrement = XIncrement;
|
|
AGRB64TEXEL *startTexels = &StartTexelAgrb[0];
|
|
AGRB64TEXEL *endTexels = &EndTexelAgrb[0];
|
|
UINT32 count = xMax - xMin;
|
|
ARGB *buffer = Scan->NextBuffer(xMin, y, count);
|
|
UINT32 intervalMask = IntervalMask;
|
|
|
|
// Given our start point in device space, figure out the corresponding
|
|
// texture pixel. Note that this is expressed as a fixed-point number
|
|
// with FRACTIONBITS bits of fractional precision:
|
|
|
|
INT32 xTexture = (xMin * M11) + (y * M21) + Dx;
|
|
|
|
do {
|
|
// We want to linearly interpolate between two pixels,
|
|
// A and B (where A is the floor pixel, B the ceiling pixel).
|
|
// 'multA' is the fraction of pixel A that we want, and
|
|
// 'multB' is the fraction of pixel B that we want:
|
|
|
|
UINT32 multB = ONEDGETFRACTIONAL8BITS(xTexture);
|
|
UINT32 multA = 256 - multB;
|
|
|
|
// We could actually do a big lookup table right off of 'xTexture'
|
|
// for however many bits of precision we wanted to do. But that
|
|
// would be too much work in the setup.
|
|
|
|
UINT32 iTexture = ONEDGETINTEGERBITS(xTexture) & intervalMask;
|
|
|
|
AGRB64TEXEL *startTexel = &startTexels[iTexture];
|
|
AGRB64TEXEL *endTexel = &endTexels[iTexture];
|
|
|
|
// Note that we can gamma correct the texels so that we don't
|
|
// have to do gamma correction here. The addition of constants
|
|
// here are to accomplish rounding:
|
|
|
|
UINT32 rrrrbbbb = (startTexel->A00rr00bb * multA)
|
|
+ (endTexel->A00rr00bb * multB)
|
|
+ 0x00800080;
|
|
|
|
UINT32 aaaagggg = (startTexel->A00aa00gg * multA)
|
|
+ (endTexel->A00aa00gg * multB)
|
|
+ 0x00800080;
|
|
|
|
*buffer = (aaaagggg & 0xff00ff00) + ((rrrrbbbb & 0xff00ff00) >> 8);
|
|
|
|
buffer++;
|
|
xTexture += xIncrement;
|
|
|
|
} while (--count != 0);
|
|
|
|
return Ok;
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Outputs a single span within a raster with a gradient brush.
|
|
*
|
|
* Created:
|
|
*
|
|
* 03/16/2000 andrewgo
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
DpOutputLinearGradientSpan_MMX::OutputSpan(
|
|
INT y,
|
|
INT xMin,
|
|
INT xMax // xMax is exclusive
|
|
)
|
|
{
|
|
ASSERT((BrushType == BrushTypeLinearGradient) /*|| (BrushType == BrushRectGrad)*/);
|
|
|
|
#if defined(_X86_)
|
|
|
|
ASSERT(xMax > xMin);
|
|
|
|
// Copy some class stuff to local variables for faster access in
|
|
// our inner loop:
|
|
|
|
INT32 xIncrement = XIncrement;
|
|
AGRB64TEXEL *startTexels = &StartTexelAgrb[0];
|
|
AGRB64TEXEL *endTexels = &EndTexelAgrb[0];
|
|
UINT32 count = xMax - xMin;
|
|
ARGB *buffer = Scan->NextBuffer(xMin, y, count);
|
|
|
|
// Given our start point in device space, figure out the corresponding
|
|
// texture pixel. Note that this is expressed as a fixed-point number
|
|
// with FRACTIONBITS bits of fractional precision:
|
|
|
|
INT32 xTexture = (xMin * M11) + (y * M21) + Dx;
|
|
|
|
// Scale up the interval count to the MSB so that we don't have to do
|
|
// a mask in the inner loop, which assumes 16.16 for the fixed point
|
|
// representation.
|
|
|
|
UINT32 downshiftAmount = 32 - NumberOfIntervalBits;
|
|
UINT32 upshiftAmount = 16 - NumberOfIntervalBits;
|
|
UINT32 intervalCounter = xTexture << upshiftAmount;
|
|
UINT32 intervalIncrement = xIncrement << upshiftAmount;
|
|
|
|
// Prepare for the three stages:
|
|
// stage1: QWORD align the destination
|
|
// stage2: process 2 pixels at a time
|
|
// stage3: process the last pixel if present
|
|
|
|
UINT32 stage1_count = 0, stage2_count = 0, stage3_count = 0;
|
|
if (count > 0)
|
|
{
|
|
// If destination is not QWORD aligned, process the first pixel
|
|
// in stage 1.
|
|
|
|
if (((UINT_PTR) buffer) & 0x4)
|
|
{
|
|
stage1_count = 1;
|
|
count--;
|
|
}
|
|
|
|
stage2_count = count >> 1;
|
|
stage3_count = count - 2 * stage2_count;
|
|
|
|
_asm
|
|
{
|
|
// eax = pointer to interval-start array
|
|
// ebx = pointer to interval-end array
|
|
// ecx = shift count
|
|
// edx = scratch
|
|
// esi = count
|
|
// edi = destination
|
|
// mm0 = interval counter
|
|
// mm1 = interval incrementer
|
|
// mm2 = fractional counter
|
|
// mm3 = fractional incrementer
|
|
// mm4 = temp
|
|
// mm5 = temp
|
|
|
|
dec stage1_count
|
|
|
|
mov eax, startTexels
|
|
mov ebx, endTexels
|
|
mov ecx, downshiftAmount
|
|
mov esi, stage2_count
|
|
mov edi, buffer
|
|
movd mm0, intervalCounter
|
|
movd mm1, intervalIncrement
|
|
|
|
movd mm2, xTexture // 0 | 0 | 0 | 0 || x | x | mult | lo
|
|
movd mm3, xIncrement
|
|
punpcklwd mm2, mm2 // 0 | x | 0 | x || mult | lo | mult | lo
|
|
punpcklwd mm3, mm3
|
|
punpckldq mm2, mm2 // mult | lo | mult | lo || mult | lo | mult | lo
|
|
punpckldq mm3, mm3
|
|
|
|
// This preparation normally happens inside the loop:
|
|
|
|
movq mm4, mm2 // mult | x | mult | x || mult | x | mult | x
|
|
movd edx, mm0
|
|
|
|
jnz pre_stage2_loop // the flags for this are set in the "dec stage1_count" above
|
|
|
|
// stage1_loop:
|
|
|
|
psrlw mm4, 8 // 0 | mult | 0 | mult || 0 | mult | 0 | mult
|
|
shr edx, cl
|
|
|
|
pmullw mm4, [ebx + edx*8]
|
|
paddd mm0, mm1 // interval counter += interval increment
|
|
|
|
add edi, 4 // buffer++
|
|
|
|
paddw mm4, [eax + edx*8]
|
|
movd edx, mm0 // Prepare for next iteration
|
|
|
|
paddw mm2, mm3 // fractional counter += fractional increment
|
|
|
|
psrlw mm4, 8 // 0 | a | 0 | r || 0 | g | 0 | b
|
|
|
|
packuswb mm4, mm4 // a | r | g | b || a | r | g | b
|
|
|
|
movd [edi - 4], mm4
|
|
movq mm4, mm2 // Prepare for next iteration
|
|
|
|
pre_stage2_loop:
|
|
|
|
cmp esi, 0
|
|
jz stage3_loop // Do we need to execute the stage2_loop?
|
|
|
|
stage2_loop:
|
|
|
|
psrlw mm4, 8 // 0 | mult | 0 | mult || 0 | mult | 0 | mult
|
|
shr edx, cl
|
|
|
|
paddd mm0, mm1 // interval counter += interval increment
|
|
pmullw mm4, [ebx + edx*8]
|
|
|
|
add edi, 8 // buffer++
|
|
|
|
paddw mm2, mm3 // fractional counter += fractional increment
|
|
|
|
paddw mm4, [eax + edx*8]
|
|
movd edx, mm0 // Prepare for next iteration
|
|
|
|
shr edx, cl
|
|
|
|
movq mm5, mm2 // Prepare for next iteration
|
|
|
|
|
|
psrlw mm5, 8 // 0 | mult | 0 | mult || 0 | mult | 0 | mult
|
|
|
|
psrlw mm4, 8 // 0 | a | 0 | r || 0 | g | 0 | b
|
|
paddd mm0, mm1 // interval counter += interval increment
|
|
pmullw mm5, [ebx + edx*8]
|
|
dec esi // count--
|
|
|
|
paddw mm5, [eax + edx*8]
|
|
movd edx, mm0 // Prepare for next iteration
|
|
|
|
paddw mm2, mm3 // fractional counter += fractional increment
|
|
|
|
psrlw mm5, 8 // 0 | a | 0 | r || 0 | g | 0 | b
|
|
|
|
packuswb mm4, mm5
|
|
|
|
movq [edi - 8], mm4
|
|
movq mm4, mm2 // Prepare for next iteration
|
|
jnz stage2_loop
|
|
|
|
stage3_loop:
|
|
|
|
dec stage3_count
|
|
jnz skip_stage3_loop
|
|
|
|
psrlw mm4, 8 // 0 | mult | 0 | mult || 0 | mult | 0 | mult
|
|
shr edx, cl
|
|
|
|
pmullw mm4, [ebx + edx*8]
|
|
paddd mm0, mm1 // interval counter += interval increment
|
|
|
|
paddw mm4, [eax + edx*8]
|
|
movd edx, mm0 // Prepare for next iteration
|
|
|
|
paddw mm2, mm3 // fractional counter += fractional increment
|
|
|
|
psrlw mm4, 8 // 0 | a | 0 | r || 0 | g | 0 | b
|
|
|
|
packuswb mm4, mm4 // a | r | g | b || a | r | g | b
|
|
|
|
movd [edi], mm4
|
|
|
|
skip_stage3_loop:
|
|
|
|
emms
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
return(Ok);
|
|
}
|
|
|
|
DpOutputPathGradientSpan::DpOutputPathGradientSpan(
|
|
const GpElementaryBrush *brush,
|
|
DpScanBuffer * scan,
|
|
DpContext* context
|
|
) : DpOutputGradientSpan(brush, scan, context)
|
|
{
|
|
SetValid(FALSE);
|
|
const GpPathGradient* pathBrush
|
|
= static_cast<const GpPathGradient*> (brush);
|
|
|
|
if(pathBrush->DeviceBrush.Path)
|
|
pathBrush->Flatten(&WorldToDevice);
|
|
|
|
Count = pathBrush->GetNumberOfPoints();
|
|
PointF pt0, pt1, pt2;
|
|
pathBrush->GetCenterPoint(&pt0);
|
|
WorldToDevice.Transform(&pt0);
|
|
|
|
GpColor c0, c1, c2;
|
|
pathBrush->GetCenterColor(&c0);
|
|
|
|
Triangles = (DpTriangleData**) GpMalloc(Count*sizeof(DpTriangleData*));
|
|
|
|
BOOL isPolygonMode = TRUE;
|
|
|
|
if(Triangles)
|
|
{
|
|
GpMemset(Triangles, 0, Count*sizeof(DpTriangleData*));
|
|
|
|
INT j;
|
|
for(INT i = 0; i < Count; i++)
|
|
{
|
|
if(i < Count - 1)
|
|
j = i + 1;
|
|
else
|
|
j = 0;
|
|
|
|
pathBrush->GetPoint(&pt1, i);
|
|
pathBrush->GetPoint(&pt2, j);
|
|
|
|
if(pt1.X != pt2.X || pt1.Y != pt2.Y)
|
|
{
|
|
DpTriangleData* tri = new DpTriangleData();
|
|
pathBrush->GetSurroundColor(&c1, i);
|
|
pathBrush->GetSurroundColor(&c2, j);
|
|
|
|
// Transform points if they have not been flattened,
|
|
// since OutputSpan gets span data as Device units and
|
|
// the BLTransform must be in the same coordinate space.
|
|
|
|
if (pathBrush->FlattenPoints.GetCount() == 0)
|
|
{
|
|
WorldToDevice.Transform(&pt1);
|
|
WorldToDevice.Transform(&pt2);
|
|
}
|
|
|
|
tri->SetTriangle(
|
|
pt0, pt1, pt2,
|
|
c0, c1, c2,
|
|
isPolygonMode,
|
|
brush->GetGammaCorrection()
|
|
);
|
|
|
|
// Set the blend factors.
|
|
tri->Falloff0 = pathBrush->DeviceBrush.Falloffs[0];
|
|
tri->Falloff1 = 1;
|
|
tri->Falloff2 = 1;
|
|
tri->BlendCount0 = pathBrush->DeviceBrush.BlendCounts[0];
|
|
tri->BlendCount1 = 1;
|
|
tri->BlendCount2 = 1;
|
|
tri->BlendFactors0 = pathBrush->DeviceBrush.BlendFactors[0];
|
|
tri->BlendFactors1 = NULL;
|
|
tri->BlendFactors2 = NULL;
|
|
tri->BlendPositions0 = pathBrush->DeviceBrush.BlendPositions[0];
|
|
tri->BlendPositions1 = NULL;
|
|
tri->BlendPositions2 = NULL;
|
|
tri->PresetColors = pathBrush->DeviceBrush.PresetColors;
|
|
tri->UsesPresetColors = pathBrush->DeviceBrush.UsesPresetColors;
|
|
|
|
Triangles[i] = tri;
|
|
}
|
|
else
|
|
Triangles[i] = NULL;
|
|
}
|
|
SetValid(TRUE);
|
|
}
|
|
else
|
|
SetValid(FALSE);
|
|
}
|
|
|
|
DpOutputPathGradientSpan::~DpOutputPathGradientSpan(
|
|
VOID
|
|
)
|
|
{
|
|
FreeData();
|
|
}
|
|
|
|
VOID
|
|
DpOutputPathGradientSpan::FreeData()
|
|
{
|
|
if(Triangles) {
|
|
for(INT i = 0; i < Count; i++)
|
|
{
|
|
DpTriangleData* tri = Triangles[i];
|
|
delete tri;
|
|
Triangles[i] = NULL;
|
|
}
|
|
|
|
GpFree(Triangles);
|
|
Triangles = NULL;
|
|
}
|
|
|
|
SetValid(FALSE);
|
|
}
|
|
|
|
/**************************************************************************\
|
|
*
|
|
* Function Description:
|
|
*
|
|
* Outputs a single span within a raster with a polygon gradient brush.
|
|
* Is called by the rasterizer.
|
|
*
|
|
* Arguments:
|
|
*
|
|
* [IN] y - the Y value of the raster being output
|
|
* [IN] leftEdge - the DDA class of the left edge
|
|
* [IN] rightEdge - the DDA class of the right edge
|
|
*
|
|
* Return Value:
|
|
*
|
|
* GpStatus - Ok
|
|
*
|
|
* Created:
|
|
*
|
|
* 03/24/1999 ikkof
|
|
*
|
|
\**************************************************************************/
|
|
|
|
GpStatus
|
|
DpOutputPathGradientSpan::OutputSpan(
|
|
INT y,
|
|
INT xMin,
|
|
INT xMax // xMax is exclusive
|
|
)
|
|
{
|
|
if(!IsValid())
|
|
return Ok;
|
|
|
|
INT xxMin = xMax, xxMax = xMin;
|
|
INT iFirst = 0x7FFFFFFF, iLast = 0;
|
|
|
|
for(INT i = 0; i < Count; i++)
|
|
{
|
|
DpTriangleData* triangle = Triangles[i];
|
|
REAL xx[2];
|
|
|
|
if(triangle &&
|
|
triangle->SetXSpan((REAL) y, (REAL) xMin, (REAL) xMax, xx))
|
|
{
|
|
xxMin = min(xxMin, GpFloor(xx[0]));
|
|
xxMax = max(xxMax, GpRound(xx[1]));
|
|
iFirst = min(iFirst, i);
|
|
iLast = i;
|
|
}
|
|
}
|
|
|
|
// Don't attempt to fill outside the specified x bounds
|
|
xxMin = max(xxMin, xMin);
|
|
xxMax = min(xxMax, xMax);
|
|
|
|
INT width = xxMax - xxMin; // Right exclusive when filling
|
|
|
|
// No triangles intersect this scan line, so exit
|
|
if (width <= 0)
|
|
return Ok;
|
|
|
|
ARGB * buffer = Scan->NextBuffer(xxMin, y, width);
|
|
GpMemset(buffer, 0, width*sizeof(ARGB));
|
|
|
|
for(INT i = iFirst; i <= iLast; i++)
|
|
{
|
|
DpTriangleData* triangle = Triangles[i];
|
|
if(triangle)
|
|
triangle->OutputSpan(buffer, CompositingMode,
|
|
y, xxMin, xxMax);
|
|
}
|
|
|
|
return Ok;
|
|
}
|
|
|
|
DpOutputOneDPathGradientSpan::DpOutputOneDPathGradientSpan(
|
|
const GpElementaryBrush *brush,
|
|
DpScanBuffer * scan,
|
|
DpContext* context,
|
|
BOOL isHorizontal,
|
|
BOOL isVertical
|
|
) : DpOutputOneDGradientSpan(
|
|
brush,
|
|
scan,
|
|
context,
|
|
isHorizontal,
|
|
isVertical)
|
|
{
|
|
if(!IsValid())
|
|
return;
|
|
|
|
SetupPathGradientOneDData(brush->GetGammaCorrection());
|
|
|
|
const GpPathGradient* pathBrush
|
|
= static_cast<const GpPathGradient*> (brush);
|
|
|
|
if(pathBrush->DeviceBrush.Path)
|
|
pathBrush->Flatten(&WorldToDevice);
|
|
|
|
Count = pathBrush->GetNumberOfPoints();
|
|
PointF pt0, pt1, pt2;
|
|
pathBrush->GetCenterPoint(&pt0);
|
|
WorldToDevice.Transform(&pt0);
|
|
|
|
// Round center point to nearest 1/16 of a pixel, since
|
|
// this is the rasterizer resolution. This eliminates
|
|
// precision errors that can affect bilinear transforms.
|
|
pt0.X = FIX4TOREAL(GpRealToFix4(pt0.X));
|
|
pt0.Y = FIX4TOREAL(GpRealToFix4(pt0.Y));
|
|
|
|
REAL xScale = pathBrush->DeviceBrush.FocusScaleX;
|
|
REAL yScale = pathBrush->DeviceBrush.FocusScaleY;
|
|
|
|
REAL inflation;
|
|
pathBrush->GetInflationFactor(&inflation);
|
|
|
|
// If we have falloff values, then there are twice the number of
|
|
// transforms. If we are inflating the gradient outwards, then
|
|
// there are additional transforms also.
|
|
INT infCount;
|
|
INT blCount = Count;
|
|
if (xScale != 0 || yScale != 0)
|
|
{
|
|
Count *= 2;
|
|
infCount = Count;
|
|
}
|
|
else
|
|
{
|
|
infCount = blCount;
|
|
}
|
|
|
|
if (inflation > 1.0f)
|
|
{
|
|
Count += blCount;
|
|
}
|
|
|
|
BLTransforms = new GpBilinearTransform[Count];
|
|
|
|
GpPointF points[4];
|
|
GpRectF rect(0, 0, 1, 1);
|
|
|
|
if(BLTransforms)
|
|
{
|
|
INT j;
|
|
for(INT i = 0; i < blCount; i++)
|
|
{
|
|
if(i < blCount - 1)
|
|
j = i + 1;
|
|
else
|
|
j = 0;
|
|
|
|
pathBrush->GetPoint(&pt1, i);
|
|
pathBrush->GetPoint(&pt2, j);
|
|
|
|
if(pt1.X != pt2.X || pt1.Y != pt2.Y)
|
|
{
|
|
// Transform points if they have not been flattened,
|
|
// since OutputSpan gets span data as Device units and
|
|
// the BLTransform must be in the same coordinate space.
|
|
if (pathBrush->FlattenPoints.GetCount() == 0)
|
|
{
|
|
WorldToDevice.Transform(&pt1);
|
|
WorldToDevice.Transform(&pt2);
|
|
}
|
|
|
|
// Round points to nearest 1/16 of a pixel, since
|
|
// this is the rasterizer resolution. This eliminates
|
|
// precision errors that can affect bilinear transforms.
|
|
pt1.X = FIX4TOREAL(GpRealToFix4(pt1.X));
|
|
pt1.Y = FIX4TOREAL(GpRealToFix4(pt1.Y));
|
|
pt2.X = FIX4TOREAL(GpRealToFix4(pt2.X));
|
|
pt2.Y = FIX4TOREAL(GpRealToFix4(pt2.Y));
|
|
|
|
points[1] = pt1;
|
|
points[3] = pt2;
|
|
|
|
if (inflation > 1.0f)
|
|
{
|
|
// Create a quadralateral extension of the gradient away
|
|
// from the outer edge, and set the fixed value to 1.0, so
|
|
// this entire quadralateral will be filled with the edge
|
|
// color. This is useful in some printing cases.
|
|
points[0].X = pt0.X + inflation*(pt1.X - pt0.X);
|
|
points[0].Y = pt0.Y + inflation*(pt1.Y - pt0.Y);
|
|
points[2].X = pt0.X + inflation*(pt2.X - pt0.X);
|
|
points[2].Y = pt0.Y + inflation*(pt2.Y - pt0.Y);
|
|
|
|
GpPointF centerPoints[4];
|
|
GpRectF centerRect(0, 0, 1, 1);
|
|
|
|
centerPoints[0] = points[0];
|
|
centerPoints[1] = pt1;
|
|
centerPoints[2] = points[2];
|
|
centerPoints[3] = pt2;
|
|
|
|
BLTransforms[infCount+i].SetBilinearTransform(centerRect, ¢erPoints[0], 4, 1.0f);
|
|
}
|
|
|
|
if(xScale == 0 && yScale == 0)
|
|
{
|
|
points[0] = pt0;
|
|
points[2] = pt0;
|
|
}
|
|
else
|
|
{
|
|
// Set up an outer quadralateral for the gradient, plus an
|
|
// inner triangle for the single center gradient color.
|
|
points[0].X = pt0.X + xScale*(pt1.X - pt0.X);
|
|
points[0].Y = pt0.Y + yScale*(pt1.Y - pt0.Y);
|
|
points[2].X = pt0.X + xScale*(pt2.X - pt0.X);
|
|
points[2].Y = pt0.Y + yScale*(pt2.Y - pt0.Y);
|
|
|
|
GpPointF centerPoints[4];
|
|
GpRectF centerRect(0, 0, 1, 1);
|
|
|
|
centerPoints[0] = pt0;
|
|
centerPoints[1] = points[0];
|
|
centerPoints[2] = pt0;
|
|
centerPoints[3] = points[2];
|
|
// Set the fixed value to use for the inner triangular
|
|
// to 0, so that the inner gradient color will be used to
|
|
// fill this region.
|
|
BLTransforms[blCount+i].SetBilinearTransform(centerRect, ¢erPoints[0], 4, 0.0f);
|
|
}
|
|
|
|
BLTransforms[i].SetBilinearTransform(rect, &points[0], 4);
|
|
}
|
|
}
|
|
SetValid(TRUE);
|
|
}
|
|
else
|
|
SetValid(FALSE);
|
|
}
|
|
|
|
VOID
|
|
DpOutputOneDPathGradientSpan::SetupPathGradientOneDData(
|
|
BOOL gammaCorrect
|
|
)
|
|
{
|
|
REAL u, u0, du;
|
|
|
|
u0 = 0;
|
|
du = 1.0f/OneDDataMultiplier;
|
|
ARGB* buffer = OneDData;
|
|
|
|
ASSERT(buffer);
|
|
if(!buffer)
|
|
return;
|
|
|
|
const GpPathGradient* pathBrush
|
|
= static_cast<const GpPathGradient*> (Brush);
|
|
|
|
GpColor c0, c1;
|
|
pathBrush->GetCenterColor(&c0);
|
|
pathBrush->GetSurroundColor(&c1, 0);
|
|
|
|
GpFColor128 color[2];
|
|
|
|
GammaLinearizeAndPremultiply(
|
|
c0.GetValue(),
|
|
gammaCorrect,
|
|
&color[0]
|
|
);
|
|
|
|
GammaLinearizeAndPremultiply(
|
|
c1.GetValue(),
|
|
gammaCorrect,
|
|
&color[1]
|
|
);
|
|
|
|
for(INT i = 0; i < OneDDataCount; i++, buffer++)
|
|
{
|
|
u = u0;
|
|
REAL w = u;
|
|
|
|
GpFColor128 colorOut;
|
|
|
|
if(!(pathBrush->HasPresetColors() &&
|
|
pathBrush->DeviceBrush.PresetColors &&
|
|
pathBrush->DeviceBrush.BlendPositions[0] &&
|
|
pathBrush->DeviceBrush.BlendCounts[0] > 1))
|
|
{
|
|
w = ::adjustValue(
|
|
w,
|
|
pathBrush->DeviceBrush.BlendCounts[0],
|
|
pathBrush->DeviceBrush.Falloffs[0],
|
|
pathBrush->DeviceBrush.BlendFactors[0],
|
|
pathBrush->DeviceBrush.BlendPositions[0]
|
|
);
|
|
|
|
colorOut.a = color[0].a + w*(color[1].a - color[0].a);
|
|
colorOut.r = color[0].r + w*(color[1].r - color[0].r);
|
|
colorOut.g = color[0].g + w*(color[1].g - color[0].g);
|
|
colorOut.b = color[0].b + w*(color[1].b - color[0].b);
|
|
}
|
|
else
|
|
{
|
|
interpolatePresetColors(
|
|
&colorOut,
|
|
w,
|
|
pathBrush->DeviceBrush.BlendCounts[0],
|
|
pathBrush->DeviceBrush.PresetColors,
|
|
pathBrush->DeviceBrush.BlendPositions[0],
|
|
gammaCorrect
|
|
);
|
|
}
|
|
|
|
if( (REALABS(colorOut.a) >= REAL_EPSILON) ||
|
|
(CompositingMode == CompositingModeSourceCopy) )
|
|
{
|
|
GpColorConverter colorConv;
|
|
|
|
// Make sure the colorOut is properly premultiplied.
|
|
|
|
CLAMP_COLOR_CHANNEL(colorOut.a, 255.0f)
|
|
CLAMP_COLOR_CHANNEL(colorOut.r, colorOut.a);
|
|
CLAMP_COLOR_CHANNEL(colorOut.g, colorOut.a);
|
|
CLAMP_COLOR_CHANNEL(colorOut.b, colorOut.a);
|
|
|
|
if(gammaCorrect)
|
|
{
|
|
colorConv.argb = GammaUnlinearizePremultiplied128(colorOut);
|
|
}
|
|
else
|
|
{
|
|
colorConv.Channel.a = static_cast<BYTE>(GpRound(colorOut.a));
|
|
colorConv.Channel.r = static_cast<BYTE>(GpRound(colorOut.r));
|
|
colorConv.Channel.g = static_cast<BYTE>(GpRound(colorOut.g));
|
|
colorConv.Channel.b = static_cast<BYTE>(GpRound(colorOut.b));
|
|
}
|
|
|
|
// Clamp to the alpha channel for the premultiplied alpha blender.
|
|
|
|
*buffer = colorConv.argb;
|
|
}
|
|
else
|
|
{
|
|
*buffer = 0; // case of CompositingModeSourceOver && alpha = 0
|
|
}
|
|
|
|
u0 += du;
|
|
}
|
|
}
|
|
|
|
GpStatus
|
|
DpOutputOneDPathGradientSpan::OutputSpan(
|
|
INT y,
|
|
INT xMin,
|
|
INT xMax // xMax is exclusive
|
|
)
|
|
{
|
|
FPUStateSaver::AssertMode();
|
|
|
|
if(!IsValid())
|
|
{
|
|
return Ok;
|
|
}
|
|
|
|
INT width = xMax - xMin;
|
|
ARGB * buffer = Scan->NextBuffer(xMin, y, width);
|
|
|
|
GpMemset(buffer, 0, width*sizeof(ARGB));
|
|
|
|
REAL* u = (REAL*) GpMalloc(2*width*sizeof(REAL));
|
|
REAL* v = u + width;
|
|
|
|
if(u == NULL)
|
|
{
|
|
GpFree(u);
|
|
|
|
return OutOfMemory;
|
|
}
|
|
|
|
for(INT i = 0; i < Count; i++)
|
|
{
|
|
INT pairCount;
|
|
INT xSpans[4];
|
|
|
|
pairCount = BLTransforms[i].GetSourceParameterArrays(
|
|
u, v, &xSpans[0], y, xMin, xMax);
|
|
if(pairCount > 0)
|
|
{
|
|
REAL* u1 = u;
|
|
|
|
for(INT k = 0; k < pairCount; k++)
|
|
{
|
|
ARGB* buffer1 = buffer + xSpans[2*k] - xMin;
|
|
INT width = xSpans[2*k + 1] - xSpans[2*k];
|
|
for(INT j = 0; j < width; j++)
|
|
{
|
|
REAL u2 = *u1;
|
|
if(u2 < 0)
|
|
u2 = 0;
|
|
*buffer1++ = OneDData[GpRound(OneDDataMultiplier*u2)];
|
|
u1++;
|
|
}
|
|
u1 = u + width;
|
|
}
|
|
}
|
|
}
|
|
|
|
GpFree(u);
|
|
|
|
return Ok;
|
|
}
|
|
|
|
#undef CLAMP_COLOR_CHANNEL
|
|
|