#include #include #include #include #include #include #include #include #include #include #include namespace mbgl { std::unique_ptr createStyleImage(const std::string& id, const PremultipliedImage& image, const int32_t srcX, const int32_t srcY, const int32_t width, const int32_t height, const double ratio, const bool sdf, style::ImageStretches&& stretchX, style::ImageStretches&& stretchY, const optional& content) { // Disallow invalid parameter configurations. if (width <= 0 || height <= 0 || width > 1024 || height > 1024 || ratio <= 0 || ratio > 10 || srcX < 0 || srcY < 0 || srcX >= static_cast(image.size.width) || srcY >= static_cast(image.size.height) || srcX + width > static_cast(image.size.width) || srcY + height > static_cast(image.size.height)) { Log::Error(Event::Sprite, "Can't create image with invalid metrics: %dx%d@%d,%d in %ux%u@%sx sprite", width, height, srcX, srcY, image.size.width, image.size.height, util::toString(ratio).c_str()); return nullptr; } const Size size(static_cast(width), static_cast(height)); PremultipliedImage dstImage(size); // Copy from the source image into our individual sprite image PremultipliedImage::copy(image, dstImage, {static_cast(srcX), static_cast(srcY)}, {0, 0}, size); try { return std::make_unique( id, std::move(dstImage), ratio, sdf, std::move(stretchX), std::move(stretchY), content); } catch (const util::StyleImageException& ex) { Log::Error(Event::Sprite, "Can't create image with invalid metadata: %s", ex.what()); return nullptr; } } namespace { uint16_t getUInt16(const JSValue& value, const char* property, const char* name, const uint16_t def = 0) { if (value.HasMember(property)) { auto& v = value[property]; if (v.IsUint() && v.GetUint() <= std::numeric_limits::max()) { return v.GetUint(); } else { Log::Warning(Event::Sprite, "Invalid sprite image '%s': value of '%s' must be an integer between 0 and 65535", name, property); } } return def; } double getDouble(const JSValue& value, const char* property, const char* name, const double def = 0) { if (value.HasMember(property)) { auto& v = value[property]; if (v.IsNumber()) { return v.GetDouble(); } else { Log::Warning(Event::Sprite, "Invalid sprite image '%s': value of '%s' must be a number", name, property); } } return def; } bool getBoolean(const JSValue& value, const char* property, const char* name, const bool def = false) { if (value.HasMember(property)) { auto& v = value[property]; if (v.IsBool()) { return v.GetBool(); } else { Log::Warning(Event::Sprite, "Invalid sprite image '%s': value of '%s' must be a boolean", name, property); } } return def; } style::ImageStretches getStretches(const JSValue& value, const char* property, const char* name) { style::ImageStretches stretches; if (value.HasMember(property)) { auto& v = value[property]; if (v.IsArray()) { for (rapidjson::SizeType i = 0; i < v.Size(); ++i) { const JSValue& stretch = v[i]; if (stretch.IsArray() && stretch.Size() == 2 && stretch[rapidjson::SizeType(0)].IsNumber() && stretch[rapidjson::SizeType(1)].IsNumber()) { stretches.emplace_back(style::ImageStretch{stretch[rapidjson::SizeType(0)].GetFloat(), stretch[rapidjson::SizeType(1)].GetFloat()}); } else { Log::Warning(Event::Sprite, "Invalid sprite image '%s': members of '%s' must be an array of two numbers", name, property); } } } else { Log::Warning(Event::Sprite, "Invalid sprite image '%s': value of '%s' must be an array", name, property); } } return stretches; } optional getContent(const JSValue& value, const char* property, const char* name) { if (value.HasMember(property)) { auto& content = value[property]; if (content.IsArray() && content.Size() == 4 && content[rapidjson::SizeType(0)].IsNumber() && content[rapidjson::SizeType(1)].IsNumber() && content[rapidjson::SizeType(2)].IsNumber() && content[rapidjson::SizeType(3)].IsNumber()) { return style::ImageContent{content[rapidjson::SizeType(0)].GetFloat(), content[rapidjson::SizeType(1)].GetFloat(), content[rapidjson::SizeType(2)].GetFloat(), content[rapidjson::SizeType(3)].GetFloat()}; } else { Log::Warning(Event::Sprite, "Invalid sprite image '%s': value of '%s' must be an array of four numbers", name, property); } } return nullopt; } } // namespace std::vector> parseSprite(const std::string& encodedImage, const std::string& json) { const PremultipliedImage raster = decodeImage(encodedImage); JSDocument doc; doc.Parse<0>(json.c_str()); if (doc.HasParseError()) { throw std::runtime_error("Failed to parse JSON: " + formatJSONParseError(doc)); } if (!doc.IsObject()) { throw std::runtime_error("Sprite JSON root must be an object"); } const auto& properties = doc.GetObject(); std::vector> images; images.reserve(properties.MemberCount()); for (const auto& property : properties) { const std::string name = {property.name.GetString(), property.name.GetStringLength()}; const JSValue& value = property.value; if (value.IsObject()) { const uint16_t x = getUInt16(value, "x", name.c_str(), 0); const uint16_t y = getUInt16(value, "y", name.c_str(), 0); const uint16_t width = getUInt16(value, "width", name.c_str(), 0); const uint16_t height = getUInt16(value, "height", name.c_str(), 0); const double pixelRatio = getDouble(value, "pixelRatio", name.c_str(), 1); const bool sdf = getBoolean(value, "sdf", name.c_str(), false); style::ImageStretches stretchX = getStretches(value, "stretchX", name.c_str()); style::ImageStretches stretchY = getStretches(value, "stretchY", name.c_str()); optional content = getContent(value, "content", name.c_str()); auto image = createStyleImage( name, raster, x, y, width, height, pixelRatio, sdf, std::move(stretchX), std::move(stretchY), content); if (image) { images.push_back(std::move(image->baseImpl)); } } } assert([&images] { std::sort(images.begin(), images.end()); return std::unique(images.begin(), images.end()) == images.end(); }()); return images; } } // namespace mbgl