+/// Draws the specified strikethrough color to the buffer
+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 StrikethroughStyleProperties& commonStrikethroughProperties,
+ const StrikethroughStyleProperties& currentStrikethroughProperties,
+ const LineRun& line)
+{
+ const Vector4& strikethroughColor = currentStrikethroughProperties.colorDefined ? currentStrikethroughProperties.color : commonStrikethroughProperties.color;
+
+ const uint32_t yRangeMin = static_cast<uint32_t>(strikethroughStartingYPosition);
+ const uint32_t yRangeMax = std::min(bufferHeight, static_cast<uint32_t>(strikethroughStartingYPosition + maxStrikethroughHeight));
+ const uint32_t xRangeMin = static_cast<uint32_t>(glyphData.horizontalOffset + lineExtentLeft);
+ const uint32_t xRangeMax = std::min(bufferWidth, static_cast<uint32_t>(glyphData.horizontalOffset + lineExtentRight + 1)); // Due to include last point, we add 1 here
+
+ // If current glyph don't need to be rendered, just ignore.
+ if(yRangeMax <= yRangeMin || xRangeMax <= xRangeMin)
+ {
+ return;
+ }
+
+ // We can optimize by memset when strikethroughColor.a is near zero
+ uint8_t strikethroughColorAlpha = static_cast<uint8_t>(strikethroughColor.a * 255.f);
+
+ uint32_t* bitmapBuffer = reinterpret_cast<uint32_t*>(glyphData.bitmapBuffer.GetBuffer());
+
+ // Skip yRangeMin line.
+ bitmapBuffer += yRangeMin * glyphData.width;
+
+ if(strikethroughColorAlpha == 0)
+ {
+ for(uint32_t y = yRangeMin; y < yRangeMax; y++)
+ {
+ // We can use memset.
+ memset(bitmapBuffer + xRangeMin, 0, (xRangeMax - xRangeMin) * sizeof(uint32_t));
+ bitmapBuffer += glyphData.width;
+ }
+ }
+ else
+ {
+ uint32_t packedStrikethroughColor = 0u;
+ uint8_t* packedStrikethroughColorBuffer = reinterpret_cast<uint8_t*>(&packedStrikethroughColor);
+
+ // Write the color to the pixel buffer
+ *(packedStrikethroughColorBuffer + 3u) = strikethroughColorAlpha;
+ *(packedStrikethroughColorBuffer + 2u) = static_cast<uint8_t>(strikethroughColor.b * strikethroughColorAlpha);
+ *(packedStrikethroughColorBuffer + 1u) = static_cast<uint8_t>(strikethroughColor.g * strikethroughColorAlpha);
+ *(packedStrikethroughColorBuffer) = static_cast<uint8_t>(strikethroughColor.r * strikethroughColorAlpha);
+
+ for(uint32_t y = yRangeMin; y < yRangeMax; y++)
+ {
+ for(uint32_t x = xRangeMin; x < xRangeMax; x++)
+ {
+ // Note : this is same logic as bitmap[y][x] = strikethroughColor;
+ *(bitmapBuffer + x) = packedStrikethroughColor;
+ }
+ bitmapBuffer += glyphData.width;
+ }
+ }
+}
+
+/**
+ * @brief Create an initialized image buffer filled with transparent color.
+ *
+ * Creates the pixel data used to generate the final image with the given size.
+ *
+ * @param[in] bufferWidth The width of the image buffer.
+ * @param[in] bufferHeight The height of the image buffer.
+ * @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).
+ *
+ * @return An image buffer.
+ */
+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;
+ memset(imageBuffer.GetBuffer(), 0, bufferSizeChar);
+ }
+ else
+ {
+ memset(imageBuffer.GetBuffer(), 0, bufferWidth * bufferHeight);
+ }
+
+ return imageBuffer;
+}
+
+/**
+ * @brief Combine the two RGBA image buffers together.
+ *
+ * The top layer buffer will blend over the bottom layer buffer:
+ * - If the pixel is not fully opaque from either buffer, it will be blended with
+ * the pixel from the other buffer and copied to the combined buffer.
+ * - If the pixels from both buffers are fully opaque, the pixels from the top layer
+ * buffer will be copied to the combined buffer.
+ *
+ * Due to the performance issue, We need to re-use input'ed pixelBuffer memory.
+ * We can determine which pixelBuffer's memory is destination
+ *
+ * @param[in, out] topPixelBuffer The top layer buffer.
+ * @param[in, out] bottomPixelBuffer The bottom layer buffer.
+ * @param[in] bufferWidth The width of the image buffer.
+ * @param[in] bufferHeight The height of the image buffer.
+ * @param[in] storeResultIntoTop True if we store the combined image buffer result into topPixelBuffer.
+ * 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)
+{
+ // Assume that we always combine two RGBA images
+ // Jump with 4bytes for optimize runtime.
+ uint32_t* topBuffer = reinterpret_cast<uint32_t*>(topPixelBuffer.GetBuffer());
+ uint32_t* bottomBuffer = reinterpret_cast<uint32_t*>(bottomPixelBuffer.GetBuffer());
+
+ if(topBuffer == NULL && bottomBuffer == NULL)
+ {
+ // Nothing to do if both buffers are empty.
+ return;
+ }
+
+ if(topBuffer == NULL)
+ {
+ // Nothing to do if topBuffer is empty.
+ // If we need to store the result into top, change topPixelBuffer as bottomPixelBuffer.
+ if(storeResultIntoTop)
+ {
+ topPixelBuffer = bottomPixelBuffer;
+ }
+ return;
+ }
+
+ if(bottomBuffer == NULL)
+ {
+ // Nothing to do if bottomBuffer is empty.
+ // If we need to store the result into bottom, change bottomPixelBuffer as topPixelBuffer.
+ if(!storeResultIntoTop)
+ {
+ bottomPixelBuffer = topPixelBuffer;
+ }
+ return;
+ }
+
+ const uint32_t bufferSizeInt = bufferWidth * bufferHeight;
+
+ uint32_t* combinedBuffer = storeResultIntoTop ? topBuffer : bottomBuffer;
+ uint8_t* topAlphaBufferPointer = reinterpret_cast<uint8_t*>(topBuffer) + 3;
+
+ for(uint32_t pixelIndex = 0; pixelIndex < bufferSizeInt; ++pixelIndex)
+ {
+ // If the alpha of the pixel in either buffer is not fully opaque, blend the two pixels.
+ // Otherwise, copy pixel from topBuffer to combinedBuffer.
+ // Note : Be careful when we read & write into combinedBuffer. It can be write into same pointer.
+
+ uint8_t topAlpha = *topAlphaBufferPointer;
+
+ if(topAlpha == 0)
+ {
+ // Copy the pixel from bottomBuffer to combinedBuffer
+ if(storeResultIntoTop)
+ {
+ *(combinedBuffer) = *(bottomBuffer);
+ }
+ }
+ else if(topAlpha == 255)
+ {
+ // Copy the pixel from topBuffer to combinedBuffer
+ if(!storeResultIntoTop)
+ {
+ *(combinedBuffer) = *(topBuffer);
+ }
+ }
+ else
+ {
+ // 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<uint8_t*>(&blendedBottomBufferColor);
+
+ blendedBottomBufferColorBuffer[0] = MultiplyAndNormalizeColor(blendedBottomBufferColorBuffer[0], 255 - topAlpha);
+ blendedBottomBufferColorBuffer[1] = MultiplyAndNormalizeColor(blendedBottomBufferColorBuffer[1], 255 - topAlpha);
+ blendedBottomBufferColorBuffer[2] = MultiplyAndNormalizeColor(blendedBottomBufferColorBuffer[2], 255 - topAlpha);
+ blendedBottomBufferColorBuffer[3] = MultiplyAndNormalizeColor(blendedBottomBufferColorBuffer[3], 255 - topAlpha);
+
+ *(combinedBuffer) = *(topBuffer) + blendedBottomBufferColor;
+ }
+
+ // Increase each buffer's pointer.
+ ++combinedBuffer;
+ ++topBuffer;
+ ++bottomBuffer;
+ topAlphaBufferPointer += sizeof(uint32_t) / sizeof(uint8_t);
+ }
+}
+