2 * Copyright (c) 2022 Samsung Electronics Co., Ltd.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
19 #include "image-atlas-impl.h"
22 #include <dali/devel-api/adaptor-framework/image-loading.h>
23 #include <dali/integration-api/debug.h>
24 #include <dali/public-api/signals/callback.h>
28 #include <dali-toolkit/internal/image-loader/async-image-loader-impl.h>
36 Texture ImageAtlas::PackToAtlas(const std::vector<PixelData>& pixelData, Dali::Vector<Vector4>& textureRects)
38 // Record each block size
39 Dali::Vector<Uint16Pair> blockSizes;
40 SizeType count = pixelData.size();
41 for(SizeType index = 0; index < count; index++)
43 blockSizes.PushBack(ImageDimensions(pixelData[index].GetWidth(), pixelData[index].GetHeight()));
46 // Ask atlasPacker for packing position of each block
47 Dali::Vector<Uint16Pair> packPositions;
48 ImageDimensions atlasSize = AtlasPacker::GroupPack(blockSizes, packPositions);
50 // Prepare for outout texture rect array
52 textureRects.Resize(count);
54 // create the texture for uploading the multiple pixel data
55 Texture atlasTexture = Texture::New(Dali::TextureType::TEXTURE_2D, Pixel::RGBA8888, atlasSize.GetWidth(), atlasSize.GetHeight());
57 float atlasWidth = static_cast<float>(atlasTexture.GetWidth());
58 float atlasHeight = static_cast<float>(atlasTexture.GetHeight());
59 int packPositionX, packPositionY;
60 // Upload the pixel data one by one to its packing position, and record the texture rects
61 for(SizeType index = 0; index < count; index++)
63 packPositionX = packPositions[index].GetX();
64 packPositionY = packPositions[index].GetY();
65 atlasTexture.Upload(pixelData[index], 0u, 0u, packPositionX, packPositionY, pixelData[index].GetWidth(), pixelData[index].GetHeight());
67 // Apply the half pixel correction to avoid the color bleeding between neighbour blocks
68 textureRects[index].x = (static_cast<float>(packPositionX) + 0.5f) / atlasWidth; // left
69 textureRects[index].y = (static_cast<float>(packPositionY) + 0.5f) / atlasHeight; // top
70 textureRects[index].z = (static_cast<float>(packPositionX + pixelData[index].GetWidth()) - 0.5f) / atlasWidth; // right
71 textureRects[index].w = (static_cast<float>(packPositionY + pixelData[index].GetHeight()) - 0.5f) / atlasHeight; // bottom
77 ImageAtlas::ImageAtlas(SizeType width, SizeType height, Pixel::Format pixelFormat)
78 : mAtlas(Texture::New(Dali::TextureType::TEXTURE_2D, pixelFormat, width, height)),
79 mPacker(width, height),
80 mAsyncLoader(Toolkit::AsyncImageLoader::New()),
83 mWidth(static_cast<float>(width)),
84 mHeight(static_cast<float>(height)),
85 mPixelFormat(pixelFormat)
87 mAsyncLoader.ImageLoadedSignal().Connect(this, &ImageAtlas::UploadToAtlas);
90 ImageAtlas::~ImageAtlas()
94 mAsyncLoader.CancelAll();
95 mAsyncLoader.ImageLoadedSignal().Disconnect(this, &ImageAtlas::UploadToAtlas);
98 const std::size_t count = mLoadingTaskInfoContainer.Count();
99 for(std::size_t i = 0; i < count; ++i)
101 // Call unregister to every observer in the list.
102 // Note that, the Atlas can be registered to same observer multiple times, and the Unregister method only remove one item each time.
103 // In this way, the atlas is actually detached from a observer either every upload call invoked by this observer is completed or atlas is destroyed.
104 if(mLoadingTaskInfoContainer[i]->observer)
106 mLoadingTaskInfoContainer[i]->observer->Unregister(*this);
110 mLoadingTaskInfoContainer.Clear();
113 IntrusivePtr<ImageAtlas> ImageAtlas::New(SizeType width, SizeType height, Pixel::Format pixelFormat)
115 IntrusivePtr<ImageAtlas> internal = new ImageAtlas(width, height, pixelFormat);
120 Texture ImageAtlas::GetAtlas()
125 float ImageAtlas::GetOccupancyRate() const
127 return 1.f - static_cast<float>(mPacker.GetAvailableArea()) / (mWidth * mHeight);
130 void ImageAtlas::SetBrokenImage(const std::string& brokenImageUrl)
132 mBrokenImageSize = Dali::GetClosestImageSize(brokenImageUrl);
133 if(mBrokenImageSize.GetWidth() > 0 && mBrokenImageSize.GetHeight() > 0) // check the url is valid
135 mBrokenImageUrl = brokenImageUrl;
139 bool ImageAtlas::Upload(Vector4& textureRect,
140 const VisualUrl& url,
141 ImageDimensions size,
142 FittingMode::Type fittingMode,
143 bool orientationCorrection,
144 AtlasUploadObserver* atlasUploadObserver)
146 ImageDimensions dimensions = size;
147 ImageDimensions zero;
148 if(size == zero) // image size not provided
150 dimensions = Dali::GetClosestImageSize(url.GetUrl());
151 if(dimensions == zero) // Fail to read the image & broken image file exists
153 if(!mBrokenImageUrl.empty())
155 return Upload(textureRect, mBrokenImageUrl, mBrokenImageSize, FittingMode::DEFAULT, true, atlasUploadObserver);
159 textureRect = Vector4::ZERO;
165 uint32_t packPositionX = 0;
166 uint32_t packPositionY = 0;
167 if(mPacker.Pack(dimensions.GetWidth(), dimensions.GetHeight(), packPositionX, packPositionY))
169 uint32_t loadId = GetImplementation(mAsyncLoader).Load(url, size, fittingMode, SamplingMode::BOX_THEN_LINEAR, orientationCorrection, DevelAsyncImageLoader::PreMultiplyOnLoad::OFF, false);
170 mLoadingTaskInfoContainer.PushBack(new LoadingTaskInfo(loadId, packPositionX, packPositionY, dimensions.GetWidth(), dimensions.GetHeight(), atlasUploadObserver));
171 // apply the half pixel correction
172 textureRect.x = (static_cast<float>(packPositionX) + 0.5f) / mWidth; // left
173 textureRect.y = (static_cast<float>(packPositionY) + 0.5f) / mHeight; // top
174 textureRect.z = (static_cast<float>(packPositionX + dimensions.GetX()) - 0.5f) / mWidth; // right
175 textureRect.w = (static_cast<float>(packPositionY + dimensions.GetY()) - 0.5f) / mHeight; // bottom
177 if(atlasUploadObserver)
179 // register to the observer,
180 // Not that a matching unregister call should be invoked in UploadToAtlas if the observer is still alive by then.
181 atlasUploadObserver->Register(*this);
190 bool ImageAtlas::Upload(Vector4& textureRect,
191 const EncodedImageBuffer& encodedImageBuffer,
192 ImageDimensions size,
193 FittingMode::Type fittingMode,
194 bool orientationCorrection,
195 AtlasUploadObserver* atlasUploadObserver)
197 ImageDimensions zero;
198 if(size == zero) // image size not provided
200 DALI_LOG_ERROR("Desired size is zero! We need to setup desired size for Atlas.\n");
201 // EncodedImageBuffer didn't support to get closest image size.
202 // Just draw broken image.
203 if(!mBrokenImageUrl.empty())
205 return Upload(textureRect, mBrokenImageUrl, mBrokenImageSize, FittingMode::DEFAULT, true, atlasUploadObserver);
209 textureRect = Vector4::ZERO;
214 uint32_t packPositionX = 0;
215 uint32_t packPositionY = 0;
216 if(mPacker.Pack(size.GetWidth(), size.GetHeight(), packPositionX, packPositionY))
218 uint32_t loadId = GetImplementation(mAsyncLoader).LoadEncodedImageBuffer(encodedImageBuffer, size, fittingMode, SamplingMode::BOX_THEN_LINEAR, orientationCorrection, DevelAsyncImageLoader::PreMultiplyOnLoad::OFF);
219 mLoadingTaskInfoContainer.PushBack(new LoadingTaskInfo(loadId, packPositionX, packPositionY, size.GetWidth(), size.GetHeight(), atlasUploadObserver));
221 // apply the half pixel correction
222 textureRect.x = (static_cast<float>(packPositionX) + 0.5f) / mWidth; // left
223 textureRect.y = (static_cast<float>(packPositionY) + 0.5f) / mHeight; // top
224 textureRect.z = (static_cast<float>(packPositionX + size.GetX()) - 0.5f) / mWidth; // right
225 textureRect.w = (static_cast<float>(packPositionY + size.GetY()) - 0.5f) / mHeight; // bottom
227 if(atlasUploadObserver)
229 // register to the observer,
230 // Not that a matching unregister call should be invoked in UploadToAtlas if the observer is still alive by then.
231 atlasUploadObserver->Register(*this);
240 bool ImageAtlas::Upload(Vector4& textureRect, PixelData pixelData)
242 uint32_t packPositionX = 0;
243 uint32_t packPositionY = 0;
244 if(mPacker.Pack(pixelData.GetWidth(), pixelData.GetHeight(), packPositionX, packPositionY))
246 mAtlas.Upload(pixelData, 0u, 0u, packPositionX, packPositionY, pixelData.GetWidth(), pixelData.GetHeight());
248 // apply the half pixel correction
249 textureRect.x = (static_cast<float>(packPositionX) + 0.5f) / mWidth; // left
250 textureRect.y = (static_cast<float>(packPositionY) + 0.5f) / mHeight; // top
251 textureRect.z = (static_cast<float>(packPositionX + pixelData.GetWidth()) - 0.5f) / mWidth; // right
252 textureRect.w = (static_cast<float>(packPositionY + pixelData.GetHeight()) - 0.5f) / mHeight; // bottom
260 void ImageAtlas::Remove(const Vector4& textureRect)
262 mPacker.DeleteBlock(static_cast<SizeType>(textureRect.x * mWidth),
263 static_cast<SizeType>(textureRect.y * mHeight),
264 static_cast<SizeType>((textureRect.z - textureRect.x) * mWidth + 1.f),
265 static_cast<SizeType>((textureRect.w - textureRect.y) * mHeight + 1.f));
268 void ImageAtlas::ObserverDestroyed(AtlasUploadObserver* observer)
270 const std::size_t count = mLoadingTaskInfoContainer.Count();
271 for(std::size_t i = 0; i < count; ++i)
273 if(mLoadingTaskInfoContainer[i]->observer == observer)
275 // the observer is destructing, so its member function should not be called anymore
276 mLoadingTaskInfoContainer[i]->observer = NULL;
281 void ImageAtlas::UploadToAtlas(uint32_t id, PixelData pixelData)
283 auto end = mLoadingTaskInfoContainer.End();
284 for(auto loadingTaskIterator = mLoadingTaskInfoContainer.Begin(); loadingTaskIterator != end; ++loadingTaskIterator)
286 if((*loadingTaskIterator) && (*loadingTaskIterator)->loadTaskId == id)
288 Rect<uint32_t> packRect((*loadingTaskIterator)->packRect);
289 if(!pixelData || (pixelData.GetWidth() == 0 && pixelData.GetHeight() == 0))
291 if(!mBrokenImageUrl.empty()) // replace with the broken image
293 UploadBrokenImage(packRect);
298 if(pixelData.GetWidth() < packRect.width || pixelData.GetHeight() < packRect.height)
300 DALI_LOG_ERROR("Can not upscale the image from actual loaded size [ %d, %d ] to specified size [ %d, %d ]\n",
301 pixelData.GetWidth(),
302 pixelData.GetHeight(),
307 mAtlas.Upload(pixelData, 0u, 0u, packRect.x, packRect.y, packRect.width, packRect.height);
310 if((*loadingTaskIterator)->observer)
312 (*loadingTaskIterator)->observer->UploadCompleted();
313 (*loadingTaskIterator)->observer->Unregister(*this);
316 mLoadingTaskInfoContainer.Erase(loadingTaskIterator);
322 void ImageAtlas::UploadBrokenImage(const Rect<uint32_t>& area)
324 Devel::PixelBuffer brokenBuffer = LoadImageFromFile(mBrokenImageUrl, ImageDimensions(area.width, area.height));
325 SizeType loadedWidth = brokenBuffer.GetWidth();
326 SizeType loadedHeight = brokenBuffer.GetHeight();
328 bool needBackgroundClear = false;
329 SizeType packX = area.x;
330 SizeType packY = area.y;
331 // locate the broken image in the middle.
332 if(area.width > loadedWidth)
334 packX += (area.width - loadedWidth) / 2;
335 needBackgroundClear = true;
337 if(area.height > loadedHeight)
339 packY += (area.height - loadedHeight) / 2;
340 needBackgroundClear = true;
343 if(needBackgroundClear)
345 SizeType size = area.width * area.height * Pixel::GetBytesPerPixel(mPixelFormat);
346 Devel::PixelBuffer background = Devel::PixelBuffer::New(area.width, area.height, mPixelFormat);
347 unsigned char* buffer = background.GetBuffer();
348 for(SizeType idx = 0; idx < size; idx++)
352 PixelData pixelData = Devel::PixelBuffer::Convert(background);
353 mAtlas.Upload(pixelData, 0u, 0u, area.x, area.y, area.width, area.height);
356 PixelData brokenPixelData = Devel::PixelBuffer::Convert(brokenBuffer);
357 mAtlas.Upload(brokenPixelData, 0u, 0u, packX, packY, loadedWidth, loadedHeight);
360 } // namespace Internal
362 } // namespace Toolkit