[dali_2.3.21] Merge branch 'devel/master'
[platform/core/uifw/dali-toolkit.git] / dali-scene3d / public-api / loader / environment-map-loader.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 // FILE HEADER
19 #include <dali-scene3d/public-api/loader/environment-map-loader.h>
20
21 // EXTERNAL INCLUDES
22 #include <dali/integration-api/pixel-data-integ.h>
23 #include <string.h>
24 #include <cmath>
25 #include <filesystem>
26
27 // INTERNAL INCLUDES
28 #include <dali-scene3d/internal/common/image-resource-loader.h>
29 #include <dali-scene3d/public-api/loader/ktx-loader.h>
30
31 #include <dali/integration-api/debug.h>
32
33 namespace Dali
34 {
35 namespace
36 {
37 const std::string_view KTX_EXTENSION = ".ktx";
38
39 static constexpr uint32_t NUMBER_OF_ENVIRONMENT_MAP_TYPE = 5;
40 static constexpr uint32_t NUMBER_OF_CUBE_MAP_TYPE        = 4;
41
42 /**
43  * @brief cube map face index
44  * Cube map layer order is as fallows:
45  * POSITIVE_X, NEGATIVE_X, POSITIVE_Y, NEGATIVE_Y, POSITIVE_Z, NEGATIVE_Z. @see CubeMapLayer
46  * The indices are for 4 kind of environment cube map. Cross_horizontal, Array_horizontal, Cross_vertical, and Array_vertical.
47  */
48 static constexpr uint32_t CUBEMAP_INDEX_X[NUMBER_OF_CUBE_MAP_TYPE][6] = {{2, 0, 1, 1, 1, 3}, {0, 1, 2, 3, 4, 5}, {1, 1, 1, 1, 0, 2}, {0, 0, 0, 0, 0, 0}};
49 static constexpr uint32_t CUBEMAP_INDEX_Y[NUMBER_OF_CUBE_MAP_TYPE][6] = {{1, 1, 0, 2, 1, 1}, {0, 0, 0, 0, 0, 0}, {1, 3, 0, 2, 1, 1}, {0, 1, 2, 3, 4, 5}};
50
51 static constexpr Vector2 NUMBER_OF_CUBE_FACE[NUMBER_OF_CUBE_MAP_TYPE] =
52   {
53     Vector2(4, 3),
54     Vector2(6, 1),
55     Vector2(3, 4),
56     Vector2(1, 6)};
57
58 static constexpr float expectedAspectRatios[NUMBER_OF_ENVIRONMENT_MAP_TYPE] =
59   {
60     4.0f / 3.0f,
61     6.0f / 1.0f,
62     3.0f / 4.0f,
63     1.0f / 6.0f,
64     2.0f / 1.0f};
65
66 enum CubeType
67 {
68   CROSS_HORIZONTAL = 0, // Cross horizontal style cube map
69   ARRAY_HORIZONTAL,     // array horizontal style cube map
70   CROSS_VERTICAL,       // Cross vertical style cube map
71   ARRAY_VERTICAL,       // array vertical style cube map
72   NONE
73 };
74
75 uint8_t* GetCroppedBuffer(uint8_t* sourceBuffer, uint32_t bytesPerPixel, uint32_t width, uint32_t height, uint32_t xOffset, uint32_t yOffset, uint32_t xFaceSize, uint32_t yFaceSize)
76 {
77   uint32_t byteSize   = bytesPerPixel * xFaceSize * yFaceSize;
78   uint8_t* destBuffer = reinterpret_cast<uint8_t*>(malloc(byteSize + 4u));
79
80   if(DALI_LIKELY(destBuffer))
81   {
82     int32_t srcStride  = width * bytesPerPixel;
83     int32_t destStride = xFaceSize * bytesPerPixel;
84     int32_t srcOffset  = xOffset * bytesPerPixel + yOffset * srcStride;
85     int32_t destOffset = 0;
86     for(uint16_t row = yOffset; row < yOffset + yFaceSize; ++row)
87     {
88       memcpy(destBuffer + destOffset, sourceBuffer + srcOffset, destStride);
89       srcOffset += srcStride;
90       destOffset += destStride;
91     }
92   }
93   else
94   {
95     DALI_LOG_ERROR("malloc is failed. request malloc size : %u\n", byteSize + 4u);
96   }
97
98   return destBuffer;
99 }
100
101 PixelData GetCubeFace(Dali::PixelData cubePixelData, uint32_t faceIndex, CubeType cubeType, float faceWidth, float faceHeight)
102 {
103   PixelData cubeFacePixelData;
104   if(cubeType != NONE)
105   {
106     auto imagePixelFormat = cubePixelData.GetPixelFormat();
107     if(!Dali::Pixel::IsCompressed(imagePixelFormat))
108     {
109       uint8_t* imageBuffer   = Dali::Integration::GetPixelDataBuffer(cubePixelData).buffer;
110       uint32_t bytesPerPixel = Pixel::GetBytesPerPixel(imagePixelFormat);
111       uint32_t imageWidth    = cubePixelData.GetWidth();
112       uint32_t imageHeight   = cubePixelData.GetHeight();
113
114       uint32_t xOffset = CUBEMAP_INDEX_X[cubeType][faceIndex] * static_cast<uint32_t>(faceWidth);
115       uint32_t yOffset = CUBEMAP_INDEX_Y[cubeType][faceIndex] * static_cast<uint32_t>(faceHeight);
116
117       uint32_t finalFaceWidth  = (xOffset + static_cast<uint32_t>(faceWidth) < imageWidth) ? static_cast<uint32_t>(faceWidth) : imageWidth - xOffset;
118       uint32_t finalFaceHeight = (yOffset + static_cast<uint32_t>(faceHeight) < imageHeight) ? static_cast<uint32_t>(faceHeight) : imageHeight - yOffset;
119       uint8_t* tempImageBuffer = GetCroppedBuffer(imageBuffer, bytesPerPixel, imageWidth, imageHeight, xOffset, yOffset, finalFaceWidth, finalFaceHeight);
120
121       if(DALI_LIKELY(tempImageBuffer))
122       {
123         cubeFacePixelData = Dali::Integration::NewPixelDataWithReleaseAfterUpload(tempImageBuffer, finalFaceWidth * finalFaceHeight * bytesPerPixel, finalFaceWidth, finalFaceHeight, 0u, imagePixelFormat, PixelData::FREE);
124       }
125     }
126   }
127   return cubeFacePixelData;
128 }
129
130 /**
131  * @brief Loads environment map data texture from an image url.
132  *
133  * @param[in] environmentMapUrl The environment map file url.
134  * @param[out] environmentMapData The data structure with all pixel data objects.
135  * @return bool True if the loading is succeded.
136  */
137 bool LoadEnvironmentMapData(const std::string& environmentMapUrl, Scene3D::Loader::EnvironmentMapData& environmentMapData)
138 {
139   // Diffuse Environment Map
140   if(environmentMapUrl.empty())
141   {
142     return false;
143   }
144
145   Dali::PixelData pixelData = Dali::Scene3D::Internal::ImageResourceLoader::GetCachedPixelData(environmentMapUrl);
146   if(pixelData)
147   {
148     CubeType cubeType    = NONE;
149     uint32_t imageWidth  = pixelData.GetWidth();
150     uint32_t imageHeight = pixelData.GetHeight();
151     /**
152      * If the environment map type is not EQUIRECTANGULAR,
153      * The type should be defined internally.
154      * DALi checkes aspect ratio of input image and find the closest type.
155      * If the environment map type is CUBEMAP, DALi determines which of the following styles is closest:
156      * cross horizontal, cross vertical, array horizontal, and array vertical.
157      * When the environment map type is AUTO, it finds the closest type, including the Equirectangular type.
158      */
159     if(environmentMapData.GetEnvironmentMapType() != Scene3D::EnvironmentMapType::EQUIRECTANGULAR)
160     {
161       float aspectRatio = (float)imageWidth / (float)imageHeight;
162
163       float    minDistance = FLT_MAX;
164       uint32_t typeCount   = (environmentMapData.GetEnvironmentMapType() == Scene3D::EnvironmentMapType::CUBEMAP) ? NUMBER_OF_CUBE_MAP_TYPE : NUMBER_OF_ENVIRONMENT_MAP_TYPE;
165       for(uint32_t i = 0; i < typeCount; ++i)
166       {
167         float distance = fabs(aspectRatio - expectedAspectRatios[i]);
168         if(distance < minDistance)
169         {
170           minDistance = distance;
171           cubeType    = static_cast<CubeType>(i);
172         }
173       }
174     }
175
176     if(cubeType != NONE)
177     {
178       float faceWidth = imageWidth, faceHeight = imageHeight;
179       faceWidth /= NUMBER_OF_CUBE_FACE[static_cast<uint32_t>(cubeType)].x;
180       faceHeight /= NUMBER_OF_CUBE_FACE[static_cast<uint32_t>(cubeType)].y;
181       environmentMapData.mPixelData.resize(6);
182       for(uint32_t i = 0; i < 6; ++i)
183       {
184         environmentMapData.mPixelData[i].resize(1);
185       }
186       for(uint32_t i = 0; i < 6; ++i)
187       {
188         environmentMapData.mPixelData[i][0] = GetCubeFace(pixelData, i, cubeType, faceWidth, faceHeight);
189       }
190       environmentMapData.SetEnvironmentMapType(Scene3D::EnvironmentMapType::CUBEMAP);
191     }
192     else
193     {
194       environmentMapData.mPixelData.resize(1);
195       environmentMapData.mPixelData[0].resize(1);
196       environmentMapData.mPixelData[0][0] = pixelData;
197       environmentMapData.SetEnvironmentMapType(Scene3D::EnvironmentMapType::EQUIRECTANGULAR);
198     }
199
200     if(!environmentMapData.mPixelData.empty() && !environmentMapData.mPixelData[0].empty() && environmentMapData.mPixelData[0][0])
201     {
202       const uint32_t pixelDataWidth  = environmentMapData.mPixelData[0][0].GetWidth();
203       const uint32_t pixelDataHeight = environmentMapData.mPixelData[0][0].GetHeight();
204
205       if(pixelDataWidth > 0u && pixelDataHeight > 0u)
206       {
207         uint32_t mipmap = static_cast<uint32_t>(1 + std::floor(std::log2(std::min(pixelDataWidth, pixelDataHeight))));
208         environmentMapData.SetMipmapLevels(mipmap);
209       }
210     }
211     return true;
212   }
213   return false;
214 }
215 } // namespace
216
217 namespace Scene3D
218 {
219 namespace Loader
220 {
221 bool LoadEnvironmentMap(const std::string& environmentMapUrl, EnvironmentMapData& environmentMapData)
222 {
223   std::filesystem::path modelPath(environmentMapUrl);
224   std::string           extension = modelPath.extension();
225   std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
226
227   bool successed = (extension == KTX_EXTENSION) ? Dali::Scene3D::Loader::LoadKtxData(environmentMapUrl, environmentMapData) : LoadEnvironmentMapData(environmentMapUrl, environmentMapData);
228   return successed;
229 }
230
231 } // namespace Loader
232 } // namespace Scene3D
233 } // namespace Dali