void OsmAnd::SymbolRasterizer_P::rasterize( const std::shared_ptr<const MapPrimitiviser::PrimitivisedObjects>& primitivisedObjects, QList< std::shared_ptr<const RasterizedSymbolsGroup> >& outSymbolsGroups, const FilterByMapObject filter, const std::shared_ptr<const IQueryController>& queryController) const { const auto& env = primitivisedObjects->mapPresentationEnvironment; for (const auto& symbolGroupEntry : rangeOf(constOf(primitivisedObjects->symbolsGroups))) { if (queryController && queryController->isAborted()) return; const auto& mapObject = symbolGroupEntry.key(); const auto& symbolsGroup = symbolGroupEntry.value(); ////////////////////////////////////////////////////////////////////////// //if (mapObject->toString().contains("1333827773")) //{ // int i = 5; //} ////////////////////////////////////////////////////////////////////////// // Apply filter, if it's present if (filter && !filter(mapObject)) continue; // Create group const std::shared_ptr<RasterizedSymbolsGroup> group(new RasterizedSymbolsGroup( mapObject)); // Total offset allows several symbols to stack into column. Offset specifies center of symbol bitmap. // This offset is computed only in case symbol is not on-path and not along-path PointI totalOffset; for (const auto& symbol : constOf(symbolsGroup->symbols)) { if (queryController && queryController->isAborted()) return; if (const auto& textSymbol = std::dynamic_pointer_cast<const MapPrimitiviser::TextSymbol>(symbol)) { TextRasterizer::Style style; if (!textSymbol->drawOnPath && textSymbol->shieldResourceName.isEmpty()) style.wrapWidth = textSymbol->wrapWidth; QList< std::shared_ptr<const SkBitmap> > backgroundLayers; if (!textSymbol->shieldResourceName.isEmpty()) { std::shared_ptr<const SkBitmap> shield; env->obtainTextShield(textSymbol->shieldResourceName, shield); if (shield) backgroundLayers.push_back(shield); } if (!textSymbol->underlayIconResourceName.isEmpty()) { std::shared_ptr<const SkBitmap> icon; env->obtainMapIcon(textSymbol->underlayIconResourceName, icon); if (icon) backgroundLayers.push_back(icon); } style.backgroundBitmap = SkiaUtilities::mergeBitmaps(backgroundLayers); if (!qFuzzyCompare(textSymbol->scaleFactor, 1.0f) && style.backgroundBitmap) { style.backgroundBitmap = SkiaUtilities::scaleBitmap( style.backgroundBitmap, textSymbol->scaleFactor, textSymbol->scaleFactor); } style .setBold(textSymbol->isBold) .setItalic(textSymbol->isItalic) .setColor(textSymbol->color) .setSize(static_cast<int>(textSymbol->size)); if (textSymbol->shadowRadius > 0) { style .setHaloColor(textSymbol->shadowColor) .setHaloRadius(textSymbol->shadowRadius); } float lineSpacing; float symbolExtraTopSpace; float symbolExtraBottomSpace; QVector<SkScalar> glyphsWidth; const auto rasterizedText = owner->textRasterizer->rasterize( textSymbol->value, style, textSymbol->drawOnPath ? &glyphsWidth : nullptr, &symbolExtraTopSpace, &symbolExtraBottomSpace, &lineSpacing); if (!rasterizedText) continue; #if OSMAND_DUMP_SYMBOLS { QDir::current().mkpath("text_symbols"); std::unique_ptr<SkImageEncoder> encoder(CreatePNGImageEncoder()); QString filename; filename.sprintf("%s\\text_symbols\\%p.png", qPrintable(QDir::currentPath()), rasterizedText.get()); encoder->encodeFile(qPrintable(filename), *rasterizedText.get(), 100); } #endif // OSMAND_DUMP_SYMBOLS if (textSymbol->drawOnPath) { // Publish new rasterized symbol const std::shared_ptr<RasterizedOnPathSymbol> rasterizedSymbol(new RasterizedOnPathSymbol( group, textSymbol)); rasterizedSymbol->bitmap = qMove(rasterizedText); rasterizedSymbol->order = textSymbol->order; rasterizedSymbol->contentType = RasterizedSymbol::ContentType::Text; rasterizedSymbol->content = textSymbol->value; rasterizedSymbol->languageId = textSymbol->languageId; rasterizedSymbol->minDistance = textSymbol->minDistance; rasterizedSymbol->glyphsWidth = glyphsWidth; group->symbols.push_back(qMove(rasterizedSymbol)); } else { // Calculate local offset. Since offset specifies center, it's a sum of // - vertical offset // - extra top space (which should be in texture, but not rendered, since transparent) // - height / 2 // This calculation is used only if this symbol is not first. Otherwise only following is used: // - vertical offset PointI localOffset; localOffset.y += textSymbol->verticalOffset; if (!group->symbols.isEmpty() && !textSymbol->drawAlongPath) { localOffset.y += symbolExtraTopSpace; localOffset.y += rasterizedText->height() / 2; } // Increment total offset if (!textSymbol->drawAlongPath) totalOffset += localOffset; // Publish new rasterized symbol const std::shared_ptr<RasterizedSpriteSymbol> rasterizedSymbol(new RasterizedSpriteSymbol(group, textSymbol)); rasterizedSymbol->bitmap = rasterizedText; rasterizedSymbol->order = textSymbol->order; rasterizedSymbol->contentType = RasterizedSymbol::ContentType::Text; rasterizedSymbol->content = textSymbol->value; rasterizedSymbol->languageId = textSymbol->languageId; rasterizedSymbol->minDistance = textSymbol->minDistance; rasterizedSymbol->location31 = textSymbol->location31; rasterizedSymbol->offset = textSymbol->drawAlongPath ? localOffset : totalOffset; rasterizedSymbol->drawAlongPath = textSymbol->drawAlongPath; if (!qIsNaN(textSymbol->intersectionSizeFactor)) { rasterizedSymbol->intersectionBBox = AreaI::fromCenterAndSize(PointI(), PointI( static_cast<int>(textSymbol->intersectionSizeFactor * rasterizedText->width()), static_cast<int>(textSymbol->intersectionSizeFactor * rasterizedText->height()))); } else if (!qIsNaN(textSymbol->intersectionSize)) { rasterizedSymbol->intersectionBBox = AreaI::fromCenterAndSize(PointI(), PointI( static_cast<int>(textSymbol->intersectionSize), static_cast<int>(textSymbol->intersectionSize))); } else if (!qIsNaN(textSymbol->intersectionMargin)) { rasterizedSymbol->intersectionBBox = AreaI::fromCenterAndSize(PointI(), PointI( rasterizedText->width() + static_cast<int>(textSymbol->intersectionMargin), rasterizedText->height() + static_cast<int>(textSymbol->intersectionMargin))); } else { rasterizedSymbol->intersectionBBox = AreaI::fromCenterAndSize(PointI(), PointI( static_cast<int>(rasterizedText->width()), static_cast<int>(rasterizedText->height()))); rasterizedSymbol->intersectionBBox.top() -= static_cast<int>(symbolExtraTopSpace); rasterizedSymbol->intersectionBBox.bottom() += static_cast<int>(symbolExtraBottomSpace); } group->symbols.push_back(qMove(std::shared_ptr<const RasterizedSymbol>(rasterizedSymbol))); // Next symbol should also take into account: // - height / 2 // - extra bottom space (which should be in texture, but not rendered, since transparent) // - spacing between lines if (!textSymbol->drawAlongPath) { totalOffset.y += rasterizedText->height() / 2; totalOffset.y += symbolExtraBottomSpace; totalOffset.y += qCeil(lineSpacing); } } } else if (const auto& iconSymbol = std::dynamic_pointer_cast<const MapPrimitiviser::IconSymbol>(symbol)) { std::shared_ptr<const SkBitmap> iconBitmap; if (!env->obtainMapIcon(iconSymbol->resourceName, iconBitmap) || !iconBitmap) continue; if (!qFuzzyCompare(iconSymbol->scaleFactor, 1.0f)) { iconBitmap = SkiaUtilities::scaleBitmap( iconBitmap, iconSymbol->scaleFactor, iconSymbol->scaleFactor); } std::shared_ptr<const SkBitmap> backgroundBitmap; if (!iconSymbol->shieldResourceName.isEmpty()) { env->obtainIconShield(iconSymbol->shieldResourceName, backgroundBitmap); if (!qFuzzyCompare(iconSymbol->scaleFactor, 1.0f) && backgroundBitmap) { backgroundBitmap = SkiaUtilities::scaleBitmap( backgroundBitmap, iconSymbol->scaleFactor, iconSymbol->scaleFactor); } } QList< std::shared_ptr<const SkBitmap> > layers; if (backgroundBitmap) layers.push_back(backgroundBitmap); for (const auto& overlayResourceName : constOf(iconSymbol->underlayResourceNames)) { std::shared_ptr<const SkBitmap> underlayBitmap; if (!env->obtainMapIcon(overlayResourceName, underlayBitmap) || !underlayBitmap) continue; layers.push_back(underlayBitmap); } layers.push_back(iconBitmap); for (const auto& overlayResourceName : constOf(iconSymbol->overlayResourceNames)) { std::shared_ptr<const SkBitmap> overlayBitmap; if (!env->obtainMapIcon(overlayResourceName, overlayBitmap) || !overlayBitmap) continue; layers.push_back(overlayBitmap); } // Compose final image const auto rasterizedIcon = SkiaUtilities::mergeBitmaps(layers); #if OSMAND_DUMP_SYMBOLS { QDir::current().mkpath("icon_symbols"); std::unique_ptr<SkImageEncoder> encoder(CreatePNGImageEncoder()); QString filename; filename.sprintf("%s\\icon_symbols\\%p.png", qPrintable(QDir::currentPath()), rasterizedIcon.get()); encoder->encodeFile(qPrintable(filename), *rasterizedIcon, 100); } #endif // OSMAND_DUMP_SYMBOLS // Calculate local offset. Since offset specifies center, it's a sum of // - height / 2 // This calculation is used only if this symbol is not first. Otherwise nothing is used. PointI localOffset; if (!qFuzzyIsNull(iconSymbol->offsetFactor.x)) localOffset.x = qRound(iconSymbol->offsetFactor.x * rasterizedIcon->width()); if (!qFuzzyIsNull(iconSymbol->offsetFactor.y)) localOffset.y = qRound(iconSymbol->offsetFactor.y * rasterizedIcon->height()); if (!group->symbols.isEmpty() && !iconSymbol->drawAlongPath) localOffset.y += rasterizedIcon->height() / 2; // Increment total offset if (!iconSymbol->drawAlongPath) totalOffset += localOffset; // Publish new rasterized symbol const std::shared_ptr<RasterizedSpriteSymbol> rasterizedSymbol(new RasterizedSpriteSymbol(group, iconSymbol)); rasterizedSymbol->bitmap = rasterizedIcon; rasterizedSymbol->order = iconSymbol->order; rasterizedSymbol->contentType = RasterizedSymbol::ContentType::Icon; rasterizedSymbol->content = iconSymbol->resourceName; rasterizedSymbol->languageId = LanguageId::Invariant; rasterizedSymbol->minDistance = iconSymbol->minDistance; rasterizedSymbol->location31 = iconSymbol->location31; rasterizedSymbol->offset = iconSymbol->drawAlongPath ? localOffset : totalOffset; rasterizedSymbol->drawAlongPath = iconSymbol->drawAlongPath; if (!qIsNaN(iconSymbol->intersectionSizeFactor)) { rasterizedSymbol->intersectionBBox = AreaI::fromCenterAndSize(PointI(), PointI( static_cast<int>(iconSymbol->intersectionSizeFactor * rasterizedIcon->width()), static_cast<int>(iconSymbol->intersectionSizeFactor * rasterizedIcon->height()))); } else if (!qIsNaN(iconSymbol->intersectionSize)) { rasterizedSymbol->intersectionBBox = AreaI::fromCenterAndSize(PointI(), PointI( static_cast<int>(iconSymbol->intersectionSize), static_cast<int>(iconSymbol->intersectionSize))); } else if (!qIsNaN(iconSymbol->intersectionMargin)) { rasterizedSymbol->intersectionBBox = AreaI::fromCenterAndSize(PointI(), PointI( rasterizedIcon->width() + static_cast<int>(iconSymbol->intersectionMargin), rasterizedIcon->height() + static_cast<int>(iconSymbol->intersectionMargin))); } else { rasterizedSymbol->intersectionBBox = AreaI::fromCenterAndSize(PointI(), PointI( static_cast<int>(rasterizedIcon->width()), static_cast<int>(rasterizedIcon->height()))); } group->symbols.push_back(qMove(std::shared_ptr<const RasterizedSymbol>(rasterizedSymbol))); // Next symbol should also take into account: // - height / 2 if (!iconSymbol->drawAlongPath) totalOffset.y += rasterizedIcon->height() / 2; } } // Add group to output outSymbolsGroups.push_back(qMove(group)); } }
void OsmAnd::SymbolRasterizer_P::rasterize( const std::shared_ptr<const Primitiviser::PrimitivisedArea>& primitivizedArea, QList< std::shared_ptr<const RasterizedSymbolsGroup> >& outSymbolsGroups, std::function<bool (const std::shared_ptr<const Model::BinaryMapObject>& mapObject)> filter, const IQueryController* const controller) { const auto& env = primitivizedArea->mapPresentationEnvironment; for (const auto& symbolsEntry : rangeOf(constOf(primitivizedArea->symbolsBySourceObjects))) { if (controller && controller->isAborted()) return; // Apply filter, if it's present if (filter && !filter(symbolsEntry.key())) continue; // Create group const auto constructedGroup = new RasterizedSymbolsGroup(symbolsEntry.key()); std::shared_ptr<const RasterizedSymbolsGroup> group(constructedGroup); // Total offset allows several symbols to stack into column. // Offset specifies center of symbol bitmap PointI totalOffset; for (const auto& symbol : constOf(symbolsEntry.value())) { if (controller && controller->isAborted()) return; if (const auto& textSymbol = std::dynamic_pointer_cast<const Primitiviser::TextSymbol>(symbol)) { TextRasterizer::Style style; if (!textSymbol->drawOnPath && textSymbol->shieldResourceName.isEmpty()) style.wrapWidth = textSymbol->wrapWidth; if (!textSymbol->shieldResourceName.isEmpty()) env->obtainTextShield(textSymbol->shieldResourceName, style.backgroundBitmap); style .setBold(textSymbol->isBold) .setColor(textSymbol->color) .setSize(textSymbol->size); if (textSymbol->shadowRadius > 0) { style .setHaloColor(textSymbol->shadowColor) .setHaloRadius(textSymbol->shadowRadius + 2 /*px*/); //NOTE: ^^^ This is same as specifying 'x:2' in style, but due to backward compatibility with Android, leave as-is } float lineSpacing; float symbolExtraTopSpace; float symbolExtraBottomSpace; QVector<SkScalar> glyphsWidth; const auto rasterizedText = TextRasterizer::getInstance().rasterize( textSymbol->value, style, textSymbol->drawOnPath ? &glyphsWidth : nullptr, &symbolExtraTopSpace, &symbolExtraBottomSpace, &lineSpacing); #if OSMAND_DUMP_SYMBOLS { QDir::current().mkpath("text_symbols"); std::unique_ptr<SkImageEncoder> encoder(CreatePNGImageEncoder()); QString filename; filename.sprintf("%s\\text_symbols\\%p.png", qPrintable(QDir::currentPath()), rasterizedText.get()); encoder->encodeFile(qPrintable(filename), *rasterizedText.get(), 100); } #endif // OSMAND_DUMP_SYMBOLS if (textSymbol->drawOnPath) { // Publish new rasterized symbol const auto rasterizedSymbol = new RasterizedOnPathSymbol( group, constructedGroup->mapObject, qMove(rasterizedText), textSymbol->order, textSymbol->value, textSymbol->languageId, textSymbol->minDistance, glyphsWidth); assert(static_cast<bool>(rasterizedSymbol->bitmap)); constructedGroup->symbols.push_back(qMove(std::shared_ptr<const RasterizedSymbol>(rasterizedSymbol))); } else { // Calculate local offset. Since offset specifies center, it's a sum of // - vertical offset // - extra top space (which should be in texture, but not rendered, since transparent) // - height / 2 // This calculation is used only if this symbol is not first. Otherwise only following is used: // - vertical offset PointI localOffset; localOffset.y += textSymbol->verticalOffset; if (!constructedGroup->symbols.isEmpty()) { localOffset.y += symbolExtraTopSpace; localOffset.y += rasterizedText->height() / 2; } // Increment total offset totalOffset += localOffset; // Publish new rasterized symbol const auto rasterizedSymbol = new RasterizedSpriteSymbol( group, constructedGroup->mapObject, qMove(rasterizedText), textSymbol->order, textSymbol->value, textSymbol->languageId, textSymbol->minDistance, textSymbol->location31, totalOffset); assert(static_cast<bool>(rasterizedSymbol->bitmap)); constructedGroup->symbols.push_back(qMove(std::shared_ptr<const RasterizedSymbol>(rasterizedSymbol))); // Next symbol should also take into account: // - height / 2 // - extra bottom space (which should be in texture, but not rendered, since transparent) // - spacing between lines totalOffset.y += rasterizedText->height() / 2; totalOffset.y += symbolExtraBottomSpace; totalOffset.y += qCeil(lineSpacing); } } else if (const auto& iconSymbol = std::dynamic_pointer_cast<const Primitiviser::IconSymbol>(symbol)) { std::shared_ptr<const SkBitmap> bitmap; if (!env->obtainMapIcon(iconSymbol->resourceName, bitmap) || !bitmap) continue; #if OSMAND_DUMP_SYMBOLS { QDir::current().mkpath("icon_symbols"); std::unique_ptr<SkImageEncoder> encoder(CreatePNGImageEncoder()); QString filename; filename.sprintf("%s\\icon_symbols\\%p.png", qPrintable(QDir::currentPath()), bitmap.get()); encoder->encodeFile(qPrintable(filename), *bitmap, 100); } #endif // OSMAND_DUMP_SYMBOLS // Calculate local offset. Since offset specifies center, it's a sum of // - height / 2 // This calculation is used only if this symbol is not first. Otherwise nothing is used. PointI localOffset; if (!constructedGroup->symbols.isEmpty()) localOffset.y += bitmap->height() / 2; // Increment total offset totalOffset += localOffset; // Publish new rasterized symbol const auto rasterizedSymbol = new RasterizedSpriteSymbol( group, constructedGroup->mapObject, qMove(bitmap), iconSymbol->order, iconSymbol->resourceName, LanguageId::Invariant, PointI(), iconSymbol->location31, totalOffset); assert(static_cast<bool>(rasterizedSymbol->bitmap)); constructedGroup->symbols.push_back(qMove(std::shared_ptr<const RasterizedSymbol>(rasterizedSymbol))); // Next symbol should also take into account: // - height / 2 totalOffset.y += bitmap->height() / 2; } } // Add group to output outSymbolsGroups.push_back(qMove(group)); } }
void rasterize(std::ostream &output, const OsmAnd::EyePiece::Configuration& cfg) #endif { // Obtain and configure rasterization style context OsmAnd::MapStyles stylesCollection; for(auto itStyleFile = cfg.styleFiles.begin(); itStyleFile != cfg.styleFiles.end(); ++itStyleFile) { const auto& styleFile = *itStyleFile; if(!stylesCollection.registerStyle(styleFile.absoluteFilePath())) output << xT("Failed to parse metadata of '") << QStringToStlString(styleFile.fileName()) << xT("' or duplicate style") << std::endl; } std::shared_ptr<const OsmAnd::MapStyle> style; if(!stylesCollection.obtainStyle(cfg.styleName, style)) { output << xT("Failed to resolve style '") << QStringToStlString(cfg.styleName) << xT("'") << std::endl; return; } if(cfg.dumpRules) style->dump(); OsmAnd::ObfsCollection obfsCollection; obfsCollection.watchDirectory(cfg.obfsDir); // Collect all map objects (this should be replaced by something like RasterizerViewport/RasterizerContext) QList< std::shared_ptr<const OsmAnd::Model::MapObject> > mapObjects; OsmAnd::AreaI bbox31( OsmAnd::Utilities::get31TileNumberY(cfg.bbox.top), OsmAnd::Utilities::get31TileNumberX(cfg.bbox.left), OsmAnd::Utilities::get31TileNumberY(cfg.bbox.bottom), OsmAnd::Utilities::get31TileNumberX(cfg.bbox.right) ); const auto& obfDI = obfsCollection.obtainDataInterface(); OsmAnd::MapFoundationType mapFoundation; obfDI->obtainMapObjects(&mapObjects, &mapFoundation, bbox31, cfg.zoom, nullptr); bool basemapAvailable; obfDI->obtainBasemapPresenceFlag(basemapAvailable); // Calculate output size in pixels const auto tileWidth = OsmAnd::Utilities::getTileNumberX(cfg.zoom, cfg.bbox.right) - OsmAnd::Utilities::getTileNumberX(cfg.zoom, cfg.bbox.left); const auto tileHeight = OsmAnd::Utilities::getTileNumberY(cfg.zoom, cfg.bbox.bottom) - OsmAnd::Utilities::getTileNumberY(cfg.zoom, cfg.bbox.top); const auto pixelWidth = qCeil(tileWidth * cfg.tileSide); const auto pixelHeight = qCeil(tileHeight * cfg.tileSide); output << xT("Will rasterize ") << mapObjects.count() << xT(" objects onto ") << pixelWidth << xT("x") << pixelHeight << xT(" bitmap") << std::endl; // Allocate render target SkBitmap renderSurface; renderSurface.setConfig(cfg.is32bit ? SkBitmap::kARGB_8888_Config : SkBitmap::kRGB_565_Config, pixelWidth, pixelHeight); if(!renderSurface.allocPixels()) { output << xT("Failed to allocated render target ") << pixelWidth << xT("x") << pixelHeight; return; } SkDevice renderTarget(renderSurface); // Create render canvas SkCanvas canvas(&renderTarget); // Perform actual rendering OsmAnd::RasterizerEnvironment rasterizerEnv(style, basemapAvailable); OsmAnd::RasterizerContext rasterizerContext; OsmAnd::Rasterizer::prepareContext(rasterizerEnv, rasterizerContext, bbox31, cfg.zoom, cfg.tileSide, cfg.densityFactor, mapFoundation, mapObjects, OsmAnd::PointF(), nullptr); if(cfg.drawMap) OsmAnd::Rasterizer::rasterizeMap(rasterizerEnv, rasterizerContext, true, canvas, nullptr); /*if(cfg.drawText) OsmAnd::Rasterizer::rasterizeText(rasterizerContext, !cfg.drawMap, canvas, nullptr);*/ // Save rendered area if(!cfg.output.isEmpty()) { std::unique_ptr<SkImageEncoder> encoder(CreatePNGImageEncoder()); std::unique_ptr<SkImageEncoder> outputStream(CreatePNGImageEncoder()); encoder->encodeFile(cfg.output.toLocal8Bit(), renderSurface, 100); } return; }
void OsmAnd::SymbolRasterizer_P::rasterize( const std::shared_ptr<const Primitiviser::PrimitivisedArea>& primitivizedArea, QList< std::shared_ptr<const RasterizedSymbolsGroup> >& outSymbolsGroups, std::function<bool (const std::shared_ptr<const Model::BinaryMapObject>& mapObject)> filter, const IQueryController* const controller) { const auto& env = primitivizedArea->mapPresentationEnvironment; for (const auto& symbolGroupEntry : rangeOf(constOf(primitivizedArea->symbolsGroups))) { if (controller && controller->isAborted()) return; const auto& mapObject = symbolGroupEntry.key(); const auto& symbolsGroup = symbolGroupEntry.value(); ////////////////////////////////////////////////////////////////////////// //if ((mapObject->id >> 1) == 189600735u) //{ // int i = 5; //} //else // continue; ////////////////////////////////////////////////////////////////////////// // Apply filter, if it's present if (filter && !filter(mapObject)) continue; // Create group const std::shared_ptr<RasterizedSymbolsGroup> group(new RasterizedSymbolsGroup(mapObject)); // Total offset allows several symbols to stack into column. Offset specifies center of symbol bitmap. // This offset is computed only in case symbol is not on-path and not along-path PointI totalOffset; for (const auto& symbol : constOf(symbolsGroup->symbols)) { if (controller && controller->isAborted()) return; if (const auto& textSymbol = std::dynamic_pointer_cast<const Primitiviser::TextSymbol>(symbol)) { TextRasterizer::Style style; if (!textSymbol->drawOnPath && textSymbol->shieldResourceName.isEmpty()) style.wrapWidth = textSymbol->wrapWidth; if (!textSymbol->shieldResourceName.isEmpty()) env->obtainTextShield(textSymbol->shieldResourceName, style.backgroundBitmap); style .setBold(textSymbol->isBold) .setColor(textSymbol->color) .setSize(textSymbol->size); if (textSymbol->shadowRadius > 0) { style .setHaloColor(textSymbol->shadowColor) .setHaloRadius(textSymbol->shadowRadius + 2 /*px*/); //NOTE: ^^^ This is same as specifying 'x:2' in style, but due to backward compatibility with Android, leave as-is } float lineSpacing; float symbolExtraTopSpace; float symbolExtraBottomSpace; QVector<SkScalar> glyphsWidth; const auto rasterizedText = TextRasterizer::globalInstance().rasterize( textSymbol->value, style, textSymbol->drawOnPath ? &glyphsWidth : nullptr, &symbolExtraTopSpace, &symbolExtraBottomSpace, &lineSpacing); if (!rasterizedText) continue; #if OSMAND_DUMP_SYMBOLS { QDir::current().mkpath("text_symbols"); std::unique_ptr<SkImageEncoder> encoder(CreatePNGImageEncoder()); QString filename; filename.sprintf("%s\\text_symbols\\%p.png", qPrintable(QDir::currentPath()), rasterizedText.get()); encoder->encodeFile(qPrintable(filename), *rasterizedText.get(), 100); } #endif // OSMAND_DUMP_SYMBOLS if (textSymbol->drawOnPath) { if (textSymbol->drawOnPath && textSymbol->verticalOffset > 0) { LogPrintf(LogSeverityLevel::Warning, "Hey, on-path + vertical offset is not supported!"); //assert(false); } // Publish new rasterized symbol const std::shared_ptr<RasterizedOnPathSymbol> rasterizedSymbol(new RasterizedOnPathSymbol(group, textSymbol)); rasterizedSymbol->bitmap = qMove(rasterizedText); rasterizedSymbol->order = textSymbol->order; rasterizedSymbol->contentType = RasterizedSymbol::ContentType::Text; rasterizedSymbol->content = textSymbol->value; rasterizedSymbol->languageId = textSymbol->languageId; rasterizedSymbol->minDistance = textSymbol->minDistance; rasterizedSymbol->glyphsWidth = glyphsWidth; group->symbols.push_back(qMove(rasterizedSymbol)); } else { // Calculate local offset. Since offset specifies center, it's a sum of // - vertical offset // - extra top space (which should be in texture, but not rendered, since transparent) // - height / 2 // This calculation is used only if this symbol is not first. Otherwise only following is used: // - vertical offset PointI localOffset; localOffset.y += textSymbol->verticalOffset; if (!group->symbols.isEmpty() && !textSymbol->drawAlongPath) { localOffset.y += symbolExtraTopSpace; localOffset.y += rasterizedText->height() / 2; } // Increment total offset if (!textSymbol->drawAlongPath) totalOffset += localOffset; // Publish new rasterized symbol const std::shared_ptr<RasterizedSpriteSymbol> rasterizedSymbol(new RasterizedSpriteSymbol(group, textSymbol)); rasterizedSymbol->bitmap = rasterizedText; rasterizedSymbol->order = textSymbol->order; rasterizedSymbol->contentType = RasterizedSymbol::ContentType::Text; rasterizedSymbol->content = textSymbol->value; rasterizedSymbol->languageId = textSymbol->languageId; rasterizedSymbol->minDistance = textSymbol->minDistance; rasterizedSymbol->location31 = textSymbol->location31; rasterizedSymbol->offset = textSymbol->drawAlongPath ? localOffset : totalOffset; rasterizedSymbol->drawAlongPath = textSymbol->drawAlongPath; rasterizedSymbol->intersectionSize = PointI(-1, -1); group->symbols.push_back(qMove(std::shared_ptr<const RasterizedSymbol>(rasterizedSymbol))); // Next symbol should also take into account: // - height / 2 // - extra bottom space (which should be in texture, but not rendered, since transparent) // - spacing between lines if (!textSymbol->drawAlongPath) { totalOffset.y += rasterizedText->height() / 2; totalOffset.y += symbolExtraBottomSpace; totalOffset.y += qCeil(lineSpacing); } } } else if (const auto& iconSymbol = std::dynamic_pointer_cast<const Primitiviser::IconSymbol>(symbol)) { std::shared_ptr<const SkBitmap> iconBitmap; if (!env->obtainMapIcon(iconSymbol->resourceName, iconBitmap) || !iconBitmap) continue; std::shared_ptr<const SkBitmap> backgroundBitmap; if (!iconSymbol->shieldResourceName.isEmpty()) env->obtainIconShield(iconSymbol->shieldResourceName, backgroundBitmap); // Compose final image std::shared_ptr<const SkBitmap> rasterizedIcon; if (!backgroundBitmap) rasterizedIcon = iconBitmap; else { // Compute composed image size const auto rasterizedIconWidth = qMax(iconBitmap->width(), backgroundBitmap->width()); const auto rasterizedIconHeight = qMax(iconBitmap->height(), backgroundBitmap->height()); // Create a bitmap that will be hold entire symbol const auto pRasterizedIcon = new SkBitmap(); rasterizedIcon.reset(pRasterizedIcon); pRasterizedIcon->setConfig(SkBitmap::kARGB_8888_Config, rasterizedIconWidth, rasterizedIconHeight); pRasterizedIcon->allocPixels(); pRasterizedIcon->eraseColor(SK_ColorTRANSPARENT); // Create canvas SkBitmapDevice target(*pRasterizedIcon); SkCanvas canvas(&target); // Draw the background canvas.drawBitmap(*backgroundBitmap, (rasterizedIconWidth - backgroundBitmap->width()) / 2.0f, (rasterizedIconHeight - backgroundBitmap->height()) / 2.0f, nullptr); // Draw the icon canvas.drawBitmap(*iconBitmap, (rasterizedIconWidth - iconBitmap->width()) / 2.0f, (rasterizedIconHeight - iconBitmap->height()) / 2.0f, nullptr); // Flush all operations canvas.flush(); } #if OSMAND_DUMP_SYMBOLS { QDir::current().mkpath("icon_symbols"); std::unique_ptr<SkImageEncoder> encoder(CreatePNGImageEncoder()); QString filename; filename.sprintf("%s\\icon_symbols\\%p.png", qPrintable(QDir::currentPath()), rasterizedIcon.get()); encoder->encodeFile(qPrintable(filename), *rasterizedIcon, 100); } #endif // OSMAND_DUMP_SYMBOLS // Calculate local offset. Since offset specifies center, it's a sum of // - height / 2 // This calculation is used only if this symbol is not first. Otherwise nothing is used. PointI localOffset; if (!group->symbols.isEmpty() && !iconSymbol->drawAlongPath) localOffset.y += rasterizedIcon->height() / 2; // Increment total offset if (!iconSymbol->drawAlongPath) totalOffset += localOffset; // Publish new rasterized symbol const std::shared_ptr<RasterizedSpriteSymbol> rasterizedSymbol(new RasterizedSpriteSymbol(group, iconSymbol)); rasterizedSymbol->bitmap = rasterizedIcon; rasterizedSymbol->order = iconSymbol->order; rasterizedSymbol->contentType = RasterizedSymbol::ContentType::Icon; rasterizedSymbol->content = iconSymbol->resourceName; rasterizedSymbol->languageId = LanguageId::Invariant; rasterizedSymbol->minDistance = PointI(); rasterizedSymbol->location31 = iconSymbol->location31; rasterizedSymbol->offset = iconSymbol->drawAlongPath ? localOffset : totalOffset; rasterizedSymbol->drawAlongPath = iconSymbol->drawAlongPath; rasterizedSymbol->intersectionSize = PointI(iconSymbol->intersectionSize, iconSymbol->intersectionSize); group->symbols.push_back(qMove(std::shared_ptr<const RasterizedSymbol>(rasterizedSymbol))); // Next symbol should also take into account: // - height / 2 if (!iconSymbol->drawAlongPath) totalOffset.y += rasterizedIcon->height() / 2; } } // Add group to output outSymbolsGroups.push_back(qMove(group)); } }