920fddd2489349257771580dc7eb4002186b8b4c
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / controls / renderers / gradient / gradient.cpp
1 /*
2  * Copyright (c) 2015 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17
18 #include "gradient.h"
19
20 #include <algorithm>    // std::sort
21 #include <dali/public-api/math/vector4.h>
22
23 namespace
24 {
25 // The maximum width of the lookup texture ( it is a 1-dimension texture with the height as 1 )
26 const unsigned int MAXIMUM_TEXTURE_RESOLUTION(128u);
27 }
28
29 namespace Dali
30 {
31
32 namespace Toolkit
33 {
34
35 namespace Internal
36 {
37
38 Gradient::Gradient()
39 : mGradientUnits( OBJECT_BOUNDING_BOX ),
40   mSpreadMethod( PAD )
41 {}
42
43 Gradient::~Gradient()
44 {}
45
46 void Gradient::AddStop( float offset, const Vector4& color )
47 {
48   // the offset is clamped to the range [0.0, 1.0]
49   mGradientStops.PushBack( GradientStop( Clamp( offset, 0.f, 1.f ), color) );
50 }
51
52 void Gradient::SetGradientUnits( GradientUnits gradientUnits )
53 {
54   mGradientUnits = gradientUnits;
55 }
56
57 Gradient::GradientUnits Gradient::GetGradientUnits() const
58 {
59   return mGradientUnits;
60 }
61
62 void Gradient::SetSpreadMethod( SpreadMethod spread )
63 {
64   mSpreadMethod = spread;
65 }
66
67 Gradient::SpreadMethod Gradient::GetSpreadMethod() const
68 {
69   return mSpreadMethod;
70 }
71
72 const Matrix3& Gradient::GetAlignmentTransform() const
73 {
74   return mAlignmentTransform;
75 }
76
77 /**
78  * Following the SVG gradient.
79  *
80  * Not only the spread method decides the texture wrap mode:
81  *    PAD-->GL_CLAMP_TO_EDGE; REPEAT-->GL_REPEAT; REFLECT-->GL_MIRROR_REPEAT
82  *
83  *  If the stops have not covered the whole zero to one range,
84  *  the REPEAT spread behaves different from the two others in the lookup texture generation.
85  */
86 BufferImage Gradient::GenerateLookupTexture()
87 {
88   std::sort( mGradientStops.Begin(), mGradientStops.End() );
89
90   unsigned int numStops = mGradientStops.Count();
91
92   /**
93    * If the stops have not covered the whole zero to one range,
94    * for PAD and REFLECT, use the color of the first stop to fill the range  [0.0, first stop offset)
95    *                  and use the color of the last stop to fill the range (last stop offset, 1.0]
96    * for REPEAT, mix the two color of the first and last stop to fill the remainder
97    */
98   bool tempFirstStop = false;
99   if( mGradientStops[0].mOffset > 0.f )
100   {
101     tempFirstStop = true;
102     Vector4 firstStopColor( mGradientStops[0].mStopColor ); // If spread method is PAD or REFLECT
103     if( mSpreadMethod == REPEAT )
104     {
105       firstStopColor = ( mGradientStops[0].mStopColor * (1.f-mGradientStops[numStops-1].mOffset)
106                        + mGradientStops[numStops-1].mStopColor  * mGradientStops[0].mOffset )
107                    / ( mGradientStops[0].mOffset+1.f-mGradientStops[numStops-1].mOffset);
108     }
109
110     mGradientStops.Insert( mGradientStops.Begin(), GradientStop(0.f, firstStopColor) );
111     numStops++;
112   }
113
114   bool tempLastStop = false;
115   if( mGradientStops[numStops-1].mOffset < 1.f )
116   {
117     tempLastStop = true;
118     Vector4 lastStopColor( mGradientStops[numStops-1].mStopColor ); // If spread method is PAD or REFLECT
119     if( mSpreadMethod == REPEAT )
120     {
121       lastStopColor = mGradientStops[0].mStopColor;
122     }
123     mGradientStops.PushBack( GradientStop(1.f, lastStopColor) );
124     numStops++;
125   }
126
127   /**
128    * Generate the pixels with the color transit from one stop to next.
129    */
130   unsigned int resolution = EstimateTextureResolution();
131   BufferImage texture = BufferImage::New( resolution, 1 );
132   PixelBuffer* pixels = texture.GetBuffer();
133   int segmentStart = 0;
134   int segmentEnd = 0;
135   int k = 0;
136   float length = static_cast<float>(resolution);
137   for( unsigned int i=0; i<numStops-1u; i++ )
138   {
139     segmentEnd = floorf(mGradientStops[i+1].mOffset * length + 0.5f);
140     if( segmentEnd == segmentStart )
141     {
142       continue;
143     }
144     float segmentWidth = static_cast<float>(segmentEnd-segmentStart);
145
146     for( int j = segmentStart; j<segmentEnd; j++ )
147     {
148       float ratio = static_cast<float>(j-segmentStart)/segmentWidth;
149       Vector4 currentColor = mGradientStops[i].mStopColor * (1.f-ratio) + mGradientStops[i+1].mStopColor * ratio;
150       pixels[k*4] = static_cast<unsigned char>( 255.f * Clamp( currentColor.r, 0.f, 1.f ) );
151       pixels[k*4+1] = static_cast<unsigned char>( 255.f * Clamp( currentColor.g, 0.f, 1.f ) );
152       pixels[k*4+2] = static_cast<unsigned char>( 255.f * Clamp( currentColor.b, 0.f, 1.f ) );
153       pixels[k*4+3] = static_cast<unsigned char>( 255.f * Clamp( currentColor.a, 0.f, 1.f ) );
154       k++;
155     }
156     segmentStart = segmentEnd;
157   }
158
159   // remove the stops added temporarily for generating the pixels, as the spread method might get changed later
160   if( tempLastStop )
161   {
162     mGradientStops.Erase( mGradientStops.Begin()+numStops-1 );
163   }
164   if( tempFirstStop )
165   {
166     mGradientStops.Erase( mGradientStops.Begin());
167   }
168
169   return texture;
170 }
171
172 unsigned int Gradient::EstimateTextureResolution()
173 {
174   float minInterval = 1.0;
175   for( unsigned int i=0, numStops = mGradientStops.Count(); i<numStops-1u; i++ )
176   {
177     float interval = mGradientStops[i+1].mOffset - mGradientStops[i].mOffset;
178     minInterval = interval > minInterval ? minInterval:interval;
179   }
180   // Use at least three pixels for each segment between two stops
181   unsigned int resolution = static_cast<int>(3.f/(minInterval+Math::MACHINE_EPSILON_100)+0.5f);
182   // Clamp the resolution to handle the overlapping stops
183   if( resolution > MAXIMUM_TEXTURE_RESOLUTION )
184   {
185     return MAXIMUM_TEXTURE_RESOLUTION;
186   }
187
188   return resolution;
189 }
190
191 } // namespace Internal
192
193 } // namespace Toolkit
194
195 } // namespace Dali