e070754c6cfbe8118102e94483bb9b3dfc2dacff
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / controls / renderers / gradient / gradient.cpp
1 /*
2  * Copyright (c) 2016 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 const Vector<Gradient::GradientStop>& Gradient::GetStops()
53 {
54   return mGradientStops;
55 }
56
57 void Gradient::SetGradientUnits( GradientUnits gradientUnits )
58 {
59   mGradientUnits = gradientUnits;
60 }
61
62 Gradient::GradientUnits Gradient::GetGradientUnits() const
63 {
64   return mGradientUnits;
65 }
66
67 void Gradient::SetSpreadMethod( SpreadMethod spread )
68 {
69   mSpreadMethod = spread;
70 }
71
72 Gradient::SpreadMethod Gradient::GetSpreadMethod() const
73 {
74   return mSpreadMethod;
75 }
76
77 const Matrix3& Gradient::GetAlignmentTransform() const
78 {
79   return mAlignmentTransform;
80 }
81
82 /**
83  * Following the SVG gradient.
84  *
85  * Not only the spread method decides the texture wrap mode:
86  *    PAD-->GL_CLAMP_TO_EDGE; REPEAT-->GL_REPEAT; REFLECT-->GL_MIRROR_REPEAT
87  *
88  *  If the stops have not covered the whole zero to one range,
89  *  the REPEAT spread behaves different from the two others in the lookup texture generation.
90  */
91 Dali::Texture Gradient::GenerateLookupTexture()
92 {
93   std::sort( mGradientStops.Begin(), mGradientStops.End() );
94
95   unsigned int numStops = mGradientStops.Count();
96
97   /**
98    * If the stops have not covered the whole zero to one range,
99    * for PAD and REFLECT, use the color of the first stop to fill the range  [0.0, first stop offset)
100    *                  and use the color of the last stop to fill the range (last stop offset, 1.0]
101    * for REPEAT, mix the two color of the first and last stop to fill the remainder
102    */
103   bool tempFirstStop = false;
104   if( mGradientStops[0].mOffset > 0.f )
105   {
106     tempFirstStop = true;
107     Vector4 firstStopColor( mGradientStops[0].mStopColor ); // If spread method is PAD or REFLECT
108     if( mSpreadMethod == REPEAT )
109     {
110       firstStopColor = ( mGradientStops[0].mStopColor * (1.f-mGradientStops[numStops-1].mOffset)
111                        + mGradientStops[numStops-1].mStopColor  * mGradientStops[0].mOffset )
112                    / ( mGradientStops[0].mOffset+1.f-mGradientStops[numStops-1].mOffset);
113     }
114
115     mGradientStops.Insert( mGradientStops.Begin(), GradientStop(0.f, firstStopColor) );
116     numStops++;
117   }
118
119   bool tempLastStop = false;
120   if( mGradientStops[numStops-1].mOffset < 1.f )
121   {
122     tempLastStop = true;
123     Vector4 lastStopColor( mGradientStops[numStops-1].mStopColor ); // If spread method is PAD or REFLECT
124     if( mSpreadMethod == REPEAT )
125     {
126       lastStopColor = mGradientStops[0].mStopColor;
127     }
128     mGradientStops.PushBack( GradientStop(1.f, lastStopColor) );
129     numStops++;
130   }
131
132   /**
133    * Generate the pixels with the color transit from one stop to next.
134    */
135   unsigned int resolution = EstimateTextureResolution();
136
137   unsigned int bufferSize = resolution * 4u;
138   unsigned char* pixels = new unsigned char[ bufferSize ];
139   PixelData pixelData = PixelData::New( pixels, bufferSize, resolution, 1u, Pixel::RGBA8888, PixelData::DELETE_ARRAY );
140
141   int segmentStart = 0;
142   int segmentEnd = 0;
143   int k = 0;
144   float length = static_cast<float>(resolution);
145   for( unsigned int i=0; i<numStops-1u; i++ )
146   {
147     segmentEnd = floorf(mGradientStops[i+1].mOffset * length + 0.5f);
148     if( segmentEnd == segmentStart )
149     {
150       continue;
151     }
152     float segmentWidth = static_cast<float>(segmentEnd-segmentStart);
153
154     for( int j = segmentStart; j<segmentEnd; j++ )
155     {
156       float ratio = static_cast<float>(j-segmentStart)/segmentWidth;
157       Vector4 currentColor = mGradientStops[i].mStopColor * (1.f-ratio) + mGradientStops[i+1].mStopColor * ratio;
158       pixels[k*4] = static_cast<unsigned char>( 255.f * Clamp( currentColor.r, 0.f, 1.f ) );
159       pixels[k*4+1] = static_cast<unsigned char>( 255.f * Clamp( currentColor.g, 0.f, 1.f ) );
160       pixels[k*4+2] = static_cast<unsigned char>( 255.f * Clamp( currentColor.b, 0.f, 1.f ) );
161       pixels[k*4+3] = static_cast<unsigned char>( 255.f * Clamp( currentColor.a, 0.f, 1.f ) );
162       k++;
163     }
164     segmentStart = segmentEnd;
165   }
166
167   Texture texture = Texture::New( TextureType::TEXTURE_2D, Pixel::RGBA8888, resolution, 1u );
168   texture.Upload( pixelData );
169
170   // remove the stops added temporarily for generating the pixels, as the spread method might get changed later
171   if( tempLastStop )
172   {
173     mGradientStops.Erase( mGradientStops.Begin()+numStops-1 );
174   }
175   if( tempFirstStop )
176   {
177     mGradientStops.Erase( mGradientStops.Begin());
178   }
179
180   return texture;
181 }
182
183 unsigned int Gradient::EstimateTextureResolution()
184 {
185   float minInterval = 1.0;
186   for( unsigned int i=0, numStops = mGradientStops.Count(); i<numStops-1u; i++ )
187   {
188     float interval = mGradientStops[i+1].mOffset - mGradientStops[i].mOffset;
189     minInterval = interval > minInterval ? minInterval:interval;
190   }
191   // Use at least three pixels for each segment between two stops
192   unsigned int resolution = static_cast<int>(3.f/(minInterval+Math::MACHINE_EPSILON_100)+0.5f);
193   // Clamp the resolution to handle the overlapping stops
194   if( resolution > MAXIMUM_TEXTURE_RESOLUTION )
195   {
196     return MAXIMUM_TEXTURE_RESOLUTION;
197   }
198
199   return resolution;
200 }
201
202 } // namespace Internal
203
204 } // namespace Toolkit
205
206 } // namespace Dali