Cache rendered glyph if required. + Copyless glyph creation 99/276599/26
authorEunki, Hong <eunkiki.hong@samsung.com>
Tue, 21 Jun 2022 10:42:31 +0000 (19:42 +0900)
committerEunki, Hong <eunkiki.hong@samsung.com>
Thu, 30 Jun 2022 08:30:15 +0000 (17:30 +0900)
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>
14 files changed:
automated-tests/src/dali-adaptor-internal/utc-Dali-FontClient.cpp
dali/devel-api/text-abstraction/font-client.cpp
dali/devel-api/text-abstraction/font-client.h
dali/internal/imaging/common/image-operations.cpp
dali/internal/imaging/common/image-operations.h
dali/internal/text/text-abstraction/cairo-renderer.cpp
dali/internal/text/text-abstraction/plugin/bitmap-font-cache-item.cpp
dali/internal/text/text-abstraction/plugin/embedded-item.cpp
dali/internal/text/text-abstraction/plugin/font-client-utils.cpp
dali/internal/text/text-abstraction/plugin/font-client-utils.h
dali/internal/text/text-abstraction/plugin/font-face-cache-item.cpp
dali/internal/text/text-abstraction/plugin/font-face-glyph-cache-manager.cpp
dali/internal/text/text-abstraction/plugin/font-face-glyph-cache-manager.h
dali/internal/text/text-abstraction/plugin/lru-cache-container.h

index 006f04d..208deb1 100644 (file)
@@ -173,11 +173,6 @@ int UtcDaliFontClientAtlasLimitationEnabled(void)
   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;
 }
 
index 7cea697..747ce2d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * 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.
@@ -19,6 +19,7 @@
 #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
@@ -48,6 +49,8 @@ const Size FontClient::MAX_SIZE_FIT_IN_ATLAS(MAX_TEXT_ATLAS_WIDTH - PADDING_TEXT
 
 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},
@@ -55,14 +58,526 @@ FontClient::GlyphBufferData::GlyphBufferData()
   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()
 {
index 992f8aa..6db1006 100644 (file)
@@ -2,7 +2,7 @@
 #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.
@@ -95,14 +95,63 @@ public:
      */
     ~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.
   };
 
   /**
index 2bdac51..675388e 100644 (file)
@@ -2058,6 +2058,57 @@ void LinearSample4BPP(const unsigned char* __restrict__ inPixels,
   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,
@@ -2257,43 +2308,29 @@ void LanczosSample1BPP(const unsigned char* __restrict__ inPixels,
   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:
@@ -2304,7 +2341,7 @@ void LinearSample(const unsigned char* __restrict__ inPixels,
   }
   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));
   }
 }
 
index 57d1255..2d27eac 100644 (file)
@@ -357,6 +357,26 @@ void LinearSample4BPP(const unsigned char* __restrict__ inPixels,
                       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
@@ -701,12 +721,23 @@ inline unsigned int BilinearFilter1Component(unsigned int tl, unsigned int tr, u
  * @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 */
index 4bccb0b..2becf8c 100644 (file)
@@ -854,7 +854,6 @@ Devel::PixelBuffer RenderTextCairo(const TextAbstraction::TextRenderer::Paramete
 
           // 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;
@@ -893,6 +892,24 @@ Devel::PixelBuffer RenderTextCairo(const TextAbstraction::TextRenderer::Paramete
             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,
@@ -904,10 +921,15 @@ Devel::PixelBuffer RenderTextCairo(const TextAbstraction::TextRenderer::Paramete
                                                     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);
index 6512b95..972f323 100644 (file)
@@ -115,10 +115,7 @@ void BitmapFontCacheItem::CreateBitmap(
 
       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;
index 16b8044..b49a4b9 100644 (file)
@@ -40,17 +40,17 @@ void EmbeddedItem::CreateBitmap(const std::vector<PixelBufferCacheItem>&
     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);
 
index 9141ca1..6d80960 100644 (file)
@@ -129,7 +129,7 @@ const FontSlant::Type DefaultFontSlant()
  * @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);
@@ -141,22 +141,32 @@ void ConvertBitmap(TextAbstraction::FontClient::GlyphBufferData& data, unsigned
   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);
   }
 }
 
@@ -171,6 +181,7 @@ void ConvertBitmap(TextAbstraction::FontClient::GlyphBufferData& data, unsigned
  */
 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)
@@ -261,19 +272,21 @@ void ConvertBitmap(TextAbstraction::FontClient::GlyphBufferData& data, FT_Bitmap
           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)
@@ -290,10 +303,7 @@ void ConvertBitmap(TextAbstraction::FontClient::GlyphBufferData& data, FT_Bitmap
         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;
       }
index daf4c5b..c73e3da 100644 (file)
@@ -35,7 +35,8 @@ namespace Dali::TextAbstraction::Internal
 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,
index dd45035..5cf1f85 100644 (file)
@@ -44,7 +44,7 @@ constexpr float MAXIMUM_RATE_OF_BITMAP_GLYPH_CACHE_RESIZE = 1.5f;
  * @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";
 
@@ -54,13 +54,63 @@ 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,
@@ -319,16 +369,17 @@ bool FontFaceCacheItem::GetGlyphMetrics(GlyphInfo& glyphInfo, unsigned int dpiVe
  * @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;
 
@@ -336,7 +387,7 @@ void FontFaceCacheItem::CreateBitmap(
   // 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
@@ -344,8 +395,10 @@ void FontFaceCacheItem::CreateBitmap(
     // 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))
@@ -354,9 +407,9 @@ void FontFaceCacheItem::CreateBitmap(
       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.");
@@ -417,29 +470,72 @@ void FontFaceCacheItem::CreateBitmap(
         }
       }
 
-      // 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
index daf0bf9..8a597f6 100644 (file)
  * 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
@@ -31,6 +33,7 @@ namespace Dali::TextAbstraction::Internal
 {
 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)
@@ -40,6 +43,7 @@ GlyphCacheManager::GlyphCacheManager(FT_Face ftFace, std::size_t maxNumberOfGlyp
 {
   DALI_LOG_INFO(gFontClientLogFilter, Debug::Verbose, "FontClient::Plugin::GlyphCacheManager Create with maximum size : %d\n", static_cast<int>(mGlyphCacheMaxSize));
 }
+
 GlyphCacheManager::~GlyphCacheManager()
 {
   while(!mLRUGlyphCache.IsEmpty())
@@ -53,11 +57,11 @@ GlyphCacheManager::~GlyphCacheManager()
 }
 
 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);
@@ -102,11 +106,11 @@ bool GlyphCacheManager::GetGlyphCacheDataFromIndex(
 }
 
 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)
@@ -167,7 +171,7 @@ bool GlyphCacheManager::LoadGlyphDataFromIndex(
       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
@@ -191,12 +195,17 @@ bool GlyphCacheManager::LoadGlyphDataFromIndex(
 }
 
 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))
@@ -206,6 +215,7 @@ void GlyphCacheManager::ResizeBitmapGlyph(
       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);
 
@@ -222,7 +232,7 @@ void GlyphCacheManager::ResizeBitmapGlyph(
           {
             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,
@@ -237,7 +247,7 @@ void GlyphCacheManager::ResizeBitmapGlyph(
           {
             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,
@@ -259,7 +269,7 @@ void GlyphCacheManager::ResizeBitmapGlyph(
         {
           // 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;
@@ -286,12 +296,116 @@ void GlyphCacheManager::ResizeBitmapGlyph(
   }
 }
 
+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;
   }
@@ -301,6 +415,11 @@ void GlyphCacheManager::GlyphCacheData::ReleaseGlyphData()
     FT_Done_Glyph(mGlyph);
     mGlyph = nullptr;
   }
+
+  if(mRenderedBuffer)
+  {
+    delete mRenderedBuffer;
+  }
 }
 
 } // namespace Dali::TextAbstraction::Internal
index 5b8d135..47fb14d 100644 (file)
  */
 
 // 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
@@ -66,6 +67,8 @@ public:
     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.
      */
@@ -86,11 +89,11 @@ public:
    * @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.
@@ -104,11 +107,11 @@ public:
    * @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.
@@ -121,11 +124,28 @@ public:
    * @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.
@@ -141,7 +161,7 @@ private:
     {
     }
 
-    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)
index d6f8d23..5c84f67 100644 (file)
  */
 
 // 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
 {