Call FT_Glyph_To_Bitmap might be overhead for some system.
But cache the rendered bitmap information could be another overhead.
So, we try to ask environment and store the bitmap result.
Follow as EFL system, we will store it as 8bit -> 4bit reduced
and RLE4 compression.
+
Now we can know the buffer's ownership for FontClient::GlyphBufferData.
We can use the buffer without copy if need.
Change-Id: I82a76961c84679c04750f0de904096130cb69df2
Signed-off-by: Eunki, Hong <eunkiki.hong@samsung.com>
DALI_TEST_GREATER(MAX_WIDTH_FIT_IN_ATLAS, glyphBufferData2000.width, TEST_LOCATION);
DALI_TEST_GREATER(MAX_HEIGHT_FIT_IN_ATLAS, glyphBufferData2000.height, TEST_LOCATION);
- // Release copied memories
- free(glyphBufferData200.buffer);
- free(glyphBufferData1000.buffer);
- free(glyphBufferData2000.buffer);
-
END_TEST;
}
/*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
#include <dali/devel-api/text-abstraction/font-client.h>
// INTERNAL INCLUDES
+#include <dali/internal/imaging/common/image-operations.h>
#include <dali/internal/text/text-abstraction/font-client-impl.h>
namespace Dali
const uint32_t FontClient::NUMBER_OF_POINTS_PER_ONE_UNIT_OF_POINT_SIZE = 64u; //Found this value from toolkit
+// FontClient::GlyphBufferData
+
FontClient::GlyphBufferData::GlyphBufferData()
: buffer{nullptr},
width{0u},
outlineOffsetX{0},
outlineOffsetY{0},
format{Pixel::A8},
+ compressType(CompressType::NO_COMPRESS),
isColorEmoji{false},
- isColorBitmap{false}
+ isColorBitmap{false},
+ isBufferOwned{false}
{
}
FontClient::GlyphBufferData::~GlyphBufferData()
{
-}
+ if(isBufferOwned)
+ {
+ free(buffer);
+ }
+}
+
+size_t FontClient::GlyphBufferData::Compress(const uint8_t* const __restrict__ inBuffer, GlyphBufferData& __restrict__ outBufferData)
+{
+ size_t bufferSize = 0u;
+ uint8_t*& __restrict__ compressedBuffer = outBufferData.buffer;
+ switch(outBufferData.compressType)
+ {
+ case TextAbstraction::FontClient::GlyphBufferData::CompressType::NO_COMPRESS:
+ {
+ bufferSize = outBufferData.width * outBufferData.height * Pixel::GetBytesPerPixel(outBufferData.format);
+
+ compressedBuffer = (uint8_t*)malloc(bufferSize);
+ if(DALI_UNLIKELY(compressedBuffer == nullptr))
+ {
+ return 0u;
+ }
+ outBufferData.isBufferOwned = true;
+
+ // Copy buffer without compress
+ memcpy(compressedBuffer, inBuffer, bufferSize);
+ break;
+ }
+ case TextAbstraction::FontClient::GlyphBufferData::CompressType::BIT_PER_PIXEL_4:
+ {
+ const uint32_t widthByte = outBufferData.width * Pixel::GetBytesPerPixel(outBufferData.format);
+ const uint32_t componentCount = (widthByte >> 1);
+ const bool considerPadding = (widthByte & 1) ? true : false;
+
+ // For BIT_PER_PIXEL_4 type, we can know final compressed buffer size immediatly.
+ bufferSize = outBufferData.height * (componentCount + (considerPadding ? 1 : 0));
+ compressedBuffer = (uint8_t*)malloc(bufferSize);
+ if(DALI_UNLIKELY(compressedBuffer == nullptr))
+ {
+ return 0u;
+ }
+ outBufferData.isBufferOwned = true;
+
+ uint8_t* __restrict__ outBufferPtr = compressedBuffer;
+ const uint8_t* __restrict__ inBufferPtr = inBuffer;
+
+ // Compress for each line
+ for(uint32_t y = 0; y < outBufferData.height; ++y)
+ {
+ for(uint32_t x = 0; x < componentCount; ++x)
+ {
+ const uint8_t v0 = Dali::Internal::Platform::CompressBitPerPixel8To4(*(inBufferPtr++));
+ const uint8_t v1 = Dali::Internal::Platform::CompressBitPerPixel8To4(*(inBufferPtr++));
+
+ *(outBufferPtr++) = (v0 << 4) | v1;
+ }
+ if(considerPadding)
+ {
+ *(outBufferPtr++) = Dali::Internal::Platform::CompressBitPerPixel8To4(*(inBufferPtr++));
+ }
+ }
+ break;
+ }
+ case TextAbstraction::FontClient::GlyphBufferData::CompressType::COMPRESS_RLE4:
+ {
+ const uint32_t widthByte = outBufferData.width * Pixel::GetBytesPerPixel(outBufferData.format);
+
+ // Allocate temperal buffer. Note that RLE4 can be bigger than original buffer.
+ uint8_t* __restrict__ tempBuffer = (uint8_t*)malloc(outBufferData.height * (widthByte + 1));
+ if(DALI_UNLIKELY(tempBuffer == nullptr))
+ {
+ return 0u;
+ }
+
+ uint8_t* __restrict__ outBufferPtr = tempBuffer;
+ const uint8_t* __restrict__ inBufferPtr = inBuffer;
+
+ bufferSize = 0u;
+
+ // Compress for each line
+ for(uint32_t y = 0; y < outBufferData.height; ++y)
+ {
+ uint32_t encodedByte = 0;
+ while(encodedByte < widthByte)
+ {
+ // Case 1 : Remain only 1 byte
+ if(DALI_UNLIKELY(encodedByte + 1 == widthByte))
+ {
+ const uint8_t prev0 = DALI_UNLIKELY(y == 0) ? 0 : Dali::Internal::Platform::CompressBitPerPixel8To4(*(inBufferPtr - widthByte));
+ const uint8_t v0 = (Dali::Internal::Platform::CompressBitPerPixel8To4(*(inBufferPtr++)) - prev0) & 0x0f; // Intented underflow
+ *(outBufferPtr++) = v0;
+ ++encodedByte;
+ ++bufferSize;
+ }
+ // Case 2 : Remain only 2 byte
+ else if(DALI_UNLIKELY(encodedByte + 2 == widthByte))
+ {
+ const uint8_t prev0 = DALI_UNLIKELY(y == 0) ? 0 : Dali::Internal::Platform::CompressBitPerPixel8To4(*(inBufferPtr - widthByte));
+ const uint8_t v0 = (Dali::Internal::Platform::CompressBitPerPixel8To4(*(inBufferPtr++)) - prev0) & 0x0f; // Intented underflow
+ const uint8_t prev1 = DALI_UNLIKELY(y == 0) ? 0 : Dali::Internal::Platform::CompressBitPerPixel8To4(*(inBufferPtr - widthByte));
+ const uint8_t v1 = (Dali::Internal::Platform::CompressBitPerPixel8To4(*(inBufferPtr++)) - prev1) & 0x0f; // Intented underflow
+ encodedByte += 2;
+ if(v0 == v1)
+ {
+ *(outBufferPtr++) = 0x80 | v0;
+ ++bufferSize;
+ }
+ else
+ {
+ *(outBufferPtr++) = 0x10 | v0;
+ *(outBufferPtr++) = v1 << 4;
+ bufferSize += 2;
+ }
+ }
+ // Case 3 : Normal case. Remain byte bigger or equal than 3.
+ else
+ {
+ // Compress rule -
+ // Read 2 byte as v0 and v1.
+ // - If v0 == v1, We can compress. mark the first bit as 1. and remain 3 bit mark as the "runLength - 2".
+ // runLength can be maximum 9.
+ // - If v0 != v1, We cannot compress. mark the first bit as 0. and remain 3 bit mark as the "(nonRunLength - 1) / 2"
+ // Due to the BitPerPixel is 4, nonRunLength should be odd value.
+ // nonRunLength cutted if v0 == v1.
+ // nonRunLength can be maximum 15.
+
+ const uint8_t prev0 = DALI_UNLIKELY(y == 0) ? 0 : Dali::Internal::Platform::CompressBitPerPixel8To4(*(inBufferPtr - widthByte));
+ const uint8_t v0 = (Dali::Internal::Platform::CompressBitPerPixel8To4(*(inBufferPtr++)) - prev0) & 0x0f; // Intented underflow
+ const uint8_t prev1 = DALI_UNLIKELY(y == 0) ? 0 : Dali::Internal::Platform::CompressBitPerPixel8To4(*(inBufferPtr - widthByte));
+ const uint8_t v1 = (Dali::Internal::Platform::CompressBitPerPixel8To4(*(inBufferPtr++)) - prev1) & 0x0f; // Intented underflow
+ encodedByte += 2;
+ // We can compress by RLE
+ if(v0 == v1)
+ {
+ uint8_t runLength = 2;
+ while(encodedByte < widthByte && runLength < 9)
+ {
+ const uint8_t prev2 = DALI_UNLIKELY(y == 0) ? 0 : Dali::Internal::Platform::CompressBitPerPixel8To4(*(inBufferPtr - widthByte));
+ const uint8_t v2 = (Dali::Internal::Platform::CompressBitPerPixel8To4(*(inBufferPtr)) - prev2) & 0x0f; // Intented underflow
+ if(v2 == v0)
+ {
+ ++inBufferPtr;
+ ++encodedByte;
+ ++runLength;
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ // Update (runLength - 2) result.
+ *(outBufferPtr++) = ((0x8 | (runLength - 2)) << 4) | v0;
+ ++bufferSize;
+ }
+ // We cannot compress by RLE.
+ else
+ {
+ // Read one more value.
+ const uint8_t prev2 = DALI_UNLIKELY(y == 0) ? 0 : Dali::Internal::Platform::CompressBitPerPixel8To4(*(inBufferPtr - widthByte));
+ const uint8_t v2 = (Dali::Internal::Platform::CompressBitPerPixel8To4(*(inBufferPtr++)) - prev2) & 0x0f; // Intented underflow
+ ++encodedByte;
+
+ uint8_t nonRunLength = 3;
+ uint8_t* nonRunLengthHeaderPtr = outBufferPtr;
+ *(outBufferPtr++) = v0;
+ *(outBufferPtr++) = (v1 << 4) | v2;
+ bufferSize += 2;
+ while(encodedByte < widthByte && nonRunLength < 15)
+ {
+ if(DALI_LIKELY(encodedByte + 1 < widthByte))
+ {
+ const uint8_t prew0 = DALI_UNLIKELY(y == 0) ? 0 : Dali::Internal::Platform::CompressBitPerPixel8To4(*(inBufferPtr - widthByte));
+ const uint8_t w0 = (Dali::Internal::Platform::CompressBitPerPixel8To4(*(inBufferPtr)) - prew0) & 0x0f; // Intented underflow
+ const uint8_t prew1 = DALI_UNLIKELY(y == 0) ? 0 : Dali::Internal::Platform::CompressBitPerPixel8To4(*(inBufferPtr + 1 - widthByte));
+ const uint8_t w1 = (Dali::Internal::Platform::CompressBitPerPixel8To4(*(inBufferPtr + 1)) - prew1) & 0x0f; // Intented underflow
+ if(w0 == w1)
+ {
+ // Stop non-compress logic.
+ break;
+ }
+ else
+ {
+ ++bufferSize;
+ *(outBufferPtr++) = (w0 << 4) | w1;
+ inBufferPtr += 2;
+ encodedByte += 2;
+ nonRunLength += 2;
+ }
+ }
+ else
+ {
+ // Edge case. There is only one pixel remained.
+ const uint8_t prew0 = DALI_UNLIKELY(y == 0) ? 0 : Dali::Internal::Platform::CompressBitPerPixel8To4(*(inBufferPtr - widthByte));
+ const uint8_t w0 = (Dali::Internal::Platform::CompressBitPerPixel8To4(*(inBufferPtr)) - prew0) & 0x0f; // Intented underflow
+ {
+ ++bufferSize;
+ *(outBufferPtr++) = (w0 << 4);
+ ++encodedByte;
+ ++inBufferPtr;
+ // Increase nonRunLength 2 even latest value is invalid.
+ nonRunLength += 2;
+ }
+ }
+ }
+
+ // Update (nonRunLength-1)/2 result into header.
+ *(nonRunLengthHeaderPtr) |= (nonRunLength >> 1) << 4;
+ }
+ }
+ }
+ }
+
+ // Allocate and copy data
+ compressedBuffer = (uint8_t*)malloc(bufferSize);
+ if(DALI_UNLIKELY(compressedBuffer == nullptr))
+ {
+ free(tempBuffer);
+ return 0u;
+ }
+ outBufferData.isBufferOwned = true;
+
+ memcpy(compressedBuffer, tempBuffer, bufferSize);
+ free(tempBuffer);
+
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+
+ return bufferSize;
+}
+
+void FontClient::GlyphBufferData::Decompress(const GlyphBufferData& __restrict__ inBufferData, uint8_t* __restrict__ outBuffer)
+{
+ if(DALI_UNLIKELY(outBuffer == nullptr))
+ {
+ return;
+ }
+
+ switch(inBufferData.compressType)
+ {
+ case TextAbstraction::FontClient::GlyphBufferData::CompressType::NO_COMPRESS:
+ {
+ const auto bufferSize = inBufferData.width * inBufferData.height * Pixel::GetBytesPerPixel(inBufferData.format);
+
+ // Copy buffer without compress
+ memcpy(outBuffer, inBufferData.buffer, bufferSize);
+ break;
+ }
+ case TextAbstraction::FontClient::GlyphBufferData::CompressType::BIT_PER_PIXEL_4:
+ {
+ const uint32_t widthByte = inBufferData.width * Pixel::GetBytesPerPixel(inBufferData.format);
+ const uint32_t componentCount = (widthByte >> 1);
+ const bool considerPadding = (widthByte & 1) ? true : false;
+
+ uint8_t* __restrict__ outBufferPtr = outBuffer;
+ const uint8_t* __restrict__ inBufferPtr = inBufferData.buffer;
+
+ // Compress for each line
+ for(uint32_t y = 0; y < inBufferData.height; ++y)
+ {
+ for(uint32_t x = 0; x < componentCount; ++x)
+ {
+ const uint8_t v = *(inBufferPtr++);
+ const uint8_t v0 = (v >> 4) & 0x0f;
+ const uint8_t v1 = v & 0x0f;
+
+ *(outBufferPtr++) = (v0 << 4) | v0;
+ *(outBufferPtr++) = (v1 << 4) | v1;
+ }
+ if(considerPadding)
+ {
+ const uint8_t v = *(inBufferPtr++);
+ *(outBufferPtr++) = (v << 4) | v;
+ }
+ }
+ break;
+ }
+ case TextAbstraction::FontClient::GlyphBufferData::CompressType::COMPRESS_RLE4:
+ {
+ const uint32_t widthByte = inBufferData.width * Pixel::GetBytesPerPixel(inBufferData.format);
+
+ uint8_t* __restrict__ outBufferPtr = outBuffer;
+ const uint8_t* __restrict__ inBufferPtr = inBufferData.buffer;
+ // Compress for each line
+ for(uint32_t y = 0; y < inBufferData.height; ++y)
+ {
+ uint32_t x = 0;
+ uint32_t decodedByte = 0;
+ while(decodedByte < widthByte)
+ {
+ const uint8_t v = *(inBufferPtr++);
+ ++x;
+ // Compress by RLE
+ if(v & 0x80)
+ {
+ const uint8_t runLength = ((v >> 4) & 0x07) + 2u;
+ decodedByte += runLength;
+ const uint8_t repeatValue = v & 0x0f;
+ for(uint8_t iter = 0; iter < runLength; ++iter)
+ {
+ const uint8_t prev0 = DALI_UNLIKELY(y == 0) ? 0 : (*(outBufferPtr - widthByte)) & 0x0f;
+ const uint8_t v0 = (prev0 + repeatValue) & 0x0f;
+ *(outBufferPtr++) = (v0 << 4) | v0;
+ }
+ }
+ // Not compress by RLE
+ else
+ {
+ const uint8_t nonRunLength = (((v >> 4) & 0x07) << 1u) + 1u;
+ decodedByte += nonRunLength;
+ // First value.
+ const uint8_t prev0 = DALI_UNLIKELY(y == 0) ? 0 : (*(outBufferPtr - widthByte)) & 0x0f;
+ const uint8_t v0 = (prev0 + (v & 0x0f)) & 0x0f;
+ *(outBufferPtr++) = (v0 << 4) | v0;
+
+ const bool ignoreLastValue = decodedByte > widthByte ? true : false;
+ if(DALI_UNLIKELY(ignoreLastValue))
+ {
+ --decodedByte;
+ for(uint8_t iter = 1; iter + 2 < nonRunLength; iter += 2)
+ {
+ const uint8_t w = *(inBufferPtr++);
+ const uint8_t prew0 = DALI_UNLIKELY(y == 0) ? 0 : (*(outBufferPtr - widthByte)) & 0x0f;
+ const uint8_t w0 = (prew0 + ((w >> 4) & 0x0f)) & 0x0f;
+ const uint8_t prew1 = DALI_UNLIKELY(y == 0) ? 0 : (*(outBufferPtr - widthByte + 1)) & 0x0f;
+ const uint8_t w1 = (prew1 + (w & 0x0f)) & 0x0f;
+ ++x;
+
+ *(outBufferPtr++) = (w0 << 4) | w0;
+ *(outBufferPtr++) = (w1 << 4) | w1;
+ }
+ // Last value.
+ {
+ const uint8_t w = ((*(inBufferPtr++)) >> 4) & 0x0f;
+ const uint8_t prew0 = DALI_UNLIKELY(y == 0) ? 0 : (*(outBufferPtr - widthByte)) & 0x0f;
+ const uint8_t w0 = (prew0 + w) & 0x0f;
+ ++x;
+
+ *(outBufferPtr++) = (w0 << 4) | w0;
+ }
+ }
+ else
+ {
+ for(uint8_t iter = 1; iter < nonRunLength; iter += 2)
+ {
+ const uint8_t w = *(inBufferPtr++);
+ const uint8_t prew0 = DALI_UNLIKELY(y == 0) ? 0 : (*(outBufferPtr - widthByte)) & 0x0f;
+ const uint8_t w0 = (prew0 + ((w >> 4) & 0x0f)) & 0x0f;
+ const uint8_t prew1 = DALI_UNLIKELY(y == 0) ? 0 : (*(outBufferPtr - widthByte + 1)) & 0x0f;
+ const uint8_t w1 = (prew1 + (w & 0x0f)) & 0x0f;
+ ++x;
+
+ *(outBufferPtr++) = (w0 << 4) | w0;
+ *(outBufferPtr++) = (w1 << 4) | w1;
+ }
+ }
+ }
+ }
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+}
+
+void FontClient::GlyphBufferData::DecompressScanline(const GlyphBufferData& __restrict__ inBufferData, uint8_t* __restrict__ outBuffer, uint32_t& __restrict__ offset)
+{
+ switch(inBufferData.compressType)
+ {
+ case TextAbstraction::FontClient::GlyphBufferData::CompressType::NO_COMPRESS:
+ {
+ const auto bufferSize = inBufferData.width * Pixel::GetBytesPerPixel(inBufferData.format);
+
+ // Copy buffer without compress
+ memcpy(outBuffer, inBufferData.buffer + offset, bufferSize);
+
+ // Update offset
+ offset += bufferSize;
+ break;
+ }
+ case TextAbstraction::FontClient::GlyphBufferData::CompressType::BIT_PER_PIXEL_4:
+ {
+ const uint32_t widthByte = inBufferData.width * Pixel::GetBytesPerPixel(inBufferData.format);
+ const uint32_t componentCount = (widthByte >> 1);
+ const bool considerPadding = (widthByte & 1) ? true : false;
+
+ uint8_t* __restrict__ outBufferPtr = outBuffer;
+ const uint8_t* __restrict__ inBufferPtr = inBufferData.buffer + offset;
+
+ // Decompress scanline
+ for(uint32_t x = 0; x < componentCount; ++x)
+ {
+ const uint8_t v = *(inBufferPtr++);
+ const uint8_t v0 = (v >> 4) & 0x0f;
+ const uint8_t v1 = v & 0x0f;
+
+ *(outBufferPtr++) = (v0 << 4) | v0;
+ *(outBufferPtr++) = (v1 << 4) | v1;
+ }
+ if(considerPadding)
+ {
+ const uint8_t v = *(inBufferPtr++);
+ *(outBufferPtr++) = (v << 4) | v;
+ }
+
+ // Update offset
+ offset += (widthByte + 1u) >> 1u;
+ break;
+ }
+ case TextAbstraction::FontClient::GlyphBufferData::CompressType::COMPRESS_RLE4:
+ {
+ const uint32_t widthByte = inBufferData.width * Pixel::GetBytesPerPixel(inBufferData.format);
+
+ uint8_t* __restrict__ outBufferPtr = outBuffer;
+ const uint8_t* __restrict__ inBufferPtr = inBufferData.buffer + offset;
+
+ // If offset is zero, fill outBuffer as 0 first.
+ if(DALI_UNLIKELY(offset == 0))
+ {
+ memset(outBufferPtr, 0, widthByte);
+ }
+
+ // Decompress scanline
+ uint32_t decodedByte = 0;
+ while(decodedByte < widthByte)
+ {
+ const uint8_t v = *(inBufferPtr++);
+ ++offset;
+ // Compress by RLE
+ if(v & 0x80)
+ {
+ const uint8_t runLength = ((v >> 4) & 0x07) + 2u;
+ decodedByte += runLength;
+ const uint8_t repeatValue = (v & 0x0f);
+ for(uint8_t iter = 0; iter < runLength; ++iter)
+ {
+ const uint8_t prev0 = (*(outBufferPtr)) & 0x0f;
+ const uint8_t v0 = (prev0 + repeatValue) & 0x0f;
+ *(outBufferPtr++) = (v0 << 4) | v0;
+ }
+ }
+ // Not compress by RLE
+ else
+ {
+ const uint8_t nonRunLength = (((v >> 4) & 0x07) << 1u) + 1u;
+ decodedByte += nonRunLength;
+ // First value.
+ const uint8_t prev0 = (*(outBufferPtr)) & 0x0f;
+ const uint8_t v0 = (prev0 + (v & 0x0f)) & 0x0f;
+ *(outBufferPtr++) = (v0 << 4) | v0;
+
+ const bool ignoreLastValue = decodedByte > widthByte ? true : false;
+ if(DALI_UNLIKELY(ignoreLastValue))
+ {
+ --decodedByte;
+ for(uint8_t iter = 1; iter + 2 < nonRunLength; iter += 2)
+ {
+ const uint8_t w = *(inBufferPtr++);
+ const uint8_t prew0 = (*(outBufferPtr)) & 0x0f;
+ const uint8_t w0 = (prew0 + ((w >> 4) & 0x0f)) & 0x0f;
+ const uint8_t prew1 = (*(outBufferPtr + 1)) & 0x0f;
+ const uint8_t w1 = (prew1 + (w & 0x0f)) & 0x0f;
+ ++offset;
+
+ *(outBufferPtr++) = (w0 << 4) | w0;
+ *(outBufferPtr++) = (w1 << 4) | w1;
+ }
+ // Last value.
+ {
+ const uint8_t w = ((*(inBufferPtr++)) >> 4) & 0x0f;
+ const uint8_t prew0 = (*(outBufferPtr)) & 0x0f;
+ const uint8_t w0 = (prew0 + w) & 0x0f;
+ ++offset;
+
+ *(outBufferPtr++) = (w0 << 4) | w0;
+ }
+ }
+ else
+ {
+ for(uint8_t iter = 1; iter < nonRunLength; iter += 2)
+ {
+ const uint8_t w = *(inBufferPtr++);
+ const uint8_t prew0 = (*(outBufferPtr)) & 0x0f;
+ const uint8_t w0 = (prew0 + ((w >> 4) & 0x0f)) & 0x0f;
+ const uint8_t prew1 = (*(outBufferPtr + 1)) & 0x0f;
+ const uint8_t w1 = (prew1 + (w & 0x0f)) & 0x0f;
+ ++offset;
+
+ *(outBufferPtr++) = (w0 << 4) | w0;
+ *(outBufferPtr++) = (w1 << 4) | w1;
+ }
+ }
+ }
+ }
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+}
+
+// FontClient
FontClient FontClient::Get()
{
#define DALI_PLATFORM_TEXT_ABSTRACTION_FONT_CLIENT_H
/*
- * Copyright (c) 2021 Samsung Electronics Co., Ltd.
+ * Copyright (c) 2022 Samsung Electronics Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*/
~GlyphBufferData();
- unsigned char* buffer; ///< The glyph's bitmap buffer data.
- unsigned int width; ///< The width of the bitmap.
- unsigned int height; ///< The height of the bitmap.
- int outlineOffsetX; ///< The additional horizontal offset to be added for the glyph's position for outline.
- int outlineOffsetY; ///< The additional vertical offset to be added for the glyph's position for outline.
- Pixel::Format format; ///< The pixel's format of the bitmap.
- bool isColorEmoji : 1; ///< Whether the glyph is an emoji.
- bool isColorBitmap : 1; ///< Whether the glyph is a color bitmap.
+ // Compressed method of buffer. Each buffer compressed line by line
+ enum class CompressType
+ {
+ NO_COMPRESS = 0, // No compression
+ BIT_PER_PIXEL_4 = 1, // Compress as 4 bit. Color become value * 17 (0x00, 0x11, 0x22, ... 0xee, 0xff). Only works for Pixel::L8 format
+ COMPRESS_RLE4 = 2, // Compress as 4 bit, and Run-Length-Encode. For more high compress rate, we store difference between previous scanline. Only works for Pixel::L8 format
+ };
+
+ // Compress priority of buffer.
+ enum class CompressPolicyType
+ {
+ SPEED = 0,
+ MEMORY = 1,
+ };
+
+ /**
+ * @brief Helper static function to compress raw buffer from inBuffer to outBufferData.buffer.
+ * outBufferData will have it's own buffer.
+ *
+ * @pre outBufferData must not have it's own buffer.
+ * @param[in] inBuffer The input raw data.
+ * @param[in] outBufferData The output glyph buffer data.
+ * @return Size of compressed out buffer, Or 0 if compress failed.
+ */
+ static size_t Compress(const uint8_t* const inBuffer, GlyphBufferData& outBufferData);
+
+ /**
+ * @brief Helper static function to decompress raw buffer from inBuffer to outBufferPtr.
+ * If outBuffer is nullptr, Do nothing.
+ *
+ * @pre outBuffer memory should be allocated.
+ * @param[in] inBufferData The input glyph buffer data.
+ * @param[in] outBuffer The output pointer of raw buffer data.
+ */
+ static void Decompress(const GlyphBufferData& inBufferData, uint8_t* outBuffer);
+
+ /**
+ * @brief Special Helper static function to decompress raw buffer from inBuffer to outBuffer one scanline.
+ * After decompress one scanline successed, offset will be changed.
+ *
+ * @pre outBuffer memory should be allocated.
+ * @param[in] inBufferData The input glyph buffer data.
+ * @param[in] outBuffer The output pointer of raw buffer data.
+ * @param[in, out] offset The offset of input. It will be changed if decompress scanline successed.
+ */
+ static void DecompressScanline(const GlyphBufferData& inBufferData, uint8_t* outBuffer, uint32_t& offset);
+
+ uint8_t* buffer; ///< The glyph's bitmap buffer data.
+ uint32_t width; ///< The width of the bitmap.
+ uint32_t height; ///< The height of the bitmap.
+ int outlineOffsetX; ///< The additional horizontal offset to be added for the glyph's position for outline.
+ int outlineOffsetY; ///< The additional vertical offset to be added for the glyph's position for outline.
+ Pixel::Format format; ///< The pixel's format of the bitmap.
+ CompressType compressType : 3; ///< The type of buffer compression.
+ bool isColorEmoji : 1; ///< Whether the glyph is an emoji.
+ bool isColorBitmap : 1; ///< Whether the glyph is a color bitmap.
+ bool isBufferOwned : 1; ///< Whether the glyph's bitmap buffer data owned by this class or not. Becareful when you use non-owned buffer data.
};
/**
LinearSampleGeneric<Pixel4Bytes, BilinearFilter4Bytes, true>(inPixels, inputDimensions, inputStride, outPixels, desiredDimensions);
}
+// Dispatch to a format-appropriate linear sampling function:
+void LinearSample(const unsigned char* __restrict__ inPixels,
+ ImageDimensions inDimensions,
+ unsigned int inStride,
+ Pixel::Format pixelFormat,
+ unsigned char* __restrict__ outPixels,
+ ImageDimensions outDimensions)
+{
+ // Check the pixel format is one that is supported:
+ if(pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::RGB565)
+ {
+ switch(pixelFormat)
+ {
+ case Pixel::RGB888:
+ {
+ LinearSample3BPP(inPixels, inDimensions, inStride, outPixels, outDimensions);
+ break;
+ }
+ case Pixel::RGBA8888:
+ {
+ LinearSample4BPP(inPixels, inDimensions, inStride, outPixels, outDimensions);
+ break;
+ }
+ case Pixel::L8:
+ case Pixel::A8:
+ {
+ LinearSample1BPP(inPixels, inDimensions, inStride, outPixels, outDimensions);
+ break;
+ }
+ case Pixel::LA88:
+ {
+ LinearSample2BPP(inPixels, inDimensions, inStride, outPixels, outDimensions);
+ break;
+ }
+ case Pixel::RGB565:
+ {
+ LinearSampleRGB565(inPixels, inDimensions, inStride, outPixels, outDimensions);
+ break;
+ }
+ default:
+ {
+ DALI_ASSERT_DEBUG(0 == "Inner branch conditions don't match outer branch.");
+ }
+ }
+ }
+ else
+ {
+ DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not linear sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat));
+ }
+}
+
void Resample(const unsigned char* __restrict__ inPixels,
ImageDimensions inputDimensions,
unsigned int inputStride,
Resample(inPixels, inputDimensions, inputStride, outPixels, desiredDimensions, Resampler::LANCZOS4, 1, false);
}
-// Dispatch to a format-appropriate linear sampling function:
-void LinearSample(const unsigned char* __restrict__ inPixels,
- ImageDimensions inDimensions,
- unsigned int inStride,
- Pixel::Format pixelFormat,
- unsigned char* __restrict__ outPixels,
- ImageDimensions outDimensions)
+// Dispatch to a format-appropriate third-party resampling function:
+void LanczosSample(const unsigned char* __restrict__ inPixels,
+ ImageDimensions inDimensions,
+ unsigned int inStride,
+ Pixel::Format pixelFormat,
+ unsigned char* __restrict__ outPixels,
+ ImageDimensions outDimensions)
{
// Check the pixel format is one that is supported:
- if(pixelFormat == Pixel::RGB888 || pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8 || pixelFormat == Pixel::LA88 || pixelFormat == Pixel::RGB565)
+ if(pixelFormat == Pixel::RGBA8888 || pixelFormat == Pixel::BGRA8888 || pixelFormat == Pixel::L8 || pixelFormat == Pixel::A8)
{
switch(pixelFormat)
{
- case Pixel::RGB888:
- {
- LinearSample3BPP(inPixels, inDimensions, inStride, outPixels, outDimensions);
- break;
- }
case Pixel::RGBA8888:
+ case Pixel::BGRA8888:
{
- LinearSample4BPP(inPixels, inDimensions, inStride, outPixels, outDimensions);
+ LanczosSample4BPP(inPixels, inDimensions, inStride, outPixels, outDimensions);
break;
}
case Pixel::L8:
case Pixel::A8:
{
- LinearSample1BPP(inPixels, inDimensions, inStride, outPixels, outDimensions);
- break;
- }
- case Pixel::LA88:
- {
- LinearSample2BPP(inPixels, inDimensions, inStride, outPixels, outDimensions);
- break;
- }
- case Pixel::RGB565:
- {
- LinearSampleRGB565(inPixels, inDimensions, inStride, outPixels, outDimensions);
+ LanczosSample1BPP(inPixels, inDimensions, inStride, outPixels, outDimensions);
break;
}
default:
}
else
{
- DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not linear sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat));
+ DALI_LOG_INFO(gImageOpsLogFilter, Dali::Integration::Log::Verbose, "Bitmap was not lanczos sampled: unsupported pixel format: %u.\n", unsigned(pixelFormat));
}
}
ImageDimensions desiredDimensions);
/**
+ * @brief Resample input image to output image using a Lanczos algorithm.
+ *
+ * @pre @p inPixels must not alias @p outPixels. The input image should be a totally
+ * separate buffer from the output buffer.
+ *
+ * @param[in] inPixels Pointer to the input image buffer.
+ * @param[in] inputDimensions The input dimensions of the image.
+ * @param[in] inputStride The input stride of the image.
+ * @param[in] pixelFormat The format of the image pointed at by pixels.
+ * @param[out] outPixels Pointer to the output image buffer.
+ * @param[in] desiredDimensions The output dimensions of the image.
+ */
+void LanczosSample(const unsigned char* __restrict__ inPixels,
+ ImageDimensions inDimensions,
+ unsigned int inStride,
+ Pixel::Format pixelFormat,
+ unsigned char* __restrict__ outPixels,
+ ImageDimensions outDimensions);
+
+/**
* @brief Resamples the input image with the Lanczos algorithm.
*
* @pre @p inPixels must not alias @p outPixels. The input image should be a totally
* @param y The value between [0..255]
* @return (x*y)/255
*/
-inline uint8_t MultiplyAndNormalizeColor(const uint8_t& x, const uint8_t& y) noexcept
+inline uint8_t MultiplyAndNormalizeColor(const uint8_t x, const uint8_t y) noexcept
{
const uint32_t xy = static_cast<const uint32_t>(x) * y;
return ((xy << 15) + (xy << 7) + xy) >> 23;
}
+/**
+ * @brief Fast division by 17 and roundup. It will be useful when we compress 8bit luminance value as 4bit for text glyph.
+ *
+ * @param x The value between [0..255]
+ * @return round(x / 17.0f).(same as (x+8)/17)
+ */
+inline uint8_t CompressBitPerPixel8To4(const uint8_t x) noexcept
+{
+ return ((((static_cast<const uint16_t>(x) << 4) - x + (x >> 4)) >> 7) + 1) >> 1;
+}
+
/**@}*/
} /* namespace Platform */
// Retrieve the image
TextAbstraction::FontClient::GlyphBufferData data;
- std::unique_ptr<GlyphBuffer> glyphBufferPtr(new GlyphBuffer(data, GlyphBuffer::FREE));
if(isEmoji)
{
data.width = parameters.glyphs[run.glyphIndex].width;
unsigned int heightOut = data.height;
const unsigned int pixelSize = Pixel::GetBytesPerPixel(data.format);
+ // If we need to decompress, create new memory and replace ownership.
+ if(data.compressType != TextAbstraction::FontClient::GlyphBufferData::CompressType::NO_COMPRESS)
+ {
+ uint8_t* newBuffer = (uint8_t*)malloc(widthOut * heightOut * pixelSize);
+ if(DALI_LIKELY(newBuffer != nullptr))
+ {
+ TextAbstraction::FontClient::GlyphBufferData::Decompress(data, newBuffer);
+ if(data.isBufferOwned)
+ {
+ // Release previous buffer
+ free(data.buffer);
+ }
+ data.isBufferOwned = true;
+ data.buffer = newBuffer;
+ data.compressType = TextAbstraction::FontClient::GlyphBufferData::CompressType::NO_COMPRESS;
+ }
+ }
+
Dali::Internal::Platform::RotateByShear(data.buffer,
data.width,
data.height,
heightOut);
if(nullptr != pixelsOut)
{
- free(data.buffer);
- data.buffer = pixelsOut;
- data.width = widthOut;
- data.height = heightOut;
+ if(data.isBufferOwned)
+ {
+ free(data.buffer);
+ }
+ data.isBufferOwned = true;
+ data.compressType = Dali::TextAbstraction::FontClient::GlyphBufferData::CompressType::NO_COMPRESS;
+ data.buffer = pixelsOut;
+ data.width = widthOut;
+ data.height = heightOut;
}
glyphX = centerX - 0.5 * static_cast<double>(data.width);
data.isColorBitmap = font.isColorFont;
- ConvertBitmap(data, data.width, data.height, pixelBuffer.GetBuffer());
-
- // Sets the pixel format.
- data.format = pixelBuffer.GetPixelFormat();
+ ConvertBitmap(data, data.width, data.height, pixelBuffer.GetBuffer(), pixelBuffer.GetPixelFormat());
break;
}
++index;
Devel::PixelBuffer pixelBuffer = pixelBufferCache[pixelBufferId - 1u].pixelBuffer;
if(pixelBuffer)
{
- ConvertBitmap(data, pixelBuffer.GetWidth(), pixelBuffer.GetHeight(), pixelBuffer.GetBuffer());
-
- // Sets the pixel format.
- data.format = pixelBuffer.GetPixelFormat();
+ ConvertBitmap(data, pixelBuffer.GetWidth(), pixelBuffer.GetHeight(), pixelBuffer.GetBuffer(), pixelBuffer.GetPixelFormat());
}
}
else
{
+ data.isBufferOwned = true;
+ data.compressType = Dali::TextAbstraction::FontClient::GlyphBufferData::CompressType::NO_COMPRESS;
+
// Creates the output buffer
- const unsigned int bufferSize = data.width * data.height * 4u;
- data.buffer = (uint8_t*)malloc(bufferSize * sizeof(uint8_t)); // @note The caller is responsible for deallocating the bitmap data using free.
+ const uint32_t bufferSize = data.width * data.height * 4u;
+ data.buffer = (uint8_t*)malloc(bufferSize); // @note The caller is responsible for deallocating the bitmap data using free.
memset(data.buffer, 0u, bufferSize);
* @param[in] srcHeight The height of the bitmap.
* @param[in] srcBuffer The buffer of the bitmap.
*/
-void ConvertBitmap(TextAbstraction::FontClient::GlyphBufferData& data, unsigned int srcWidth, unsigned int srcHeight, const unsigned char* const srcBuffer)
+void ConvertBitmap(TextAbstraction::FontClient::GlyphBufferData& data, unsigned int srcWidth, unsigned int srcHeight, const unsigned char* const srcBuffer, const Pixel::Format srcFormat)
{
// Set the input dimensions.
const ImageDimensions inputDimensions(srcWidth, srcHeight);
data.height = (data.height == 0) ? srcHeight : data.height;
const ImageDimensions desiredDimensions(data.width, data.height);
+ data.format = srcFormat;
+
+ // Note we don't compress here
+ data.compressType = TextAbstraction::FontClient::GlyphBufferData::CompressType::NO_COMPRESS;
+
+ const uint32_t bytePerPixel = Dali::Pixel::GetBytesPerPixel(srcFormat);
+
// Creates the output buffer
- const unsigned int bufferSize = data.width * data.height * 4u;
- data.buffer = (uint8_t*)malloc(bufferSize * sizeof(uint8_t)); // @note The caller is responsible for deallocating the bitmap data using free.
+ const uint32_t bufferSize = data.width * data.height * bytePerPixel;
if(inputDimensions == desiredDimensions)
{
// There isn't downscaling.
- memcpy(data.buffer, srcBuffer, bufferSize);
+ data.isBufferOwned = false;
+ data.buffer = const_cast<uint8_t*>(srcBuffer);
}
else
{
- Dali::Internal::Platform::LanczosSample4BPP(srcBuffer,
- inputDimensions,
- srcWidth,
- data.buffer,
- desiredDimensions);
+ data.isBufferOwned = true;
+ data.buffer = (uint8_t*)malloc(bufferSize); // @note The caller is responsible for deallocating the bitmap data using free.
+ Dali::Internal::Platform::LanczosSample(srcBuffer,
+ inputDimensions,
+ srcWidth,
+ srcFormat,
+ data.buffer,
+ desiredDimensions);
}
}
*/
void ConvertBitmap(TextAbstraction::FontClient::GlyphBufferData& data, FT_Bitmap& srcBitmap, bool isShearRequired, bool moveBuffer)
{
+ data.buffer = nullptr;
if(srcBitmap.width * srcBitmap.rows > 0)
{
switch(srcBitmap.pixel_mode)
data.height = height;
data.format = Pixel::L8; // Sets the pixel format.
+ // Note we don't compress here
+ data.compressType = TextAbstraction::FontClient::GlyphBufferData::CompressType::NO_COMPRESS;
+
if(moveBuffer)
{
- data.buffer = pixelsIn;
+ data.isBufferOwned = true;
+ data.buffer = pixelsIn;
// Happy trick for copyless convert bitmap!
srcBitmap.buffer = nullptr;
}
else
{
- const unsigned int bufferSize = width * height;
- data.buffer = (uint8_t*)malloc(bufferSize * sizeof(uint8_t)); // @note The caller is responsible for deallocating the bitmap data using free.
-
- memcpy(data.buffer, pixelsIn, bufferSize);
+ data.isBufferOwned = false;
+ data.buffer = pixelsIn;
}
if(releaseRequiredPixelPtr)
if(srcBitmap.pitch == static_cast<int>(srcBitmap.width << 2u))
{
// Color glyph doesn't support copyless convert bitmap. Just memcpy
- ConvertBitmap(data, srcBitmap.width, srcBitmap.rows, srcBitmap.buffer);
-
- // Sets the pixel format.
- data.format = Pixel::BGRA8888;
+ ConvertBitmap(data, srcBitmap.width, srcBitmap.rows, srcBitmap.buffer, Pixel::BGRA8888);
}
break;
}
void ConvertBitmap(TextAbstraction::FontClient::GlyphBufferData& data,
unsigned int srcWidth,
unsigned int srcHeight,
- const unsigned char* const srcBuffer);
+ const unsigned char* const srcBuffer,
+ const Pixel::Format srcFormat);
void ConvertBitmap(TextAbstraction::FontClient::GlyphBufferData& data,
FT_Bitmap& srcBitmap,
* @brief Maximum size of glyph cache per each font face.
*/
constexpr std::size_t DEFAULT_GLYPH_CACHE_MAX = 128;
-constexpr std::size_t MINIMUM_SIZE_OF_GLYPH_CACHE_MAX = 2u;
+constexpr std::size_t MINIMUM_SIZE_OF_GLYPH_CACHE_MAX = 3u;
constexpr auto MAX_NUMBER_OF_GLYPH_CACHE_ENV = "DALI_GLYPH_CACHE_MAX";
* @note This value fixed when we call it first time.
* @return The max size of glyph cache.
*/
-size_t GetMaxNumberOfGlyphCache()
+inline const size_t GetMaxNumberOfGlyphCache()
{
using Dali::EnvironmentVariable::GetEnvironmentVariable;
static auto numberString = GetEnvironmentVariable(MAX_NUMBER_OF_GLYPH_CACHE_ENV);
static auto number = numberString ? std::strtoul(numberString, nullptr, 10) : DEFAULT_GLYPH_CACHE_MAX;
return (number < MINIMUM_SIZE_OF_GLYPH_CACHE_MAX) ? MINIMUM_SIZE_OF_GLYPH_CACHE_MAX : number;
}
+
+/**
+ * @brief Behavior about cache the rendered glyph cache.
+ */
+constexpr bool DEFAULT_ENABLE_CACHE_RENDERED_GLYPH = true;
+constexpr auto ENABLE_CACHE_RENDERED_GLYPH_ENV = "DALI_ENABLE_CACHE_RENDERED_GLYPH";
+
+/**
+ * @brief Get whether we allow to cache rendered glyph from environment.
+ * If not settuped, default as true.
+ * @note This value fixed when we call it first time.
+ * @return True if we allow to cache rendered glyph.
+ */
+inline const bool EnableCacheRenderedGlyph()
+{
+ using Dali::EnvironmentVariable::GetEnvironmentVariable;
+ static auto numberString = GetEnvironmentVariable(ENABLE_CACHE_RENDERED_GLYPH_ENV);
+ static auto number = numberString ? (std::strtoul(numberString, nullptr, 10) ? true : false) : DEFAULT_ENABLE_CACHE_RENDERED_GLYPH;
+ return number;
+}
+
+/**
+ * @brief Policy about compress the cached rendered glyph.
+ * It will be used only if CacheRenderedGlyph is enabled
+ */
+constexpr auto DEFAULT_RENDERED_GLYPH_COMPRESS_POLICY =
+#if !(defined(DALI_PROFILE_UBUNTU) || defined(ANDROID) || defined(WIN32) || defined(__APPLE__))
+ TextAbstraction::FontClient::GlyphBufferData::CompressPolicyType::MEMORY; // If tizen target
+#else
+ TextAbstraction::FontClient::GlyphBufferData::CompressPolicyType::SPEED; // If not tizen target
+#endif
+constexpr auto RENDERED_GLYPH_COMPRESS_POLICY_ENV = "DALI_RENDERED_GLYPH_COMPRESS_POLICY";
+
+/**
+ * @brief Get whether we allow to cache rendered glyph from environment.
+ * If not settuped, default value used, as defined above.
+ * @note This value fixed when we call it first time.
+ * @return SPEED if value start with 's' or 'S'. MEMORY if value start with 'm' or 'M'. otherwise, use default
+ */
+inline const TextAbstraction::FontClient::GlyphBufferData::CompressPolicyType GetRenderedGlyphCompressPolicy()
+{
+ using Dali::EnvironmentVariable::GetEnvironmentVariable;
+ static auto policyString = GetEnvironmentVariable(RENDERED_GLYPH_COMPRESS_POLICY_ENV);
+
+ static auto policy = policyString ? policyString[0] == 's' || policyString[0] == 'S' ? TextAbstraction::FontClient::GlyphBufferData::CompressPolicyType::SPEED
+ : policyString[0] == 'm' || policyString[0] == 'M' ? TextAbstraction::FontClient::GlyphBufferData::CompressPolicyType::MEMORY
+ : DEFAULT_RENDERED_GLYPH_COMPRESS_POLICY
+ : DEFAULT_RENDERED_GLYPH_COMPRESS_POLICY;
+ return policy;
+}
} // namespace
FontFaceCacheItem::FontFaceCacheItem(FT_Library& freeTypeLibrary,
* @brief Create a bitmap representation of a glyph from a face font
*
* @param[in] glyphIndex The index of a glyph within the specified font.
- * @param[in] isItalicRequired Whether the glyph requires italic style.
- * @param[in] isBoldRequired Whether the glyph requires bold style.
* @param[out] data The bitmap data.
* @param[in] outlineWidth The width of the glyph outline in pixels.
+ * @param[in] isItalicRequired Whether the glyph requires italic style.
+ * @param[in] isBoldRequired Whether the glyph requires bold style.
*/
void FontFaceCacheItem::CreateBitmap(
GlyphIndex glyphIndex, Dali::TextAbstraction::FontClient::GlyphBufferData& data, int outlineWidth, bool isItalicRequired, bool isBoldRequired) const
{
GlyphCacheManager::GlyphCacheData glyphData;
FT_Error error;
+ FT_Int32 loadFlag;
// For the software italics.
bool isShearRequired = false;
// Check to see if this is fixed size bitmap
if(mIsFixedSizeBitmap)
{
- mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphIndex, FT_LOAD_COLOR, isBoldRequired, glyphData, error);
+ loadFlag = FT_LOAD_COLOR;
}
else
#endif
// FT_LOAD_DEFAULT causes some issues in the alignment of the glyph inside the bitmap.
// i.e. with the SNum-3R font.
// @todo: add an option to use the FT_LOAD_DEFAULT if required?
- mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphIndex, FT_LOAD_NO_AUTOHINT, isBoldRequired, glyphData, error);
+ loadFlag = FT_LOAD_NO_AUTOHINT;
}
+ mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphIndex, loadFlag, isBoldRequired, glyphData, error);
+
if(FT_Err_Ok == error)
{
if(isItalicRequired && !(glyphData.mStyleFlags & FT_STYLE_FLAG_ITALIC))
isShearRequired = true;
}
- // Convert to bitmap if necessary
if(!glyphData.mIsBitmap)
{
+ // Convert to bitmap if necessary
FT_Glyph glyph = glyphData.mGlyph;
DALI_ASSERT_ALWAYS(glyph->format != FT_GLYPH_FORMAT_BITMAP && "Something wrong with cashing. Some bitmap glyph cached failed.");
}
}
- // Copy new glyph, and keep original cached glyph.
- // If we already copy new glyph by stroke, just re-use that.
- error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, isStrokeGlyphSuccess);
- if(FT_Err_Ok == error)
- {
- FT_BitmapGlyph bitmapGlyph = reinterpret_cast<FT_BitmapGlyph>(glyph);
-
- if(isOutlineGlyph)
- {
- // Calculate the additional horizontal and vertical offsets needed for the position of the outline glyph
- data.outlineOffsetX = offsetX - bitmapGlyph->left - outlineWidth;
- data.outlineOffsetY = bitmapGlyph->top - offsetY - outlineWidth;
- }
-
- // Move bitmap buffer into data.buffer
- ConvertBitmap(data, bitmapGlyph->bitmap, isShearRequired, true);
+ const bool ableUseCachedRenderedGlyph = EnableCacheRenderedGlyph() && !isOutlineGlyph && !isShearRequired;
- // Copied FT_Glyph object must be released with FT_Done_Glyph
- FT_Done_Glyph(glyph);
+ // If we cache rendered glyph, and if we can use it, use cached thing first.
+ if(ableUseCachedRenderedGlyph && glyphData.mRenderedBuffer)
+ {
+ data.buffer = glyphData.mRenderedBuffer->buffer;
+ data.width = glyphData.mRenderedBuffer->width;
+ data.height = glyphData.mRenderedBuffer->height;
+ data.format = glyphData.mRenderedBuffer->format;
+ data.compressType = glyphData.mRenderedBuffer->compressType;
+ data.isBufferOwned = false;
}
else
{
- DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::CreateBitmap. FT_Get_Glyph Failed with error: %d\n", error);
+ // Copy new glyph, and keep original cached glyph.
+ // If we already copy new glyph by stroke, just re-use that.
+ error = FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, isStrokeGlyphSuccess);
+ if(FT_Err_Ok == error)
+ {
+ FT_BitmapGlyph bitmapGlyph = reinterpret_cast<FT_BitmapGlyph>(glyph);
+
+ if(isOutlineGlyph)
+ {
+ // Calculate the additional horizontal and vertical offsets needed for the position of the outline glyph
+ data.outlineOffsetX = offsetX - bitmapGlyph->left - outlineWidth;
+ data.outlineOffsetY = bitmapGlyph->top - offsetY - outlineWidth;
+ }
+
+ // If we can cache this bitmapGlyph, store it.
+ // Note : We will call this API once per each glyph.
+ if(ableUseCachedRenderedGlyph)
+ {
+ mGlyphCacheManager->CacheRenderedGlyphBuffer(glyphIndex, loadFlag, isBoldRequired, bitmapGlyph->bitmap, GetRenderedGlyphCompressPolicy());
+
+ GlyphCacheManager::GlyphCacheData dummyData;
+ mGlyphCacheManager->GetGlyphCacheDataFromIndex(glyphIndex, loadFlag, isBoldRequired, dummyData, error);
+
+ if(DALI_LIKELY(FT_Err_Ok == error && dummyData.mRenderedBuffer))
+ {
+ data.buffer = dummyData.mRenderedBuffer->buffer;
+ data.width = dummyData.mRenderedBuffer->width;
+ data.height = dummyData.mRenderedBuffer->height;
+ data.format = dummyData.mRenderedBuffer->format;
+ data.compressType = dummyData.mRenderedBuffer->compressType;
+ data.isBufferOwned = false;
+ }
+ else
+ {
+ // Something problem during cache or get rendered glyph buffer.
+ // Move bitmap buffer into data.buffer
+ ConvertBitmap(data, bitmapGlyph->bitmap, isShearRequired, true);
+ }
+ }
+ else
+ {
+ // Move bitmap buffer into data.buffer
+ ConvertBitmap(data, bitmapGlyph->bitmap, isShearRequired, true);
+ }
+
+ // Copied FT_Glyph object must be released with FT_Done_Glyph
+ FT_Done_Glyph(glyph);
+ }
+ else
+ {
+ DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::CreateBitmap. FT_Get_Glyph Failed with error: %d\n", error);
+ }
}
}
else
* limitations under the License.
*/
+// CLASS HEADER
+#include <dali/internal/text/text-abstraction/plugin/font-face-glyph-cache-manager.h>
+
// INTERNAL INCLUDES
#include <dali/integration-api/debug.h>
#include <dali/internal/imaging/common/image-operations.h>
#include <dali/internal/text/text-abstraction/plugin/font-client-utils.h>
-#include <dali/internal/text/text-abstraction/plugin/font-face-glyph-cache-manager.h>
// EXTERNAL INCLUDES
#include FT_BITMAP_H
{
namespace
{
+constexpr uint32_t THRESHOLD_WIDTH_FOR_BPP_COMPRESS = 8; // The smallest width of glyph that we use RLE4 method.
} // namespace
GlyphCacheManager::GlyphCacheManager(FT_Face ftFace, std::size_t maxNumberOfGlyphCache)
{
DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, "FontClient::Plugin::GlyphCacheManager Create with maximum size : %d\n", static_cast<int>(mGlyphCacheMaxSize));
}
+
GlyphCacheManager::~GlyphCacheManager()
{
while(!mLRUGlyphCache.IsEmpty())
}
bool GlyphCacheManager::GetGlyphCacheDataFromIndex(
- const GlyphIndex& index,
- const FT_Int32& flag,
- const bool& isBoldRequired,
- GlyphCacheData& glyphData,
- FT_Error& error)
+ const GlyphIndex index,
+ const FT_Int32 flag,
+ const bool isBoldRequired,
+ GlyphCacheData& glyphData,
+ FT_Error& error)
{
// Append some error value here instead of FT_Err_Ok.
error = static_cast<FT_Error>(-1);
}
bool GlyphCacheManager::LoadGlyphDataFromIndex(
- const GlyphIndex& index,
- const FT_Int32& flag,
- const bool& isBoldRequired,
- GlyphCacheData& glyphData,
- FT_Error& error)
+ const GlyphIndex index,
+ const FT_Int32 flag,
+ const bool isBoldRequired,
+ GlyphCacheData& glyphData,
+ FT_Error& error)
{
error = FT_Load_Glyph(mFreeTypeFace, index, flag);
if(FT_Err_Ok == error)
if(bufferSize > 0)
{
glyphData.mIsBitmap = true;
- glyphData.mBitmap->buffer = new uint8_t[bufferSize];
+ glyphData.mBitmap->buffer = (uint8_t*)malloc(bufferSize * sizeof(uint8_t)); // @note The caller is responsible for deallocating the bitmap data using free.
memcpy(glyphData.mBitmap->buffer, mFreeTypeFace->glyph->bitmap.buffer, bufferSize);
}
else
}
void GlyphCacheManager::ResizeBitmapGlyph(
- const GlyphIndex& index,
- const FT_Int32& flag,
- const bool& isBoldRequired,
- const uint32_t& desiredWidth,
- const uint32_t& desiredHeight)
+ const GlyphIndex index,
+ const FT_Int32 flag,
+ const bool isBoldRequired,
+ const uint32_t desiredWidth,
+ const uint32_t desiredHeight)
{
+ if(desiredWidth * desiredHeight <= 0)
+ {
+ // Skip this API if desired size is zero
+ return;
+ }
FT_Error error;
GlyphCacheData originGlyphData;
if(GetGlyphCacheDataFromIndex(index, flag, isBoldRequired, originGlyphData, error))
const bool requiredResize = (originGlyphData.mBitmap->rows != desiredHeight) || (originGlyphData.mBitmap->width != desiredWidth);
if(requiredResize)
{
+ // originalGlyphData is copy data. For change cached information, we should access as iterator.
const GlyphCacheKey key = GlyphCacheKey(index, flag, isBoldRequired);
auto iter = mLRUGlyphCache.Find(key);
{
if(destinationGlpyhData.mBitmap->pitch == static_cast<int>(destinationGlpyhData.mBitmap->width))
{
- desiredBuffer = new uint8_t[desiredWidth * desiredHeight];
+ desiredBuffer = (uint8_t*)malloc(desiredWidth * desiredHeight * sizeof(uint8_t)); // @note The caller is responsible for deallocating the bitmap data using free.
// Resize bitmap here.
Dali::Internal::Platform::LanczosSample1BPP(destinationGlpyhData.mBitmap->buffer,
inputDimensions,
{
if(destinationGlpyhData.mBitmap->pitch == static_cast<int>(destinationGlpyhData.mBitmap->width << 2u))
{
- desiredBuffer = new uint8_t[(desiredWidth * desiredHeight) << 2u];
+ desiredBuffer = (uint8_t*)malloc((desiredWidth * desiredHeight * sizeof(uint8_t)) << 2u); // @note The caller is responsible for deallocating the bitmap data using free.
// Resize bitmap here.
Dali::Internal::Platform::LanczosSample4BPP(destinationGlpyhData.mBitmap->buffer,
inputDimensions,
{
// Success to resize bitmap glyph.
// Release origin bitmap buffer.
- delete[] destinationGlpyhData.mBitmap->buffer;
+ free(destinationGlpyhData.mBitmap->buffer);
// Replace as desired buffer and size.
destinationGlpyhData.mBitmap->buffer = desiredBuffer;
}
}
+void GlyphCacheManager::CacheRenderedGlyphBuffer(
+ const GlyphIndex index,
+ const FT_Int32 flag,
+ const bool isBoldRequired,
+ const FT_Bitmap& srcBitmap,
+ const TextAbstraction::FontClient::GlyphBufferData::CompressPolicyType policy)
+{
+ if(srcBitmap.width * srcBitmap.rows <= 0)
+ {
+ // Skip this API if rendered bitmap size is zero
+ return;
+ }
+ FT_Error error;
+ GlyphCacheData originGlyphData;
+ if(GetGlyphCacheDataFromIndex(index, flag, isBoldRequired, originGlyphData, error))
+ {
+ if(DALI_LIKELY(!originGlyphData.mIsBitmap && originGlyphData.mRenderedBuffer == nullptr))
+ {
+ // originalGlyphData is copy data. For change cached information, we should access as iterator.
+ const GlyphCacheKey key = GlyphCacheKey(index, flag, isBoldRequired);
+ auto iter = mLRUGlyphCache.Find(key);
+
+ GlyphCacheData& destinationGlpyhData = iter->element;
+
+ destinationGlpyhData.mRenderedBuffer = new TextAbstraction::FontClient::GlyphBufferData();
+ if(DALI_UNLIKELY(!destinationGlpyhData.mRenderedBuffer))
+ {
+ DALI_LOG_ERROR("Allocate GlyphBufferData failed\n");
+ return;
+ }
+
+ TextAbstraction::FontClient::GlyphBufferData& renderBuffer = *destinationGlpyhData.mRenderedBuffer;
+
+ // Set basic informations.
+ renderBuffer.width = srcBitmap.width;
+ renderBuffer.height = srcBitmap.rows;
+
+ switch(srcBitmap.pixel_mode)
+ {
+ case FT_PIXEL_MODE_GRAY:
+ {
+ renderBuffer.format = Pixel::L8;
+
+ if(policy == TextAbstraction::FontClient::GlyphBufferData::CompressPolicyType::SPEED)
+ {
+ // If policy is SPEED, we will not compress bitmap.
+ renderBuffer.compressType = TextAbstraction::FontClient::GlyphBufferData::CompressType::NO_COMPRESS;
+ }
+ else
+ {
+ // If small enough glyph, compress as BPP4 method.
+ if(srcBitmap.width < THRESHOLD_WIDTH_FOR_BPP_COMPRESS)
+ {
+ renderBuffer.compressType = TextAbstraction::FontClient::GlyphBufferData::CompressType::BIT_PER_PIXEL_4;
+ }
+ else
+ {
+ renderBuffer.compressType = TextAbstraction::FontClient::GlyphBufferData::CompressType::COMPRESS_RLE4;
+ }
+ }
+
+ const auto compressedBufferSize = TextAbstraction::FontClient::GlyphBufferData::Compress(srcBitmap.buffer, renderBuffer);
+ if(DALI_UNLIKELY(compressedBufferSize == 0u))
+ {
+ DALI_ASSERT_DEBUG(0 == "Compress failed at FT_PIXEL_MODE_GRAY");
+ DALI_LOG_ERROR("Compress failed. Ignore cache\n");
+ delete destinationGlpyhData.mRenderedBuffer;
+ destinationGlpyhData.mRenderedBuffer = nullptr;
+ return;
+ }
+ break;
+ }
+#ifdef FREETYPE_BITMAP_SUPPORT
+ case FT_PIXEL_MODE_BGRA:
+ {
+ // Copy buffer without compress
+ renderBuffer.compressType = TextAbstraction::FontClient::GlyphBufferData::CompressType::NO_COMPRESS;
+ renderBuffer.format = Pixel::BGRA8888;
+
+ const auto compressedBufferSize = TextAbstraction::FontClient::GlyphBufferData::Compress(srcBitmap.buffer, renderBuffer);
+ if(DALI_UNLIKELY(compressedBufferSize == 0u))
+ {
+ DALI_ASSERT_DEBUG(0 == "Compress failed at FT_PIXEL_MODE_BGRA");
+ DALI_LOG_ERROR("Compress failed. Ignore cache\n");
+ delete destinationGlpyhData.mRenderedBuffer;
+ destinationGlpyhData.mRenderedBuffer = nullptr;
+ return;
+ }
+ break;
+ }
+#endif
+ default:
+ {
+ DALI_LOG_INFO(gFontClientLogFilter, Debug::General, "FontClient::Plugin::GlyphCacheManager::CacheRenderedGlyphBuffer. FontClient Unable to create Bitmap of this PixelType\n");
+ delete destinationGlpyhData.mRenderedBuffer;
+ destinationGlpyhData.mRenderedBuffer = nullptr;
+ break;
+ }
+ }
+ }
+ }
+}
+
void GlyphCacheManager::GlyphCacheData::ReleaseGlyphData()
{
if(mIsBitmap && mBitmap)
{
// Created FT_Bitmap object must be released with FT_Bitmap_Done
- delete[] mBitmap->buffer;
+ free(mBitmap->buffer); // This buffer created by malloc
+
delete mBitmap;
mBitmap = nullptr;
}
FT_Done_Glyph(mGlyph);
mGlyph = nullptr;
}
+
+ if(mRenderedBuffer)
+ {
+ delete mRenderedBuffer;
+ }
}
} // namespace Dali::TextAbstraction::Internal
*/
// INTERNAL INCLUDES
+#include <dali/devel-api/text-abstraction/font-client.h> // For GlyphBufferData
+#include <dali/devel-api/text-abstraction/text-abstraction-definitions.h>
#include <dali/internal/text/text-abstraction/plugin/lru-cache-container.h>
// EXTERNAL INCLUDES
-
#include <ft2build.h>
#include FT_FREETYPE_H
#include FT_GLYPH_H
FT_Int32 mStyleFlags{0}; // Get from FT_Face
bool mIsBitmap{false};
+ TextAbstraction::FontClient::GlyphBufferData* mRenderedBuffer{nullptr}; // Rendered glyph buffer. Cached only if system allow to cache and we rendered it before. Otherwise, just nullptr
+
/**
* @brief Release the memory of loaded mGlyph / mBitmap.
*/
* @return True if load successfully. False if something error occured.
*/
bool GetGlyphCacheDataFromIndex(
- const GlyphIndex& index,
- const FT_Int32& flag,
- const bool& isBoldRequired,
- GlyphCacheData& data,
- FT_Error& error);
+ const GlyphIndex index,
+ const FT_Int32 flag,
+ const bool isBoldRequired,
+ GlyphCacheData& data,
+ FT_Error& error);
/**
* @brief Load GlyphCacheData from face. The result will not be cached.
* @return True if load successfully. False if something error occured.
*/
bool LoadGlyphDataFromIndex(
- const GlyphIndex& index,
- const FT_Int32& flag,
- const bool& isBoldRequired,
- GlyphCacheData& data,
- FT_Error& error);
+ const GlyphIndex index,
+ const FT_Int32 flag,
+ const bool isBoldRequired,
+ GlyphCacheData& data,
+ FT_Error& error);
/**
* @brief Resize bitmap glyph. The result will change cached glyph bitmap information.
* @param[in] desiredHeight Desired height of bitmap.
*/
void ResizeBitmapGlyph(
- const GlyphIndex& index,
- const FT_Int32& flag,
- const bool& isBoldRequired,
- const uint32_t& desiredWidth,
- const uint32_t& desiredHeight);
+ const GlyphIndex index,
+ const FT_Int32 flag,
+ const bool isBoldRequired,
+ const uint32_t desiredWidth,
+ const uint32_t desiredHeight);
+
+ /**
+ * @brief Cache rendered glyph bitmap. The result will change cached glyph information.
+ * If glyph is not single color glyph, or we already cached buffer before, nothing happened.
+ *
+ * @param[in] index Index of glyph in this face.
+ * @param[in] flag Flag when we load the glyph.
+ * @param[in] isBoldRequired True if we require some software bold.
+ * @param[in] srcBitmap Rendered glyph bitmap.
+ * @param[in] policy Compress behavior policy. default is MEMORY
+ */
+ void CacheRenderedGlyphBuffer(
+ const GlyphIndex index,
+ const FT_Int32 flag,
+ const bool isBoldRequired,
+ const FT_Bitmap& srcBitmap,
+ const TextAbstraction::FontClient::GlyphBufferData::CompressPolicyType policy);
private:
// Private struct area.
{
}
- GlyphCacheKey(const GlyphIndex& index, const FT_Int32& flag, const bool& boldRequired)
+ GlyphCacheKey(const GlyphIndex index, const FT_Int32 flag, const bool boldRequired)
: mIndex(index),
mFlag(flag),
mIsBoldRequired(boldRequired)
*/
// EXTERNAL INCLUDES
+#include <dali/public-api/common/dali-common.h>
+#include <dali/public-api/common/vector-wrapper.h>
#include <limits> // for std::numeric_limits
#include <unordered_map>
-#include <vector>
namespace Dali::TextAbstraction::Internal
{