X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;ds=sidebyside;f=dali-toolkit%2Finternal%2Ftext%2Frendering%2Ftext-typesetter.cpp;h=73ae2c5a59b4f15adcb21262a00059baaa8b42c2;hb=HEAD;hp=826e8941d305a4f874108a175635ad2f32af6bec;hpb=e443d5fbefaabaf421aebf83a0350ea98380a4eb;p=platform%2Fcore%2Fuifw%2Fdali-toolkit.git diff --git a/dali-toolkit/internal/text/rendering/text-typesetter.cpp b/dali-toolkit/internal/text/rendering/text-typesetter.cpp index 826e894..ec6dce2 100644 --- a/dali-toolkit/internal/text/rendering/text-typesetter.cpp +++ b/dali-toolkit/internal/text/rendering/text-typesetter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Samsung Electronics Co., Ltd. + * Copyright (c) 2024 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,13 +19,18 @@ #include // EXTERNAL INCLUDES +#include #include +#include +#include #include +#include #include // INTERNAL INCLUDES #include #include +#include #include #include #include @@ -39,6 +44,8 @@ namespace Text { namespace { +DALI_INIT_TRACE_FILTER(gTraceFilter, DALI_TRACE_TEXT_PERFORMANCE_MARKER, false); + const float HALF(0.5f); const float ONE_AND_A_HALF(1.5f); @@ -49,24 +56,102 @@ const float ONE_AND_A_HALF(1.5f); * @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(x) * y; return ((xy << 15) + (xy << 7) + xy) >> 23; } /** + * @brief Fast multiply & Summation & divide by 255. + * + * @param x1 The value between [0..255] + * @param y1 The value between [0..255] + * @param x2 The value between [0..255] + * @param y2 The value between [0..255] + * @return min(255, (x1*y1)/255 + (x2*y2)/255) + */ +inline uint8_t MultiplyAndSummationAndNormalizeColor(const uint8_t x1, const uint8_t y1, const uint8_t x2, const uint8_t y2) noexcept +{ + const uint32_t xy1 = static_cast(x1) * y1; + const uint32_t xy2 = static_cast(x2) * y2; + const uint32_t res = std::min(65025u, xy1 + xy2); // 65025 is 255 * 255. + return ((res + ((res + 257) >> 8)) >> 8); // fast divide by 255. +} + +/// Helper macro define for glyph typesetter. It will reduce some duplicated code line. +// clang-format off +/** + * @brief Prepare decode glyph bitmap data. It must be call END_GLYPH_BITMAP end of same scope. + */ +#define BEGIN_GLYPH_BITMAP(data) \ +{ \ + uint32_t glyphOffet = 0u; \ + const bool useLocalScanline = data.glyphBitmap.compressionType != TextAbstraction::GlyphBufferData::CompressionType::NO_COMPRESSION; \ + uint8_t* __restrict__ glyphScanline = useLocalScanline ? (uint8_t*)malloc(data.glyphBitmap.width * glyphPixelSize) : data.glyphBitmap.buffer; \ + DALI_ASSERT_ALWAYS(glyphScanline && "Glyph scanline for buffer is null!"); + +/** + * @brief Macro to skip useless line fast. + */ +#define SKIP_GLYPH_SCANLINE(skipLine) \ +if(useLocalScanline) \ +{ \ + for(int32_t lineIndex = 0; lineIndex < skipLine; ++lineIndex) \ + { \ + TextAbstraction::GlyphBufferData::DecompressScanline(data.glyphBitmap, glyphScanline, glyphOffet); \ + } \ +} \ +else \ +{ \ + glyphScanline += skipLine * static_cast(data.glyphBitmap.width * glyphPixelSize); \ +} + +/** + * @brief Prepare scanline of glyph bitmap data per each lines. It must be call END_GLYPH_SCANLINE_DECODE end of same scope. + */ +#define BEGIN_GLYPH_SCANLINE_DECODE(data) \ +{ \ + if(useLocalScanline) \ + { \ + TextAbstraction::GlyphBufferData::DecompressScanline(data.glyphBitmap, glyphScanline, glyphOffet); \ + } + +/** + * @brief Finalize scanline of glyph bitmap data per each lines. + */ +#define END_GLYPH_SCANLINE_DECODE(data) \ + if(!useLocalScanline) \ + { \ + glyphScanline += data.glyphBitmap.width * glyphPixelSize; \ + } \ +} // For ensure that we call BEGIN_GLYPH_SCANLINE_DECODE before + +/** + * @brief Finalize decode glyph bitmap data. + */ +#define END_GLYPH_BITMAP() \ + if(useLocalScanline) \ + { \ + free(glyphScanline); \ + } \ +} // For ensure that we call BEGIN_GLYPH_BITMAP before + +// clang-format on +/// Helper macro define end. + +/** * @brief Data struct used to set the buffer of the glyph's bitmap into the final bitmap's buffer. */ struct GlyphData { - Devel::PixelBuffer bitmapBuffer; ///< The buffer of the whole bitmap. The format is RGBA8888. - Vector2* position; ///< The position of the glyph. - TextAbstraction::FontClient::GlyphBufferData glyphBitmap; ///< The glyph's bitmap. - uint32_t width; ///< The bitmap's width. - uint32_t height; ///< The bitmap's height. - int32_t horizontalOffset; ///< The horizontal offset to be added to the 'x' glyph's position. - int32_t verticalOffset; ///< The vertical offset to be added to the 'y' glyph's position. + Devel::PixelBuffer bitmapBuffer; ///< The buffer of the whole bitmap. The format is RGBA8888. + Vector2* position; ///< The position of the glyph. + TextAbstraction::GlyphBufferData glyphBitmap; ///< The glyph's bitmap. + uint32_t width; ///< The bitmap's width. + uint32_t height; ///< The bitmap's height. + int32_t horizontalOffset; ///< The horizontal offset to be added to the 'x' glyph's position. + int32_t verticalOffset; ///< The vertical offset to be added to the 'y' glyph's position. }; /** @@ -78,11 +163,11 @@ struct GlyphData * @param[in] style The style of the text. * @param[in] pixelFormat The format of the pixel in the image that the text is rendered as (i.e. either Pixel::BGRA8888 or Pixel::L8). */ -void TypesetGlyph(GlyphData& data, - const Vector2* const position, - const Vector4* const color, - Typesetter::Style style, - Pixel::Format pixelFormat) +void TypesetGlyph(GlyphData& __restrict__ data, + const Vector2* const __restrict__ position, + const Vector4* const __restrict__ color, + const Typesetter::Style style, + const Pixel::Format pixelFormat) { if((0u == data.glyphBitmap.width) || (0u == data.glyphBitmap.height)) { @@ -95,9 +180,9 @@ void TypesetGlyph(GlyphData& data, const int32_t xOffset = data.horizontalOffset + position->x; // Whether the given glyph is a color one. - const bool isColorGlyph = data.glyphBitmap.isColorEmoji || data.glyphBitmap.isColorBitmap; - const uint32_t glyphPixelSize = Pixel::GetBytesPerPixel(data.glyphBitmap.format); - const uint32_t alphaIndex = glyphPixelSize - 1u; + const bool isColorGlyph = data.glyphBitmap.isColorEmoji || data.glyphBitmap.isColorBitmap; + const uint32_t glyphPixelSize = Pixel::GetBytesPerPixel(data.glyphBitmap.format); + const uint32_t glyphAlphaIndex = (glyphPixelSize > 0u) ? glyphPixelSize - 1u : 0u; // Determinate iterator range. const int32_t lineIndexRangeMin = std::max(0, -yOffset); @@ -113,9 +198,9 @@ void TypesetGlyph(GlyphData& data, if(Pixel::RGBA8888 == pixelFormat) { - const bool swapChannelsBR = Pixel::BGRA8888 == data.glyphBitmap.format; - - uint32_t* bitmapBuffer = reinterpret_cast(data.bitmapBuffer.GetBuffer()); + uint32_t* __restrict__ bitmapBuffer = reinterpret_cast(data.bitmapBuffer.GetBuffer()); + // Skip basic line. + bitmapBuffer += (lineIndexRangeMin + yOffset) * static_cast(data.width); // Fast-cut if style is MASK or OUTLINE. Outline not shown for color glyph. // Just overwrite transparent color and return. @@ -123,43 +208,44 @@ void TypesetGlyph(GlyphData& data, { for(int32_t lineIndex = lineIndexRangeMin; lineIndex < lineIndexRangeMax; ++lineIndex) { - const int32_t yOffsetIndex = yOffset + lineIndex; - const int32_t verticalOffset = yOffsetIndex * data.width; - // We can use memset here. - memset(bitmapBuffer + verticalOffset + xOffset + indexRangeMin, 0, (indexRangeMax - indexRangeMin) * sizeof(uint32_t)); + memset(bitmapBuffer + xOffset + indexRangeMin, 0, (indexRangeMax - indexRangeMin) * sizeof(uint32_t)); + bitmapBuffer += data.width; } return; } - // Pointer to the color glyph if there is one. - const uint32_t* const colorGlyphBuffer = isColorGlyph ? reinterpret_cast(data.glyphBitmap.buffer) : NULL; + const bool swapChannelsBR = Pixel::BGRA8888 == data.glyphBitmap.format; // Precalculate input color's packed result. - uint32_t packedInputColor = 0u; - uint8_t* packedInputColorBuffer = reinterpret_cast(&packedInputColor); + uint32_t packedInputColor = 0u; + uint8_t* __restrict__ packedInputColorBuffer = reinterpret_cast(&packedInputColor); *(packedInputColorBuffer + 3u) = static_cast(color->a * 255); *(packedInputColorBuffer + 2u) = static_cast(color->b * 255); *(packedInputColorBuffer + 1u) = static_cast(color->g * 255); *(packedInputColorBuffer) = static_cast(color->r * 255); + // Prepare glyph bitmap + BEGIN_GLYPH_BITMAP(data); + + // Skip basic line of glyph. + SKIP_GLYPH_SCANLINE(lineIndexRangeMin); + // Traverse the pixels of the glyph line per line. - for(int32_t lineIndex = lineIndexRangeMin; lineIndex < lineIndexRangeMax; ++lineIndex) + if(isColorGlyph) { - const int32_t yOffsetIndex = yOffset + lineIndex; - - const int32_t verticalOffset = yOffsetIndex * data.width; - const int32_t glyphBufferOffset = lineIndex * static_cast(data.glyphBitmap.width); - for(int32_t index = indexRangeMin; index < indexRangeMax; ++index) + for(int32_t lineIndex = lineIndexRangeMin; lineIndex < lineIndexRangeMax; ++lineIndex) { - const int32_t xOffsetIndex = xOffset + index; + BEGIN_GLYPH_SCANLINE_DECODE(data); - if(isColorGlyph) + for(int32_t index = indexRangeMin; index < indexRangeMax; ++index) { + const int32_t xOffsetIndex = xOffset + index; + // Retrieves the color from the color glyph. - uint32_t packedColorGlyph = *(colorGlyphBuffer + glyphBufferOffset + index); - uint8_t* packedColorGlyphBuffer = reinterpret_cast(&packedColorGlyph); + uint32_t packedColorGlyph = *(reinterpret_cast(glyphScanline + (index << 2))); + uint8_t* __restrict__ packedColorGlyphBuffer = reinterpret_cast(&packedColorGlyph); // Update the alpha channel. const uint8_t colorAlpha = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 3u), *(packedColorGlyphBuffer + 3u)); @@ -192,23 +278,32 @@ void TypesetGlyph(GlyphData& data, } // Set the color into the final pixel buffer. - *(bitmapBuffer + verticalOffset + xOffsetIndex) = packedColorGlyph; + *(bitmapBuffer + xOffsetIndex) = packedColorGlyph; } - else - { - // Pack the given color into a 32bit buffer. The alpha channel will be updated later for each pixel. - // The format is RGBA8888. - uint32_t packedColor = 0u; - uint8_t* packedColorBuffer = reinterpret_cast(&packedColor); + bitmapBuffer += data.width; + + END_GLYPH_SCANLINE_DECODE(data); + } + } + else + { + for(int32_t lineIndex = lineIndexRangeMin; lineIndex < lineIndexRangeMax; ++lineIndex) + { + BEGIN_GLYPH_SCANLINE_DECODE(data); + + for(int32_t index = indexRangeMin; index < indexRangeMax; ++index) + { // Update the alpha channel. - const uint8_t alpha = *(data.glyphBitmap.buffer + glyphPixelSize * (glyphBufferOffset + index) + alphaIndex); + const uint8_t alpha = *(glyphScanline + index * glyphPixelSize + glyphAlphaIndex); // Copy non-transparent pixels only if(alpha > 0u) { + const int32_t xOffsetIndex = xOffset + index; + // Check alpha of overlapped pixels - uint32_t& currentColor = *(bitmapBuffer + verticalOffset + xOffsetIndex); + uint32_t& currentColor = *(bitmapBuffer + xOffsetIndex); uint8_t* packedCurrentColorBuffer = reinterpret_cast(¤tColor); // For any pixel overlapped with the pixel in previous glyphs, make sure we don't @@ -224,6 +319,11 @@ void TypesetGlyph(GlyphData& data, } else { + // Pack the given color into a 32bit buffer. The alpha channel will be updated later for each pixel. + // The format is RGBA8888. + uint32_t packedColor = 0u; + uint8_t* __restrict__ packedColorBuffer = reinterpret_cast(&packedColor); + // Color is pre-muliplied with its alpha. *(packedColorBuffer + 3u) = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 3u), currentAlpha); *(packedColorBuffer + 2u) = MultiplyAndNormalizeColor(*(packedInputColorBuffer + 2u), currentAlpha); @@ -235,35 +335,47 @@ void TypesetGlyph(GlyphData& data, } } } + + bitmapBuffer += data.width; + + END_GLYPH_SCANLINE_DECODE(data); } } + + END_GLYPH_BITMAP(); } - else + else // Pixel::L8 { // Below codes required only if not color glyph. if(!isColorGlyph) { - uint8_t* bitmapBuffer = reinterpret_cast(data.bitmapBuffer.GetBuffer()); + uint8_t* __restrict__ bitmapBuffer = data.bitmapBuffer.GetBuffer(); + // Skip basic line. + bitmapBuffer += (lineIndexRangeMin + yOffset) * static_cast(data.width); + + // Prepare glyph bitmap + BEGIN_GLYPH_BITMAP(data); + + // Skip basic line of glyph. + SKIP_GLYPH_SCANLINE(lineIndexRangeMin); // Traverse the pixels of the glyph line per line. for(int32_t lineIndex = lineIndexRangeMin; lineIndex < lineIndexRangeMax; ++lineIndex) { - const int32_t yOffsetIndex = yOffset + lineIndex; + BEGIN_GLYPH_SCANLINE_DECODE(data); - const int32_t verticalOffset = yOffsetIndex * data.width; - const int32_t glyphBufferOffset = lineIndex * static_cast(data.glyphBitmap.width); for(int32_t index = indexRangeMin; index < indexRangeMax; ++index) { const int32_t xOffsetIndex = xOffset + index; // Update the alpha channel. - const uint8_t alpha = *(data.glyphBitmap.buffer + glyphPixelSize * (glyphBufferOffset + index) + alphaIndex); + const uint8_t alpha = *(glyphScanline + index * glyphPixelSize + glyphAlphaIndex); // Copy non-transparent pixels only if(alpha > 0u) { // Check alpha of overlapped pixels - uint8_t& currentAlpha = *(bitmapBuffer + verticalOffset + xOffsetIndex); + uint8_t& currentAlpha = *(bitmapBuffer + xOffsetIndex); // For any pixel overlapped with the pixel in previous glyphs, make sure we don't // overwrite a previous bigger alpha with a smaller alpha (in order to avoid @@ -272,21 +384,27 @@ void TypesetGlyph(GlyphData& data, currentAlpha = std::max(currentAlpha, alpha); } } + + bitmapBuffer += data.width; + + END_GLYPH_SCANLINE_DECODE(data); } + + END_GLYPH_BITMAP(); } } } /// Draws the specified underline color to the buffer void DrawUnderline( - const uint32_t& bufferWidth, - const uint32_t& bufferHeight, + const uint32_t bufferWidth, + const uint32_t bufferHeight, GlyphData& glyphData, - const float& baseline, - const float& currentUnderlinePosition, - const float& maxUnderlineHeight, - const float& lineExtentLeft, - const float& lineExtentRight, + const float baseline, + const float currentUnderlinePosition, + const float maxUnderlineHeight, + const float lineExtentLeft, + const float lineExtentRight, const UnderlineStyleProperties& commonUnderlineProperties, const UnderlineStyleProperties& currentUnderlineProperties, const LineRun& line) @@ -363,7 +481,7 @@ void DrawUnderline( for(uint32_t x = xRangeMin; x < xRangeMax; x++) { - if(dashGap == 0 && dashWidth > 0) + if(Dali::EqualsZero(dashGap) && dashWidth > 0) { // Note : this is same logic as bitmap[y][x] = underlineColor; *(bitmapBuffer + x) = packedUnderlineColor; @@ -415,14 +533,14 @@ void DrawUnderline( /// Draws the background color to the buffer void DrawBackgroundColor( - Vector4 backgroundColor, - const uint32_t& bufferWidth, - const uint32_t& bufferHeight, - GlyphData& glyphData, - const float& baseline, - const LineRun& line, - const float& lineExtentLeft, - const float& lineExtentRight) + Vector4 backgroundColor, + const uint32_t bufferWidth, + const uint32_t bufferHeight, + GlyphData& glyphData, + const float baseline, + const LineRun& line, + const float lineExtentLeft, + const float lineExtentRight) { const int32_t yRangeMin = std::max(0, static_cast(glyphData.verticalOffset + baseline - line.ascender)); const int32_t yRangeMax = std::min(static_cast(bufferHeight), static_cast(glyphData.verticalOffset + baseline - line.descender)); @@ -475,7 +593,7 @@ void DrawBackgroundColor( } } -Devel::PixelBuffer DrawGlyphsBackground(const ViewModel* model, Devel::PixelBuffer& buffer, const uint32_t& bufferWidth, const uint32_t& bufferHeight, bool ignoreHorizontalAlignment, int32_t horizontalOffset, int32_t verticalOffset) +Devel::PixelBuffer DrawGlyphsBackground(const ViewModel* model, Devel::PixelBuffer& buffer, const uint32_t bufferWidth, const uint32_t bufferHeight, const bool ignoreHorizontalAlignment, const int32_t horizontalOffset, const int32_t verticalOffset) { // Retrieve lines, glyphs, positions and colors from the view model. const Length modelNumberOfLines = model->GetNumberOfLines(); @@ -485,6 +603,10 @@ Devel::PixelBuffer DrawGlyphsBackground(const ViewModel* model, Devel::PixelBuff const Vector2* const positionBuffer = model->GetLayout(); const Vector4* const backgroundColorsBuffer = model->GetBackgroundColors(); const ColorIndex* const backgroundColorIndicesBuffer = model->GetBackgroundColorIndices(); + const bool removeFrontInset = model->IsRemoveFrontInset(); + const bool removeBackInset = model->IsRemoveBackInset(); + + const DevelText::VerticalLineAlignment::Type verLineAlign = model->GetVerticalLineAlignment(); // Create and initialize the pixel buffer. GlyphData glyphData; @@ -507,13 +629,7 @@ Devel::PixelBuffer DrawGlyphsBackground(const ViewModel* model, Devel::PixelBuff glyphData.horizontalOffset += horizontalOffset; // Increases the vertical offset with the line's ascender. - glyphData.verticalOffset += static_cast(line.ascender); - - // Include line spacing after first line - if(lineIndex > 0u) - { - glyphData.verticalOffset += static_cast(line.lineSpacing); - } + glyphData.verticalOffset += static_cast(line.ascender + GetPreOffsetVerticalLineAlignment(line, verLineAlign)); float left = bufferWidth; float right = 0.0f; @@ -558,14 +674,36 @@ Devel::PixelBuffer DrawGlyphsBackground(const ViewModel* model, Devel::PixelBuff } // Calculate the positions of leftmost and rightmost glyphs in the current line - if((position->x < left) || (backgroundColorIndex != prevBackgroundColorIndex)) + if(removeFrontInset) { - left = position->x - glyphInfo->xBearing; + if((position->x < left) || (backgroundColorIndex != prevBackgroundColorIndex)) + { + left = position->x; + } + } + else + { + const float originPositionLeft = position->x - glyphInfo->xBearing; + if((originPositionLeft < left) || (backgroundColorIndex != prevBackgroundColorIndex)) + { + left = originPositionLeft; + } } - if(position->x + glyphInfo->width > right) + if(removeBackInset) + { + if(position->x + glyphInfo->width > right) + { + right = position->x - position->x + glyphInfo->width; + } + } + else { - right = position->x - glyphInfo->xBearing + glyphInfo->advance; + const float originPositionRight = position->x - glyphInfo->xBearing + glyphInfo->advance; + if(originPositionRight > right) + { + right = originPositionRight; + } } prevBackgroundColorIndex = backgroundColorIndex; @@ -579,21 +717,21 @@ Devel::PixelBuffer DrawGlyphsBackground(const ViewModel* model, Devel::PixelBuff } // Increases the vertical offset with the line's descender. - glyphData.verticalOffset += static_cast(-line.descender); + glyphData.verticalOffset += static_cast(-line.descender + GetPostOffsetVerticalLineAlignment(line, verLineAlign)); } return glyphData.bitmapBuffer; } /// Draws the specified strikethrough color to the buffer -void DrawStrikethrough(const uint32_t& bufferWidth, - const uint32_t& bufferHeight, +void DrawStrikethrough(const uint32_t bufferWidth, + const uint32_t bufferHeight, GlyphData& glyphData, - const float& baseline, - const float& strikethroughStartingYPosition, - const float& maxStrikethroughHeight, - const float& lineExtentLeft, - const float& lineExtentRight, + const float baseline, + const float strikethroughStartingYPosition, + const float maxStrikethroughHeight, + const float lineExtentLeft, + const float lineExtentRight, const StrikethroughStyleProperties& commonStrikethroughProperties, const StrikethroughStyleProperties& currentStrikethroughProperties, const LineRun& line) @@ -662,19 +800,19 @@ void DrawStrikethrough(const uint32_t& bufferWidth, * * @return An image buffer. */ -inline Devel::PixelBuffer CreateTransparentImageBuffer(const uint32_t& bufferWidth, const uint32_t& bufferHeight, const Pixel::Format& pixelFormat) +inline Devel::PixelBuffer CreateTransparentImageBuffer(const uint32_t bufferWidth, const uint32_t bufferHeight, const Pixel::Format pixelFormat) { Devel::PixelBuffer imageBuffer = Devel::PixelBuffer::New(bufferWidth, bufferHeight, pixelFormat); if(Pixel::RGBA8888 == pixelFormat) { const uint32_t bufferSizeInt = bufferWidth * bufferHeight; - const uint32_t bufferSizeChar = sizeof(uint32_t) * bufferSizeInt; + const size_t bufferSizeChar = sizeof(uint32_t) * static_cast(bufferSizeInt); memset(imageBuffer.GetBuffer(), 0, bufferSizeChar); } else { - memset(imageBuffer.GetBuffer(), 0, bufferWidth * bufferHeight); + memset(imageBuffer.GetBuffer(), 0, static_cast(bufferWidth * bufferHeight)); } return imageBuffer; @@ -700,7 +838,7 @@ inline Devel::PixelBuffer CreateTransparentImageBuffer(const uint32_t& bufferWid * False if we store the combined image buffer result into bottomPixelBuffer. * */ -void CombineImageBuffer(Devel::PixelBuffer& topPixelBuffer, Devel::PixelBuffer& bottomPixelBuffer, const uint32_t& bufferWidth, const uint32_t& bufferHeight, bool storeResultIntoTop) +void CombineImageBuffer(Devel::PixelBuffer& __restrict__ topPixelBuffer, Devel::PixelBuffer& __restrict__ bottomPixelBuffer, const uint32_t bufferWidth, const uint32_t bufferHeight, bool storeResultIntoTop) { // Assume that we always combine two RGBA images // Jump with 4bytes for optimize runtime. @@ -737,8 +875,8 @@ void CombineImageBuffer(Devel::PixelBuffer& topPixelBuffer, Devel::PixelBuffer& const uint32_t bufferSizeInt = bufferWidth * bufferHeight; - uint32_t* combinedBuffer = storeResultIntoTop ? topBuffer : bottomBuffer; - uint8_t* topAlphaBufferPointer = reinterpret_cast(topBuffer) + 3; + uint32_t* __restrict__ combinedBuffer = storeResultIntoTop ? topBuffer : bottomBuffer; + uint8_t* __restrict__ topAlphaBufferPointer = reinterpret_cast(topBuffer) + 3; for(uint32_t pixelIndex = 0; pixelIndex < bufferSizeInt; ++pixelIndex) { @@ -768,8 +906,8 @@ void CombineImageBuffer(Devel::PixelBuffer& topPixelBuffer, Devel::PixelBuffer& { // At least one pixel is not fully opaque // "Over" blend the the pixel from topBuffer with the pixel in bottomBuffer - uint32_t blendedBottomBufferColor = *(bottomBuffer); - uint8_t* blendedBottomBufferColorBuffer = reinterpret_cast(&blendedBottomBufferColor); + uint32_t blendedBottomBufferColor = *(bottomBuffer); + uint8_t* __restrict__ blendedBottomBufferColorBuffer = reinterpret_cast(&blendedBottomBufferColor); blendedBottomBufferColorBuffer[0] = MultiplyAndNormalizeColor(blendedBottomBufferColorBuffer[0], 255 - topAlpha); blendedBottomBufferColorBuffer[1] = MultiplyAndNormalizeColor(blendedBottomBufferColorBuffer[1], 255 - topAlpha); @@ -801,6 +939,25 @@ ViewModel* Typesetter::GetViewModel() PixelData Typesetter::Render(const Vector2& size, Toolkit::DevelText::TextDirection::Type textDirection, RenderBehaviour behaviour, bool ignoreHorizontalAlignment, Pixel::Format pixelFormat) { + Devel::PixelBuffer result = RenderWithPixelBuffer(size, textDirection, behaviour, ignoreHorizontalAlignment, pixelFormat); + PixelData pixelData = Devel::PixelBuffer::Convert(result); + + return pixelData; +} + +PixelData Typesetter::RenderWithCutout(const Vector2& size, Toolkit::DevelText::TextDirection::Type textDirection, Devel::PixelBuffer mask, RenderBehaviour behaviour, bool ignoreHorizontalAlignment, Pixel::Format pixelFormat, float originAlpha) +{ + Devel::PixelBuffer result = RenderWithPixelBuffer(size, textDirection, behaviour, ignoreHorizontalAlignment, pixelFormat); + SetMaskForImageBuffer(mask, result, size.width, size.height, originAlpha); + + PixelData pixelData = Devel::PixelBuffer::Convert(result); + + return pixelData; +} + +Devel::PixelBuffer Typesetter::RenderWithPixelBuffer(const Vector2& size, Toolkit::DevelText::TextDirection::Type textDirection, RenderBehaviour behaviour, bool ignoreHorizontalAlignment, Pixel::Format pixelFormat) +{ + DALI_TRACE_SCOPE(gTraceFilter, "DALI_TEXT_RENDERING_TYPESETTER"); // @todo. This initial implementation for a TextLabel has only one visible page. // Elides the text if needed. @@ -808,12 +965,10 @@ PixelData Typesetter::Render(const Vector2& size, Toolkit::DevelText::TextDirect // Retrieves the layout size. const Size& layoutSize = mModel->GetLayoutSize(); - const int32_t outlineWidth = static_cast(mModel->GetOutlineWidth()); // Set the offset for the horizontal alignment according to the text direction and outline width. int32_t penX = 0; - switch(mModel->GetHorizontalAlignment()) { case HorizontalAlignment::BEGIN: @@ -835,7 +990,6 @@ PixelData Typesetter::Render(const Vector2& size, Toolkit::DevelText::TextDirect // Set the offset for the vertical alignment. int32_t penY = 0u; - switch(mModel->GetVerticalAlignment()) { case VerticalAlignment::TOP: @@ -845,8 +999,9 @@ PixelData Typesetter::Render(const Vector2& size, Toolkit::DevelText::TextDirect } case VerticalAlignment::CENTER: { - penY = static_cast(0.5f * (size.height - layoutSize.height)); + penY = static_cast(std::round(0.5f * (size.height - layoutSize.height))); penY = penY < 0.f ? 0.f : penY; + break; } case VerticalAlignment::BOTTOM: @@ -856,29 +1011,6 @@ PixelData Typesetter::Render(const Vector2& size, Toolkit::DevelText::TextDirect } } - // Calculate vertical line alignment - switch(mModel->GetVerticalLineAlignment()) - { - case DevelText::VerticalLineAlignment::TOP: - { - break; - } - case DevelText::VerticalLineAlignment::MIDDLE: - { - const auto& line = *mModel->GetLines(); - penY -= line.descender; - penY += static_cast(line.lineSpacing * 0.5f + line.descender); - break; - } - case DevelText::VerticalLineAlignment::BOTTOM: - { - const auto& line = *mModel->GetLines(); - const auto lineHeight = line.ascender + (-line.descender) + line.lineSpacing; - penY += static_cast(lineHeight - (line.ascender - line.descender)); - break; - } - } - // Generate the image buffers of the text for each different style first, // then combine all of them together as one final image buffer. We try to // do all of these in CPU only, so that once the final texture is generated, @@ -888,7 +1020,7 @@ PixelData Typesetter::Render(const Vector2& size, Toolkit::DevelText::TextDirect const uint32_t bufferHeight = static_cast(size.height); const uint32_t bufferSizeInt = bufferWidth * bufferHeight; - const uint32_t bufferSizeChar = sizeof(uint32_t) * bufferSizeInt; + const size_t bufferSizeChar = sizeof(uint32_t) * static_cast(bufferSizeInt); //Elided text in ellipsis at START could start on index greater than 0 auto startIndexOfGlyphs = mModel->GetStartIndexOfElidedGlyphs(); @@ -917,20 +1049,29 @@ PixelData Typesetter::Render(const Vector2& size, Toolkit::DevelText::TextDirect { // Generate the outline if enabled const uint16_t outlineWidth = mModel->GetOutlineWidth(); - if(outlineWidth != 0u && RENDER_OVERLAY_STYLE != behaviour) + const float outlineAlpha = mModel->GetOutlineColor().a; + if(outlineWidth != 0u && fabsf(outlineAlpha) > Math::MACHINE_EPSILON_1 && RENDER_OVERLAY_STYLE != behaviour) { // Create the image buffer for outline Devel::PixelBuffer outlineImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_OUTLINE, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs); + const float& blurRadius = mModel->GetOutlineBlurRadius(); + + if(blurRadius > Math::MACHINE_EPSILON_1) + { + outlineImageBuffer.ApplyGaussianBlur(blurRadius); + } + // Combine the two buffers CombineImageBuffer(imageBuffer, outlineImageBuffer, bufferWidth, bufferHeight, true); } - // @todo. Support shadow and underline for partial text later on. + // @todo. Support shadow for partial text later on. // Generate the shadow if enabled const Vector2& shadowOffset = mModel->GetShadowOffset(); - if(RENDER_OVERLAY_STYLE != behaviour && (fabsf(shadowOffset.x) > Math::MACHINE_EPSILON_1 || fabsf(shadowOffset.y) > Math::MACHINE_EPSILON_1)) + const float shadowAlpha = mModel->GetShadowColor().a; + if(RENDER_OVERLAY_STYLE != behaviour && fabsf(shadowAlpha) > Math::MACHINE_EPSILON_1 && (fabsf(shadowOffset.x) > Math::MACHINE_EPSILON_1 || fabsf(shadowOffset.y) > Math::MACHINE_EPSILON_1)) { // Create the image buffer for shadow Devel::PixelBuffer shadowImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_SHADOW, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs); @@ -947,17 +1088,6 @@ PixelData Typesetter::Render(const Vector2& size, Toolkit::DevelText::TextDirect CombineImageBuffer(imageBuffer, shadowImageBuffer, bufferWidth, bufferHeight, true); } - // Generate the underline if enabled - const bool underlineEnabled = mModel->IsUnderlineEnabled(); - if(underlineEnabled && RENDER_OVERLAY_STYLE == behaviour) - { - // Create the image buffer for underline - Devel::PixelBuffer underlineImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_UNDERLINE, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs); - - // Combine the two buffers - CombineImageBuffer(imageBuffer, underlineImageBuffer, bufferWidth, bufferHeight, true); - } - // Generate the background if enabled const bool backgroundEnabled = mModel->IsBackgroundEnabled(); const bool backgroundMarkupSet = mModel->IsMarkupBackgroundColorSet(); @@ -983,40 +1113,95 @@ PixelData Typesetter::Render(const Vector2& size, Toolkit::DevelText::TextDirect CombineImageBuffer(imageBuffer, backgroundImageBuffer, bufferWidth, bufferHeight, true); } - // Generate the strikethrough if enabled - const bool strikethroughEnabled = mModel->IsStrikethroughEnabled(); - if(strikethroughEnabled && RENDER_OVERLAY_STYLE == behaviour) + // Generate the background_with_mask if enabled + const bool backgroundWithCutoutEnabled = mModel->IsBackgroundWithCutoutEnabled(); + if((backgroundWithCutoutEnabled) && RENDER_OVERLAY_STYLE != behaviour) { - // Create the image buffer for strikethrough - Devel::PixelBuffer strikethroughImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_STRIKETHROUGH, ignoreHorizontalAlignment, pixelFormat, penX, penY, 0u, endIndexOfGlyphs); + Devel::PixelBuffer backgroundImageBuffer; + + backgroundImageBuffer = CreateFullBackgroundBuffer(bufferWidth, bufferHeight, mModel->GetBackgroundColorWithCutout()); // Combine the two buffers - CombineImageBuffer(imageBuffer, strikethroughImageBuffer, bufferWidth, bufferHeight, true); + CombineImageBuffer(imageBuffer, backgroundImageBuffer, bufferWidth, bufferHeight, true); } - // Markup-Processor + if(RENDER_OVERLAY_STYLE == behaviour) + { + if(mModel->IsUnderlineEnabled()) + { + // Create the image buffer for underline + Devel::PixelBuffer underlineImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_UNDERLINE, ignoreHorizontalAlignment, pixelFormat, penX, penY, startIndexOfGlyphs, endIndexOfGlyphs); + + // Combine the two buffers + CombineImageBuffer(imageBuffer, underlineImageBuffer, bufferWidth, bufferHeight, true); + } + + if(mModel->IsStrikethroughEnabled()) + { + // Create the image buffer for strikethrough + Devel::PixelBuffer strikethroughImageBuffer = CreateImageBuffer(bufferWidth, bufferHeight, Typesetter::STYLE_STRIKETHROUGH, ignoreHorizontalAlignment, pixelFormat, penX, penY, 0u, endIndexOfGlyphs); + + // Combine the two buffers + CombineImageBuffer(imageBuffer, strikethroughImageBuffer, bufferWidth, bufferHeight, true); + } + + // Markup-Processor for overlay styles + if(mModel->IsMarkupProcessorEnabled() || mModel->IsSpannedTextPlaced()) + { + if(mModel->IsMarkupUnderlineSet()) + { + imageBuffer = ApplyUnderlineMarkupImageBuffer(imageBuffer, bufferWidth, bufferHeight, ignoreHorizontalAlignment, pixelFormat, penX, penY); + } - imageBuffer = ApplyMarkupProcessorOnPixelBuffer(imageBuffer, bufferWidth, bufferHeight, ignoreHorizontalAlignment, pixelFormat, penX, penY); + if(mModel->IsMarkupStrikethroughSet()) + { + imageBuffer = ApplyStrikethroughMarkupImageBuffer(imageBuffer, bufferWidth, bufferHeight, ignoreHorizontalAlignment, pixelFormat, penX, penY); + } + } + } } - // Create the final PixelData for the combined image buffer - PixelData pixelData = Devel::PixelBuffer::Convert(imageBuffer); + return imageBuffer; +} - return pixelData; +Devel::PixelBuffer Typesetter::CreateFullBackgroundBuffer(const uint32_t bufferWidth, const uint32_t bufferHeight, const Vector4& backgroundColor) +{ + const uint32_t bufferSizeInt = bufferWidth * bufferHeight; + uint8_t backgroundColorAlpha = static_cast(backgroundColor.a * 255.f); + + Devel::PixelBuffer buffer = Devel::PixelBuffer::New(bufferWidth, bufferHeight, Pixel::RGBA8888); + + uint32_t* bitmapBuffer = reinterpret_cast(buffer.GetBuffer()); + + uint32_t packedBackgroundColor = 0u; + uint8_t* packedBackgroundColorBuffer = reinterpret_cast(&packedBackgroundColor); + + // Write the color to the pixel buffer + *(packedBackgroundColorBuffer + 3u) = backgroundColorAlpha; + *(packedBackgroundColorBuffer + 2u) = static_cast(backgroundColor.b * backgroundColorAlpha); + *(packedBackgroundColorBuffer + 1u) = static_cast(backgroundColor.g * backgroundColorAlpha); + *(packedBackgroundColorBuffer) = static_cast(backgroundColor.r * backgroundColorAlpha); + + std::fill(bitmapBuffer, bitmapBuffer + bufferSizeInt, packedBackgroundColor); + + return buffer; } -Devel::PixelBuffer Typesetter::CreateImageBuffer(const uint32_t& bufferWidth, const uint32_t& bufferHeight, Typesetter::Style style, bool ignoreHorizontalAlignment, Pixel::Format pixelFormat, const int32_t& horizontalOffset, const int32_t& verticalOffset, GlyphIndex fromGlyphIndex, GlyphIndex toGlyphIndex) +Devel::PixelBuffer Typesetter::CreateImageBuffer(const uint32_t bufferWidth, const uint32_t bufferHeight, const Typesetter::Style style, const bool ignoreHorizontalAlignment, const Pixel::Format pixelFormat, const int32_t horizontalOffset, const int32_t verticalOffset, const GlyphIndex fromGlyphIndex, const GlyphIndex toGlyphIndex) { // Retrieve lines, glyphs, positions and colors from the view model. - const Length modelNumberOfLines = mModel->GetNumberOfLines(); - const LineRun* const modelLinesBuffer = mModel->GetLines(); - const GlyphInfo* const glyphsBuffer = mModel->GetGlyphs(); - const Vector2* const positionBuffer = mModel->GetLayout(); - const Vector4* const colorsBuffer = mModel->GetColors(); - const ColorIndex* const colorIndexBuffer = mModel->GetColorIndices(); - const GlyphInfo* hyphens = mModel->GetHyphens(); - const Length* hyphenIndices = mModel->GetHyphenIndices(); - const Length hyphensCount = mModel->GetHyphensCount(); + const Length modelNumberOfLines = mModel->GetNumberOfLines(); + const LineRun* const __restrict__ modelLinesBuffer = mModel->GetLines(); + const GlyphInfo* const __restrict__ glyphsBuffer = mModel->GetGlyphs(); + const Vector2* const __restrict__ positionBuffer = mModel->GetLayout(); + const Vector4* const __restrict__ colorsBuffer = mModel->GetColors(); + const ColorIndex* const __restrict__ colorIndexBuffer = mModel->GetColorIndices(); + const GlyphInfo* __restrict__ hyphens = mModel->GetHyphens(); + const Length* __restrict__ hyphenIndices = mModel->GetHyphenIndices(); + const Length hyphensCount = mModel->GetHyphensCount(); + const bool removeFrontInset = mModel->IsRemoveFrontInset(); + const bool removeBackInset = mModel->IsRemoveBackInset(); + const bool cutoutEnabled = mModel->IsCutoutEnabled(); // Elided text info. Indices according to elided text and Ellipsis position. const auto startIndexOfGlyphs = mModel->GetStartIndexOfElidedGlyphs(); @@ -1041,10 +1226,12 @@ Devel::PixelBuffer Typesetter::CreateImageBuffer(const uint32_t& bufferWidth, co TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get(); Length hyphenIndex = 0; - const Character* textBuffer = mModel->GetTextBuffer(); - float calculatedAdvance = 0.f; - const Vector& glyphToCharacterMap = mModel->GetGlyphsToCharacters(); - const CharacterIndex* glyphToCharacterMapBuffer = glyphToCharacterMap.Begin(); + const Character* __restrict__ textBuffer = mModel->GetTextBuffer(); + float calculatedAdvance = 0.f; + const Vector& __restrict__ glyphToCharacterMap = mModel->GetGlyphsToCharacters(); + const CharacterIndex* __restrict__ glyphToCharacterMapBuffer = glyphToCharacterMap.Begin(); + + const DevelText::VerticalLineAlignment::Type verLineAlign = mModel->GetVerticalLineAlignment(); // Traverses the lines of the text. for(LineIndex lineIndex = 0u; lineIndex < modelNumberOfLines; ++lineIndex) @@ -1056,18 +1243,22 @@ Devel::PixelBuffer Typesetter::CreateImageBuffer(const uint32_t& bufferWidth, co glyphData.horizontalOffset += horizontalOffset; // Increases the vertical offset with the line's ascender. - glyphData.verticalOffset += static_cast(line.ascender); + glyphData.verticalOffset += static_cast(line.ascender + GetPreOffsetVerticalLineAlignment(line, verLineAlign)); // Retrieves the glyph's outline width float outlineWidth = static_cast(mModel->GetOutlineWidth()); if(style == Typesetter::STYLE_OUTLINE) { + const Vector2& outlineOffset = mModel->GetOutlineOffset(); + glyphData.horizontalOffset -= outlineWidth; + glyphData.horizontalOffset += outlineOffset.x; if(lineIndex == 0u) { // Only need to add the vertical outline offset for the first line glyphData.verticalOffset -= outlineWidth; + glyphData.verticalOffset += outlineOffset.y; } } else if(style == Typesetter::STYLE_SHADOW) @@ -1087,7 +1278,7 @@ Devel::PixelBuffer Typesetter::CreateImageBuffer(const uint32_t& bufferWidth, co const float modelCharacterSpacing = mModel->GetCharacterSpacing(); // Get the character-spacing runs. - const Vector& characterSpacingGlyphRuns = mModel->GetCharacterSpacingGlyphRuns(); + const Vector& __restrict__ characterSpacingGlyphRuns = mModel->GetCharacterSpacingGlyphRuns(); // Aggregate underline-style-properties from mModel const UnderlineStyleProperties modelUnderlineProperties{mModel->GetUnderlineType(), @@ -1239,14 +1430,36 @@ Devel::PixelBuffer Typesetter::CreateImageBuffer(const uint32_t& bufferWidth, co } // Calculate the positions of leftmost and rightmost glyphs in the current line - if(position.x < lineExtentLeft) + if(removeFrontInset) { - lineExtentLeft = position.x; + if(position.x < lineExtentLeft) + { + lineExtentLeft = position.x; + } + } + else + { + const float originPositionLeft = position.x - glyphInfo->xBearing; + if(originPositionLeft < lineExtentLeft) + { + lineExtentLeft = originPositionLeft; + } } - if(position.x + glyphInfo->width > lineExtentRight) + if(removeBackInset) { - lineExtentRight = position.x + glyphInfo->width; + if(position.x + glyphInfo->width > lineExtentRight) + { + lineExtentRight = position.x + glyphInfo->width; + } + } + else + { + const float originPositionRight = position.x - glyphInfo->xBearing + glyphInfo->advance; + if(originPositionRight > lineExtentRight) + { + lineExtentRight = originPositionRight; + } } // Retrieves the glyph's color. @@ -1266,6 +1479,12 @@ Devel::PixelBuffer Typesetter::CreateImageBuffer(const uint32_t& bufferWidth, co color = (useDefaultColor || (0u == colorIndex)) ? defaultColor : *(colorsBuffer + (colorIndex - 1u)); } + if(style == Typesetter::STYLE_NONE && cutoutEnabled) + { + // Temporarily adjust the transparency to 1.f + color.a = 1.f; + } + // Premultiply alpha color.r *= color.a; color.g *= color.a; @@ -1316,8 +1535,12 @@ Devel::PixelBuffer Typesetter::CreateImageBuffer(const uint32_t& bufferWidth, co glyphData.verticalOffset += glyphData.glyphBitmap.outlineOffsetY; } - // delete the glyphBitmap.buffer as it is now copied into glyphData.bitmapBuffer - delete[] glyphData.glyphBitmap.buffer; + // free the glyphBitmap.buffer if it is owner of buffer + if(glyphData.glyphBitmap.isBufferOwned) + { + free(glyphData.glyphBitmap.buffer); + glyphData.glyphBitmap.isBufferOwned = false; + } glyphData.glyphBitmap.buffer = NULL; } @@ -1357,13 +1580,13 @@ Devel::PixelBuffer Typesetter::CreateImageBuffer(const uint32_t& bufferWidth, co } // Increases the vertical offset with the line's descender & line spacing. - glyphData.verticalOffset += static_cast(-line.descender + line.lineSpacing); + glyphData.verticalOffset += static_cast(-line.descender + GetPostOffsetVerticalLineAlignment(line, verLineAlign)); } return glyphData.bitmapBuffer; } -Devel::PixelBuffer Typesetter::ApplyUnderlineMarkupImageBuffer(Devel::PixelBuffer topPixelBuffer, const uint32_t& bufferWidth, const uint32_t& bufferHeight, bool ignoreHorizontalAlignment, Pixel::Format pixelFormat, const int32_t& horizontalOffset, const int32_t& verticalOffset) +Devel::PixelBuffer Typesetter::ApplyUnderlineMarkupImageBuffer(Devel::PixelBuffer topPixelBuffer, const uint32_t bufferWidth, const uint32_t bufferHeight, const bool ignoreHorizontalAlignment, const Pixel::Format pixelFormat, const int32_t horizontalOffset, const int32_t verticalOffset) { // Underline-tags (this is for Markup case) // Get the underline runs. @@ -1395,7 +1618,7 @@ Devel::PixelBuffer Typesetter::ApplyUnderlineMarkupImageBuffer(Devel::PixelBuffe return topPixelBuffer; } -Devel::PixelBuffer Typesetter::ApplyStrikethroughMarkupImageBuffer(Devel::PixelBuffer topPixelBuffer, const uint32_t& bufferWidth, const uint32_t& bufferHeight, bool ignoreHorizontalAlignment, Pixel::Format pixelFormat, const int32_t& horizontalOffset, const int32_t& verticalOffset) +Devel::PixelBuffer Typesetter::ApplyStrikethroughMarkupImageBuffer(Devel::PixelBuffer topPixelBuffer, const uint32_t bufferWidth, const uint32_t bufferHeight, const bool ignoreHorizontalAlignment, const Pixel::Format pixelFormat, const int32_t horizontalOffset, const int32_t verticalOffset) { // strikethrough-tags (this is for Markup case) // Get the strikethrough runs. @@ -1427,18 +1650,46 @@ Devel::PixelBuffer Typesetter::ApplyStrikethroughMarkupImageBuffer(Devel::PixelB return topPixelBuffer; } -Devel::PixelBuffer Typesetter::ApplyMarkupProcessorOnPixelBuffer(Devel::PixelBuffer topPixelBuffer, const uint32_t& bufferWidth, const uint32_t& bufferHeight, bool ignoreHorizontalAlignment, Pixel::Format pixelFormat, const int32_t& horizontalOffset, const int32_t& verticalOffset) +void Typesetter::SetMaskForImageBuffer(Devel::PixelBuffer& __restrict__ topPixelBuffer, Devel::PixelBuffer& __restrict__ bottomPixelBuffer, const uint32_t bufferWidth, const uint32_t bufferHeight, float originAlpha) { - // Apply the markup-Processor if enabled - const bool markupProcessorEnabled = mModel->IsMarkupProcessorEnabled(); - if(markupProcessorEnabled) - { - topPixelBuffer = ApplyUnderlineMarkupImageBuffer(topPixelBuffer, bufferWidth, bufferHeight, ignoreHorizontalAlignment, pixelFormat, horizontalOffset, verticalOffset); + // Assume that we always combine two RGBA images + // Jump with 4bytes for optimize runtime. + uint32_t* topBuffer = reinterpret_cast(topPixelBuffer.GetBuffer()); + uint32_t* bottomBuffer = reinterpret_cast(bottomPixelBuffer.GetBuffer()); - topPixelBuffer = ApplyStrikethroughMarkupImageBuffer(topPixelBuffer, bufferWidth, bufferHeight, ignoreHorizontalAlignment, pixelFormat, horizontalOffset, verticalOffset); + if(topBuffer == NULL || bottomBuffer == NULL) + { + // Nothing to do if one of both buffers are empty. + return; } - return topPixelBuffer; + const uint32_t bufferSizeInt = bufferWidth * bufferHeight; + + for(uint32_t pixelIndex = 0; pixelIndex < bufferSizeInt; ++pixelIndex) + { + uint32_t topBufferColor = *(topBuffer); + uint32_t bottomBufferColor = *(bottomBuffer); + uint8_t* __restrict__ topBufferColorBuffer = reinterpret_cast(&topBufferColor); + uint8_t* __restrict__ bottomBufferColorBuffer = reinterpret_cast(&bottomBufferColor); + + // Return the transparency of the text to original. + uint8_t originAlphaInt = originAlpha * 255; + + uint8_t topAlpha = topBufferColorBuffer[3]; + uint8_t bottomAlpha = 255 - topAlpha; + + // Manual blending. + bottomBufferColorBuffer[0] = MultiplyAndSummationAndNormalizeColor(topBufferColorBuffer[0], originAlphaInt, bottomBufferColorBuffer[0], bottomAlpha); + bottomBufferColorBuffer[1] = MultiplyAndSummationAndNormalizeColor(topBufferColorBuffer[1], originAlphaInt, bottomBufferColorBuffer[1], bottomAlpha); + bottomBufferColorBuffer[2] = MultiplyAndSummationAndNormalizeColor(topBufferColorBuffer[2], originAlphaInt, bottomBufferColorBuffer[2], bottomAlpha); + bottomBufferColorBuffer[3] = MultiplyAndSummationAndNormalizeColor(topBufferColorBuffer[3], originAlphaInt, bottomBufferColorBuffer[3], bottomAlpha); + + *(bottomBuffer) = bottomBufferColor; + + // Increase each buffer's pointer. + ++topBuffer; + ++bottomBuffer; + } } Typesetter::Typesetter(const ModelInterface* const model)