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