[dali_2.3.21] Merge branch 'devel/master'
[platform/core/uifw/dali-toolkit.git] / dali-toolkit / internal / visuals / gradient / gradient.cpp
1 /*
2  * Copyright (c) 2024 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   uint32_t numStops = mGradientStops.Count();
95   DALI_ASSERT_ALWAYS(numStops > 0u && "The number of gradient stop should not be zero!");
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 == Toolkit::GradientVisual::SpreadMethod::REPEAT)
109     {
110       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);
111     }
112
113     mGradientStops.Insert(mGradientStops.Begin(), GradientStop(0.f, firstStopColor));
114     numStops++;
115   }
116
117   bool tempLastStop = false;
118   if(mGradientStops[numStops - 1].mOffset < 1.f)
119   {
120     tempLastStop = true;
121     Vector4 lastStopColor(mGradientStops[numStops - 1].mStopColor); // If spread method is PAD or REFLECT
122     if(mSpreadMethod == Toolkit::GradientVisual::SpreadMethod::REPEAT)
123     {
124       lastStopColor = mGradientStops[0].mStopColor;
125     }
126     mGradientStops.PushBack(GradientStop(1.f, lastStopColor));
127     numStops++;
128   }
129
130   /**
131    * Generate the pixels with the color transit from one stop to next.
132    */
133   unsigned int resolution = EstimateTextureResolution();
134
135   unsigned int   bufferSize = resolution * 4u;
136   unsigned char* pixels     = new unsigned char[bufferSize];
137   PixelData      pixelData  = PixelData::New(pixels, bufferSize, resolution, 1u, Pixel::RGBA8888, PixelData::DELETE_ARRAY);
138
139   int   segmentStart = 0;
140   int   segmentEnd   = 0;
141   int   k            = 0;
142   float length       = static_cast<float>(resolution);
143   for(unsigned int i = 0; i < numStops - 1u; i++)
144   {
145     segmentEnd = floorf(mGradientStops[i + 1].mOffset * length + 0.5f);
146     if(segmentEnd == segmentStart)
147     {
148       continue;
149     }
150     float segmentWidth = static_cast<float>(segmentEnd - segmentStart);
151
152     for(int j = segmentStart; j < segmentEnd; j++)
153     {
154       float   ratio        = static_cast<float>(j - segmentStart) / (segmentWidth - 1);
155       Vector4 currentColor = mGradientStops[i].mStopColor * (1.f - ratio) + mGradientStops[i + 1].mStopColor * ratio;
156       pixels[k * 4]        = static_cast<unsigned char>(255.f * Clamp(currentColor.r, 0.f, 1.f));
157       pixels[k * 4 + 1]    = static_cast<unsigned char>(255.f * Clamp(currentColor.g, 0.f, 1.f));
158       pixels[k * 4 + 2]    = static_cast<unsigned char>(255.f * Clamp(currentColor.b, 0.f, 1.f));
159       pixels[k * 4 + 3]    = static_cast<unsigned char>(255.f * Clamp(currentColor.a, 0.f, 1.f));
160       k++;
161     }
162     segmentStart = segmentEnd;
163   }
164
165   Texture texture = Texture::New(TextureType::TEXTURE_2D, Pixel::RGBA8888, resolution, 1u);
166   texture.Upload(pixelData);
167
168   // remove the stops added temporarily for generating the pixels, as the spread method might get changed later
169   if(tempLastStop)
170   {
171     mGradientStops.Erase(mGradientStops.Begin() + numStops - 1);
172   }
173   if(tempFirstStop)
174   {
175     mGradientStops.Erase(mGradientStops.Begin());
176   }
177
178   return texture;
179 }
180
181 unsigned int Gradient::EstimateTextureResolution()
182 {
183   float          minInterval = 1.0;
184   const uint32_t numStops    = mGradientStops.Count();
185   DALI_ASSERT_ALWAYS(numStops > 0u && "The number of gradient stop should not be zero!");
186   for(uint32_t i = 0; 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