* @param[in] position The position of the glyph.
* @param[in] color The color of the glyph.
* @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)
+ Typesetter::Style style,
+ Pixel::Format pixelFormat )
{
if( ( 0u == data.glyphBitmap.width ) || ( 0u == data.glyphBitmap.height ) )
{
const int widthMinusOne = static_cast<int>( data.width - 1u );
const int heightMinusOne = static_cast<int>( data.height - 1u );
- // Whether the given glyph is a color one.
- const bool isColorGlyph = Pixel::BGRA8888 == data.glyphBitmap.format;
-
- // Pointer to the color glyph if there is one.
- const uint32_t* const colorGlyphBuffer = isColorGlyph ? reinterpret_cast<uint32_t*>( data.glyphBitmap.buffer ) : NULL;
+ if ( Pixel::RGBA8888 == pixelFormat )
+ {
+ // Whether the given glyph is a color one.
+ const bool isColorGlyph = Pixel::BGRA8888 == data.glyphBitmap.format;
- // 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<uint8_t*>( &packedColor );
- *( packedColorBuffer + 2 ) = static_cast<uint8_t>( color->b * 255.f );
- *( packedColorBuffer + 1 ) = static_cast<uint8_t>( color->g * 255.f );
- *packedColorBuffer = static_cast<uint8_t>( color->r * 255.f );
+ // Pointer to the color glyph if there is one.
+ const uint32_t* const colorGlyphBuffer = isColorGlyph ? reinterpret_cast<uint32_t*>( data.glyphBitmap.buffer ) : NULL;
- // Initial vertical offset.
- const int yOffset = data.verticalOffset + position->y;
+ // 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<uint8_t*>( &packedColor );
+ *( packedColorBuffer + 2 ) = static_cast<uint8_t>( color->b * 255.f );
+ *( packedColorBuffer + 1 ) = static_cast<uint8_t>( color->g * 255.f );
+ *packedColorBuffer = static_cast<uint8_t>( color->r * 255.f );
- // Traverse the pixels of the glyph line per line.
- for( int lineIndex = 0, glyphHeight = static_cast<int>( data.glyphBitmap.height ); lineIndex < glyphHeight; ++lineIndex )
- {
- const int yOffsetIndex = yOffset + lineIndex;
- if( ( 0 > yOffsetIndex ) || ( yOffsetIndex > heightMinusOne ) )
- {
- // Do not write out of bounds.
- break;
- }
+ // Initial vertical offset.
+ const int yOffset = data.verticalOffset + position->y;
- const int verticalOffset = yOffsetIndex * data.width;
- const int xOffset = data.horizontalOffset + position->x;
- const int glyphBufferOffset = lineIndex * static_cast<int>( data.glyphBitmap.width );
- for( int index = 0, glyphWidth = static_cast<int>( data.glyphBitmap.width ); index < glyphWidth; ++index )
+ // Traverse the pixels of the glyph line per line.
+ for( int lineIndex = 0, glyphHeight = static_cast<int>( data.glyphBitmap.height ); lineIndex < glyphHeight; ++lineIndex )
{
- const int xOffsetIndex = xOffset + index;
- if( ( 0 > xOffsetIndex ) || ( xOffsetIndex > widthMinusOne ) )
+ const int yOffsetIndex = yOffset + lineIndex;
+ if( ( 0 > yOffsetIndex ) || ( yOffsetIndex > heightMinusOne ) )
{
- // Don't write out of bounds.
- break;
+ // Do not write out of bounds.
+ continue;
}
- uint32_t* bitmapBuffer = reinterpret_cast< uint32_t* >( data.bitmapBuffer.GetBuffer() );
-
- if( isColorGlyph )
+ const int verticalOffset = yOffsetIndex * data.width;
+ const int xOffset = data.horizontalOffset + position->x;
+ const int glyphBufferOffset = lineIndex * static_cast<int>( data.glyphBitmap.width );
+ for( int index = 0, glyphWidth = static_cast<int>( data.glyphBitmap.width ); index < glyphWidth; ++index )
{
- // Retrieves the color from the color glyph. The format is BGRA8888.
- uint32_t packedColorGlyph = *( colorGlyphBuffer + glyphBufferOffset + index );
- uint8_t* packedColorGlyphBuffer = reinterpret_cast<uint8_t*>( &packedColorGlyph );
-
- if( Typesetter::STYLE_SHADOW == style )
- {
- // The shadow of color glyph needs to have the shadow color.
- *( packedColorGlyphBuffer + 2 ) = static_cast<uint8_t>( color->b * 255.f );
- *( packedColorGlyphBuffer + 1 ) = static_cast<uint8_t>( color->g * 255.f );
- *packedColorGlyphBuffer = static_cast<uint8_t>( color->r * 255.f );
- }
- else
+ const int xOffsetIndex = xOffset + index;
+ if( ( 0 > xOffsetIndex ) || ( xOffsetIndex > widthMinusOne ) )
{
- std::swap( *packedColorGlyphBuffer, *( packedColorGlyphBuffer + 2u ) ); // Swap B and R.
+ // Don't write out of bounds.
+ continue;
}
- // Update the alpha channel.
- if( Typesetter::STYLE_MASK == style )
+ uint32_t* bitmapBuffer = reinterpret_cast< uint32_t* >( data.bitmapBuffer.GetBuffer() );
+
+ if( isColorGlyph )
{
- // Create an alpha mask for color glyph.
- *( packedColorGlyphBuffer + 3u ) = 0u;
+ // Retrieves the color from the color glyph. The format is BGRA8888.
+ uint32_t packedColorGlyph = *( colorGlyphBuffer + glyphBufferOffset + index );
+ uint8_t* packedColorGlyphBuffer = reinterpret_cast<uint8_t*>( &packedColorGlyph );
+
+ if( Typesetter::STYLE_SHADOW == style )
+ {
+ // The shadow of color glyph needs to have the shadow color.
+ *( packedColorGlyphBuffer + 2 ) = static_cast<uint8_t>( color->b * 255.f );
+ *( packedColorGlyphBuffer + 1 ) = static_cast<uint8_t>( color->g * 255.f );
+ *packedColorGlyphBuffer = static_cast<uint8_t>( color->r * 255.f );
+ }
+ else
+ {
+ std::swap( *packedColorGlyphBuffer, *( packedColorGlyphBuffer + 2u ) ); // Swap B and R.
+ }
+
+ // Update the alpha channel.
+ if( Typesetter::STYLE_MASK == style || Typesetter::STYLE_OUTLINE == style ) // Outline not shown for color glyph
+ {
+ // Create an alpha mask for color glyph.
+ *( packedColorGlyphBuffer + 3u ) = 0u;
+ }
+ else
+ {
+ *( packedColorGlyphBuffer + 3u ) = static_cast<uint8_t>( color->a * static_cast<float>( *( packedColorGlyphBuffer + 3u ) ) );
+ }
+
+ // Set the color into the final pixel buffer.
+ *( bitmapBuffer + verticalOffset + xOffsetIndex ) = packedColorGlyph;
}
else
{
- *( packedColorGlyphBuffer + 3u ) = static_cast<uint8_t>( color->a * static_cast<float>( *( packedColorGlyphBuffer + 3u ) ) );
+ // Update the alpha channel.
+ const uint8_t alpha = *( data.glyphBitmap.buffer + glyphBufferOffset + index );
+
+ // Copy non-transparent pixels only
+ if ( alpha > 0u )
+ {
+ // Check alpha of overlapped pixels
+ uint32_t& currentColor = *( bitmapBuffer + verticalOffset + xOffsetIndex );
+ uint8_t* packedCurrentColorBuffer = reinterpret_cast<uint8_t*>( ¤tColor );
+
+ uint8_t currentAlpha = *( packedCurrentColorBuffer + 3u );
+ uint8_t newAlpha = static_cast<uint8_t>( color->a * static_cast<float>( alpha ) );
+
+ // 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
+ // semi-transparent gaps between joint glyphs with overlapped pixels, which could
+ // happen, for example, in the RTL text when we copy glyphs from right to left).
+ *( packedColorBuffer + 3u ) = std::max( currentAlpha, newAlpha );
+
+ // Set the color into the final pixel buffer.
+ currentColor = packedColor;
+ }
}
+ }
+ }
+ }
+ else
+ {
+ // Initial vertical offset.
+ const int yOffset = data.verticalOffset + position->y;
- // Set the color into the final pixel buffer.
- *( bitmapBuffer + verticalOffset + xOffsetIndex ) = packedColorGlyph;
+ // Traverse the pixels of the glyph line per line.
+ for( int lineIndex = 0, glyphHeight = static_cast<int>( data.glyphBitmap.height ); lineIndex < glyphHeight; ++lineIndex )
+ {
+ const int yOffsetIndex = yOffset + lineIndex;
+ if( ( 0 > yOffsetIndex ) || ( yOffsetIndex > heightMinusOne ) )
+ {
+ // Do not write out of bounds.
+ continue;
}
- else
+
+ const int verticalOffset = yOffsetIndex * data.width;
+ const int xOffset = data.horizontalOffset + position->x;
+ const int glyphBufferOffset = lineIndex * static_cast<int>( data.glyphBitmap.width );
+ for( int index = 0, glyphWidth = static_cast<int>( data.glyphBitmap.width ); index < glyphWidth; ++index )
{
+ const int xOffsetIndex = xOffset + index;
+ if( ( 0 > xOffsetIndex ) || ( xOffsetIndex > widthMinusOne ) )
+ {
+ // Don't write out of bounds.
+ continue;
+ }
+
+ uint8_t* bitmapBuffer = reinterpret_cast< uint8_t* >( data.bitmapBuffer.GetBuffer() );
+
// Update the alpha channel.
const uint8_t alpha = *( data.glyphBitmap.buffer + glyphBufferOffset + index );
if ( alpha > 0u )
{
// Check alpha of overlapped pixels
- uint32_t& currentColor = *( bitmapBuffer + verticalOffset + xOffsetIndex );
- uint8_t* packedCurrentColorBuffer = reinterpret_cast<uint8_t*>( ¤tColor );
-
- uint8_t currentAlpha = *( packedCurrentColorBuffer + 3u );
+ uint8_t& currentAlpha = *( bitmapBuffer + verticalOffset + xOffsetIndex );
uint8_t newAlpha = static_cast<uint8_t>( color->a * static_cast<float>( alpha ) );
// 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
// semi-transparent gaps between joint glyphs with overlapped pixels, which could
// happen, for example, in the RTL text when we copy glyphs from right to left).
- *( packedColorBuffer + 3u ) = std::max( currentAlpha, newAlpha );
-
- // Set the color into the final pixel buffer.
- currentColor = packedColor;
+ *( bitmapBuffer + verticalOffset + xOffsetIndex ) = std::max( currentAlpha, newAlpha );
}
}
}
return mModel;
}
-PixelData Typesetter::Render( const Vector2& size, RenderBehaviour behaviour, bool ignoreHorizontalAlignment )
+PixelData Typesetter::Render( const Vector2& size, RenderBehaviour behaviour, bool ignoreHorizontalAlignment, Pixel::Format pixelFormat )
{
// @todo. This initial implementation for a TextLabel has only one visible page.
if( RENDER_MASK == behaviour )
{
// Generate the image buffer as an alpha mask for color glyphs.
- imageBuffer = CreateImageBuffer( bufferWidth, bufferHeight, Typesetter::STYLE_MASK, ignoreHorizontalAlignment, penY, 0u, numberOfGlyphs - 1 );
+ imageBuffer = CreateImageBuffer( bufferWidth, bufferHeight, Typesetter::STYLE_MASK, ignoreHorizontalAlignment, pixelFormat, penY, 0u, numberOfGlyphs - 1 );
}
else if( RENDER_NO_TEXT == behaviour )
{
else
{
// Generate the image buffer for the text with no style.
- imageBuffer = CreateImageBuffer( bufferWidth, bufferHeight, Typesetter::STYLE_NONE, ignoreHorizontalAlignment, penY, 0u, numberOfGlyphs -1 );
+ imageBuffer = CreateImageBuffer( bufferWidth, bufferHeight, Typesetter::STYLE_NONE, ignoreHorizontalAlignment, pixelFormat, penY, 0u, numberOfGlyphs -1 );
}
if ( ( RENDER_NO_STYLES != behaviour ) && ( RENDER_MASK != behaviour ) )
{
+
+ // Generate the outline if enabled
+ const float outlineWidth = mModel->GetOutlineWidth();
+ if ( outlineWidth > Math::MACHINE_EPSILON_1 )
+ {
+ // Create the image buffer for outline
+ Devel::PixelBuffer outlineImageBuffer = CreateImageBuffer( bufferWidth, bufferHeight, Typesetter::STYLE_OUTLINE, ignoreHorizontalAlignment, pixelFormat, penY, 0u, numberOfGlyphs -1 );
+
+ // Combine the two buffers
+ imageBuffer = CombineImageBuffer( imageBuffer, outlineImageBuffer, bufferWidth, bufferHeight );
+ }
+
// @todo. Support shadow and underline for partial text later on.
// Generate the shadow if enabled
if ( 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, penY, 0u, numberOfGlyphs - 1 );
+ Devel::PixelBuffer shadowImageBuffer = CreateImageBuffer( bufferWidth, bufferHeight, Typesetter::STYLE_SHADOW, ignoreHorizontalAlignment, pixelFormat, penY, 0u, numberOfGlyphs - 1 );
// Combine the two buffers
imageBuffer = CombineImageBuffer( imageBuffer, shadowImageBuffer, bufferWidth, bufferHeight );
if ( underlineEnabled )
{
// Create the image buffer for underline
- Devel::PixelBuffer underlineImageBuffer = CreateImageBuffer( bufferWidth, bufferHeight, Typesetter::STYLE_UNDERLINE, ignoreHorizontalAlignment, penY, 0u, numberOfGlyphs - 1 );
+ Devel::PixelBuffer underlineImageBuffer = CreateImageBuffer( bufferWidth, bufferHeight, Typesetter::STYLE_UNDERLINE, ignoreHorizontalAlignment, pixelFormat, penY, 0u, numberOfGlyphs - 1 );
// Combine the two buffers
imageBuffer = CombineImageBuffer( imageBuffer, underlineImageBuffer, bufferWidth, bufferHeight );
return pixelData;
}
-Devel::PixelBuffer Typesetter::CreateImageBuffer( const unsigned int bufferWidth, const unsigned int bufferHeight, Typesetter::Style style, bool ignoreHorizontalAlignment, int verticalOffset, GlyphIndex fromGlyphIndex, GlyphIndex toGlyphIndex )
+Devel::PixelBuffer Typesetter::CreateImageBuffer( const unsigned int bufferWidth, const unsigned int bufferHeight, Typesetter::Style style, bool ignoreHorizontalAlignment, Pixel::Format pixelFormat, int verticalOffset, GlyphIndex fromGlyphIndex, GlyphIndex toGlyphIndex )
{
// Retrieve lines, glyphs, positions and colors from the view model.
const Length modelNumberOfLines = mModel->GetNumberOfLines();
// Create and initialize the pixel buffer.
GlyphData glyphData;
glyphData.verticalOffset = verticalOffset;
-
glyphData.width = bufferWidth;
glyphData.height = bufferHeight;
- const unsigned int bufferSizeInt = bufferWidth * bufferHeight;
- const unsigned int bufferSizeChar = 4u * bufferSizeInt;
- glyphData.bitmapBuffer = Devel::PixelBuffer::New( bufferWidth, bufferHeight, Pixel::RGBA8888 );
- memset( glyphData.bitmapBuffer.GetBuffer(), 0u, bufferSizeChar );
+ glyphData.bitmapBuffer = Devel::PixelBuffer::New( bufferWidth, bufferHeight, pixelFormat );
+ glyphData.horizontalOffset = 0;
+
+ if ( Pixel::RGBA8888 == pixelFormat )
+ {
+ const unsigned int bufferSizeInt = bufferWidth * bufferHeight;
+ const unsigned int bufferSizeChar = 4u * bufferSizeInt;
+ memset( glyphData.bitmapBuffer.GetBuffer(), 0u, bufferSizeChar );
+ }
+ else
+ {
+ memset( glyphData.bitmapBuffer.GetBuffer(), 0, bufferWidth * bufferHeight );
+ }
// Get a handle of the font client. Used to retrieve the bitmaps of the glyphs.
TextAbstraction::FontClient fontClient = TextAbstraction::FontClient::Get();
// Increases the vertical offset with the line's ascender.
glyphData.verticalOffset += static_cast<int>( line.ascender );
- if ( style == Typesetter::STYLE_SHADOW )
+ // Retrieves the glyph's outline width
+ float outlineWidth = mModel->GetOutlineWidth();
+
+ if( style == Typesetter::STYLE_OUTLINE )
+ {
+ glyphData.horizontalOffset -= outlineWidth;
+ if( lineIndex == 0u )
+ {
+ // Only need to add the vertical outline offset for the first line
+ glyphData.verticalOffset -= outlineWidth;
+ }
+ }
+ else if ( style == Typesetter::STYLE_SHADOW )
{
const Vector2& shadowOffset = mModel->GetShadowOffset();
- glyphData.horizontalOffset += shadowOffset.x;
+ glyphData.horizontalOffset += shadowOffset.x - outlineWidth; // if outline enabled then shadow should offset from outline
+
if ( lineIndex == 0u )
{
- // Only need to add the vertical shadow offset for once
- glyphData.verticalOffset += shadowOffset.y;
+ // Only need to add the vertical shadow offset for first line
+ glyphData.verticalOffset += shadowOffset.y - outlineWidth;
}
}
{
color = &( mModel->GetShadowColor() );
}
+ else if ( style == Typesetter::STYLE_OUTLINE )
+ {
+ color = &( mModel->GetOutlineColor() );
+ }
else
{
color = ( useDefaultColor || ( 0u == colorIndex ) ) ? &defaultColor : colorsBuffer + ( colorIndex - 1u );
glyphData.glyphBitmap.buffer = NULL;
glyphData.glyphBitmap.width = glyphInfo->width; // Desired width and height.
glyphData.glyphBitmap.height = glyphInfo->height;
+
+ if( style != Typesetter::STYLE_OUTLINE && style != Typesetter::STYLE_SHADOW )
+ {
+ // Don't render outline for other styles
+ outlineWidth = 0.0f;
+ }
+
fontClient.CreateBitmap( glyphInfo->fontId,
glyphInfo->index,
- glyphData.glyphBitmap );
+ glyphData.glyphBitmap,
+ outlineWidth );
// Sets the glyph's bitmap into the bitmap of the whole text.
if( NULL != glyphData.glyphBitmap.buffer )
TypesetGlyph( glyphData,
position,
color,
- style );
+ style,
+ pixelFormat);
// delete the glyphBitmap.buffer as it is now copied into glyphData.bitmapBuffer
delete []glyphData.glyphBitmap.buffer;
glyphData.glyphBitmap.buffer = NULL;
break;
}
+ // Always RGBA image for text with styles
uint32_t* bitmapBuffer = reinterpret_cast< uint32_t* >( glyphData.bitmapBuffer.GetBuffer() );
uint32_t underlinePixel = *( bitmapBuffer + y * glyphData.width + x );
uint8_t* underlinePixelBuffer = reinterpret_cast<uint8_t*>( &underlinePixel );
return topPixelBuffer;
}
+ // Always combine two RGBA images
const unsigned int bufferSizeInt = bufferWidth * bufferHeight;
const unsigned int bufferSizeChar = 4u * bufferSizeInt;