f7f998e4defbe27e3016fabe043a93f0f4f2e0a3
[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   int32_t srcStride  = width * bytesPerPixel;
81   int32_t destStride = xFaceSize * bytesPerPixel;
82   int32_t srcOffset  = xOffset * bytesPerPixel + yOffset * srcStride;
83   int32_t destOffset = 0;
84   for(uint16_t row = yOffset; row < yOffset + yFaceSize; ++row)
85   {
86     memcpy(destBuffer + destOffset, sourceBuffer + srcOffset, destStride);
87     srcOffset += srcStride;
88     destOffset += destStride;
89   }
90
91   return destBuffer;
92 }
93
94 PixelData GetCubeFace(Dali::PixelData cubePixelData, uint32_t faceIndex, CubeType cubeType, float faceWidth, float faceHeight)
95 {
96   PixelData cubeFacePixelData;
97   if(cubeType != NONE)
98   {
99     auto imagePixelFormat = cubePixelData.GetPixelFormat();
100     if(!Dali::Pixel::IsCompressed(imagePixelFormat))
101     {
102       uint8_t* imageBuffer   = Dali::Integration::GetPixelDataBuffer(cubePixelData).buffer;
103       uint32_t bytesPerPixel = Pixel::GetBytesPerPixel(imagePixelFormat);
104       uint32_t imageWidth    = cubePixelData.GetWidth();
105       uint32_t imageHeight   = cubePixelData.GetHeight();
106
107       uint32_t xOffset = CUBEMAP_INDEX_X[cubeType][faceIndex] * static_cast<uint32_t>(faceWidth);
108       uint32_t yOffset = CUBEMAP_INDEX_Y[cubeType][faceIndex] * static_cast<uint32_t>(faceHeight);
109
110       uint32_t finalFaceWidth  = (xOffset + static_cast<uint32_t>(faceWidth) < imageWidth) ? static_cast<uint32_t>(faceWidth) : imageWidth - xOffset;
111       uint32_t finalFaceHeight = (yOffset + static_cast<uint32_t>(faceHeight) < imageHeight) ? static_cast<uint32_t>(faceHeight) : imageHeight - yOffset;
112       uint8_t* tempImageBuffer = GetCroppedBuffer(imageBuffer, bytesPerPixel, imageWidth, imageHeight, xOffset, yOffset, finalFaceWidth, finalFaceHeight);
113
114       cubeFacePixelData = Dali::Integration::NewPixelDataWithReleaseAfterUpload(tempImageBuffer, finalFaceWidth * finalFaceHeight * bytesPerPixel, finalFaceWidth, finalFaceHeight, 0u, imagePixelFormat, PixelData::FREE);
115     }
116   }
117   return cubeFacePixelData;
118 }
119
120 /**
121  * @brief Loads environment map data texture from an image url.
122  *
123  * @param[in] environmentMapUrl The environment map file url.
124  * @param[out] environmentMapData The data structure with all pixel data objects.
125  * @return bool True if the loading is succeded.
126  */
127 bool LoadEnvironmentMapData(const std::string& environmentMapUrl, Scene3D::Loader::EnvironmentMapData& environmentMapData)
128 {
129   // Diffuse Environment Map
130   if(environmentMapUrl.empty())
131   {
132     return false;
133   }
134
135   Dali::PixelData pixelData = Dali::Scene3D::Internal::ImageResourceLoader::GetCachedPixelData(environmentMapUrl);
136   if(pixelData)
137   {
138     CubeType cubeType    = NONE;
139     uint32_t imageWidth  = pixelData.GetWidth();
140     uint32_t imageHeight = pixelData.GetHeight();
141     /**
142      * If the environment map type is not EQUIRECTANGULAR,
143      * The type should be defined internally.
144      * DALi checkes aspect ratio of input image and find the closest type.
145      * If the environment map type is CUBEMAP, DALi determines which of the following styles is closest:
146      * cross horizontal, cross vertical, array horizontal, and array vertical.
147      * When the environment map type is AUTO, it finds the closest type, including the Equirectangular type.
148      */
149     if(environmentMapData.GetEnvironmentMapType() != Scene3D::EnvironmentMapType::EQUIRECTANGULAR)
150     {
151       float aspectRatio = (float)imageWidth / (float)imageHeight;
152
153       float    minDistance = FLT_MAX;
154       uint32_t typeCount   = (environmentMapData.GetEnvironmentMapType() == Scene3D::EnvironmentMapType::CUBEMAP) ? NUMBER_OF_CUBE_MAP_TYPE : NUMBER_OF_ENVIRONMENT_MAP_TYPE;
155       for(uint32_t i = 0; i < typeCount; ++i)
156       {
157         float distance = fabs(aspectRatio - expectedAspectRatios[i]);
158         if(distance < minDistance)
159         {
160           minDistance = distance;
161           cubeType    = static_cast<CubeType>(i);
162         }
163       }
164     }
165
166     if(cubeType != NONE)
167     {
168       float faceWidth = imageWidth, faceHeight = imageHeight;
169       faceWidth /= NUMBER_OF_CUBE_FACE[static_cast<uint32_t>(cubeType)].x;
170       faceHeight /= NUMBER_OF_CUBE_FACE[static_cast<uint32_t>(cubeType)].y;
171       environmentMapData.mPixelData.resize(6);
172       for(uint32_t i = 0; i < 6; ++i)
173       {
174         environmentMapData.mPixelData[i].resize(1);
175       }
176       for(uint32_t i = 0; i < 6; ++i)
177       {
178         environmentMapData.mPixelData[i][0] = GetCubeFace(pixelData, i, cubeType, faceWidth, faceHeight);
179       }
180       environmentMapData.SetEnvironmentMapType(Scene3D::EnvironmentMapType::CUBEMAP);
181     }
182     else
183     {
184       environmentMapData.mPixelData.resize(1);
185       environmentMapData.mPixelData[0].resize(1);
186       environmentMapData.mPixelData[0][0] = pixelData;
187       environmentMapData.SetEnvironmentMapType(Scene3D::EnvironmentMapType::EQUIRECTANGULAR);
188     }
189
190     if(!environmentMapData.mPixelData.empty() && !environmentMapData.mPixelData[0].empty() && environmentMapData.mPixelData[0][0])
191     {
192       const uint32_t pixelDataWidth  = environmentMapData.mPixelData[0][0].GetWidth();
193       const uint32_t pixelDataHeight = environmentMapData.mPixelData[0][0].GetHeight();
194
195       if(pixelDataWidth > 0u && pixelDataHeight > 0u)
196       {
197         uint32_t mipmap = static_cast<uint32_t>(1 + std::floor(std::log2(std::min(pixelDataWidth, pixelDataHeight))));
198         environmentMapData.SetMipmapLevels(mipmap);
199       }
200     }
201     return true;
202   }
203   return false;
204 }
205 } // namespace
206
207 namespace Scene3D
208 {
209 namespace Loader
210 {
211 bool LoadEnvironmentMap(const std::string& environmentMapUrl, EnvironmentMapData& environmentMapData)
212 {
213   std::filesystem::path modelPath(environmentMapUrl);
214   std::string           extension = modelPath.extension();
215   std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
216
217   bool successed = (extension == KTX_EXTENSION) ? Dali::Scene3D::Loader::LoadKtxData(environmentMapUrl, environmentMapData) : LoadEnvironmentMapData(environmentMapUrl, environmentMapData);
218   return successed;
219 }
220
221 } // namespace Loader
222 } // namespace Scene3D
223 } // namespace Dali