Fixing SVG Rendering Quality in Flutter: A Deep Dive
Fuzzy, pixelated rendering of what should be crisp vectors, and inconsistent sizing of SVGs led me down a path to creating a new package.

While working with SVG images in Flutter, I needed a way to render raw SVG strings directly in the Image
widget, along with getting crisp, properly scaled vector graphics. While investigating existing solutions, I found the flutter_svg_provider
package by yang-f, which provided a great starting point but it needed some improvements in rendering quality and features.
Background
The flutter_svg_provider
package (https://github.com/yang-f/flutter_svg_provider) was created to bridge the gap between Flutter’s Image
widget and SVG files, using flutter_svg
for parsing. While functional, the package has several open unanswered issues regarding image quality, particularly when rendering at different scales.
I really feel packages in pub.dev could benefit from an abandoned flag!
The Original Implementation
The original package used a direct approach to convert vectors (specifically SVG) to raster images:
final image = pictureInfo.picture.toImage(
pictureInfo.size.width.round(),
pictureInfo.size.height.round(),
);
This implementation, while straightforward, led to quality issues:
- Blurry/pixelated rendering
- Inconsistent sizing
- Poor scaling on high-DPI displays
The Problem
My primary need was to render raw SVG strings directly in the Image
widget, but the rendering issues needed to be addressed first. The original package’s use of Flutter’s built-in toImage()
method to convert SVG pictures to raster images led to two major issues:
1. Fuzzy/pixelated rendering of what should be crisp vector graphics, and
2. Inconsistent sizing where SVGs wouldn’t render at their intended dimensions.
This direct conversion didn’t properly handle scaling and transformation, resulting in quality loss and sizing issues.
The Solution
The key to fixing these issues was to take control of the rendering process using Flutter’s Canvas
API. I also improved the package’s functionality by adding better package asset support, SVG validation and error handling.
Here’s the enhanced implementation:
static Future<ImageInfo> _loadAsync(SvgImageKey key, Color color) async {
final rawSvg = await getSvgString(key);
try {
final pictureInfo = await vg.loadPicture(
SvgStringLoader(rawSvg, theme: SvgTheme(currentColor: color)),
null,
clipViewbox: false,
);
final recorder = ui.PictureRecorder();
final canvas = Canvas(recorder);
final scaleX = key.pixelWidth / pictureInfo.size.width;
final scaleY = key.pixelHeight / pictureInfo.size.height;
final scale = math.min(scaleX, scaleY);
final dx = (key.pixelWidth - (pictureInfo.size.width * scale)) / 2;
final dy = (key.pixelHeight - (pictureInfo.size.height * scale)) / 2;
canvas.translate(dx, dy);
canvas.scale(scale);
canvas.drawPicture(pictureInfo.picture);
final image = await recorder.endRecording().toImage(
key.pixelWidth,
key.pixelHeight,
);
return ImageInfo(image: image, scale: 1.0);
} catch (e) {
throw Exception('Failed to render SVG: $e');
}
}
Key Improvements
1. Precise Scaling: By calculating the exact scale needed to fit the SVG into the target size while maintaining aspect ratio, we ensure the image is neither stretched nor pixelated.
2. Direct Canvas Control: Using PictureRecorder
and Canvas
gives us full control over the rendering process, ensuring optimal quality.
3. Correct Dimensions: The final image is rendered exactly at the requested dimensions without unwanted scaling artifacts.
4. Raw Source Support: Added support for loading SVGs directly from their raw source, allowing for more flexibility in usage scenarios.
5. Package Source Support: Added proper support for loading SVGs from other packages with validation:
case SvgSource.package:
if (key.package == null) {
throw ArgumentError(
'Package parameter is required for SvgSource.package',
);
}
final packagePath = 'packages/${key.package}/${key.path}';
return await rootBundle.loadString(packagePath);
6. Improved Error Handling: Added comprehensive error handling for each source type with specific error messages:
— Network timeouts and status code validation
— File existence checks
— Package asset validation
— Raw SVG string validation
7. Centered Rendering: SVGs are now properly centered within their target bounds using calculated offsets:
// Calculate centering offsets
final dx = (key.pixelWidth - (pictureInfo.size.width * scale)) / 2;
final dy = (key.pixelHeight - (pictureInfo.size.height * scale)) / 2;
// Scale and center the SVG
canvas.translate(dx, dy);
canvas.scale(scale);
8. Network Timeout Control: Added timeout handling for network requests to prevent hanging:
try {
final response = await http.get(Uri.parse(key.path))
.timeout(const Duration(seconds: 10));
if (response.statusCode != 200) {
throw Exception(
'Failed to load network SVG. Status: ${response.statusCode}',
);
}
return response.body;
} on TimeoutException {
throw Exception('Network SVG request timed out');
}
9. SVG Validation System: Added comprehensive SVG validation with configurable options:
— Structure validation (tags, namespace, content)
— ViewBox validation
— Dimension constraints
— Attribute validation
— Element whitelist enforcement
— Configurable validation levels (none
, basic
, strict
)
10. Security Features: Implemented security-focused validation to prevent common SVG-based attacks:
— Script element blocking
— External resource validation
— Malicious attribute detection
— Size constraint enforcement
Same Simple Usage
The improved SVG provider maintains simple usage while supporting multiple source types:
// From assets
Image(
width: 32,
height: 32,
image: SvgProvider('assets/my_icon.svg'),
);
// From network
Image(
width: 32,
height: 32,
image: SvgProvider(
'https://example.com/my_icon.svg',
source: SvgSource.network,
),
);
// From file
Image(
width: 32,
height: 32,
image: SvgProvider(
'path/to/my_icon.svg',
source: SvgSource.file,
),
);
// From package
Image(
width: 32,
height: 32,
image: SvgProvider(
'assets/icons/my_icon.svg',
color: Colors.blue,
source: SvgSource.package,
package: 'my_package',
),
);
// From raw
Image(
width: 32,
height: 32,
image: SvgProvider(
// SVG image credit: https://lucide.dev/icons/bot
r'<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bot"><path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/></svg>',
source: SvgSource.raw,
),
);
Source Types
The provider supports multiple source types through the SvgSource
enum:
- asset
: Load from local assets (default) — for SVGs in your app’s asset bundle.
- network
: Load from URLs — supports timeout handling and status code validation.
- file
: Load from file system — includes existence checks and error handling.
- package
: Load from other packages — properly validates package parameters.
- raw
: Use raw SVG string directly — includes validation of string content.
Validation

The new SVG provider includes a robust validation system with configurable options to ensure both quality and security:
// Basic validation - structure only
Image(
width: 32,
height: 32,
image: SvgProvider(
'assets/icon.svg',
validationOptions: SvgValidationOptions.basic,
),
);
// Strict validation - all checks enabled
Image(
width: 32,
height: 32,
image: SvgProvider(
'assets/icon.svg',
validationOptions: SvgValidationOptions.strict,
),
);
// Custom validation configuration
Image(
width: 32,
height: 32,
image: SvgProvider(
'assets/icon.svg',
validationOptions: SvgValidationOptions(
validateStructure: true,
validateViewBox: true,
validateDimensions: true,
maxDimension: 1000,
minDimension: 10,
validateAttributes: true,
validateElements: true,
),
),
);
Validation Options
The validation system offers three preset levels:
1. None (SvgValidationOptions.none
):
— No validation performed
— Use only with trusted SVGs
— Fastest performance
2. Basic (SvgValidationOptions.basic
):
— Validates SVG structure
— Checks namespace
— Ensures basic content validity
— Good balance of security and performance
3. Strict (SvgValidationOptions.strict
):
— All validations enabled
— Maximum security
— Best for untrusted sources
Custom Validation
You can create custom validation configurations to match your specific needs:
final options = SvgValidationOptions(
// Structure validation
validateStructure: true, // Check basic SVG structure
// Size constraints
validateDimensions: true,
maxDimension: 1000, // Maximum allowed dimension
minDimension: 10, // Minimum allowed dimension
// Content validation
validateViewBox: true, // Ensure valid viewBox
validateAttributes: true, // Check attribute formatting
validateElements: true, // Validate against allowlist
);
Security Considerations

SVG files can pose significant security risks when not properly validated. The validation system helps prevent several common attack vectors:
Common SVG Security Risks
- Script Injection
— Risk: SVGs can include<script>
tags that execute JavaScript
— Prevention: Element validation blocks script tags
// Blocks SVGs containing script elements
final options = SvgValidationOptions(
validateElements: true, // Enables element validation
);
2. External Resource Loading
— Risk: SVGs can load external resources via <use>
, <image>
, etc.
— Prevention: Attribute validation and element restrictions
// Validates external references
final options = SvgValidationOptions(
validateAttributes: true,
validateElements: true,
);
3. XML Entity Expansion
— Risk: Malicious XML entities can cause Denial of Service (DoS) attacks
— Prevention: Structure validation catches malformed XML
// Basic validation catches XML issues
final options = SvgValidationOptions.basic;
Real-World Security Incidents
Several high-profile security incidents demonstrate the importance of proper SVG validation:
1. WordPress CVE-2023–23488
From the WordPress Security Release notes:
“Authenticated users could upload SVG files that could lead to cross-site scripting in certain configurations.”
This vulnerability allowed contributors to upload SVG files containing malicious scripts that could be executed when embedded in posts and pages, highlighting how SVGs can be weaponized for XSS attacks.
2. Electron CVE-2020–15216
A more severe vulnerability demonstrating how SVGs can be used for Remote Code Execution:
“A specially crafted SVG containing MPEG4 content could trigger a heap buffer overflow”
This incident showed that SVG security isn’t just about script injection — embedded multimedia content in SVGs could lead to buffer overflows and potential code execution in Electron applications.
3. ModSecurity CRS CVE-2021–35368
A vulnerability in the ModSecurity Core Rule Set that demonstrated how SVG namespaces could be used to bypass security controls:
“Fixed bypass for SVG namespace allowing XSS”
This incident revealed how attackers could use SVG namespace manipulation to bypass XSS protection rules, emphasizing the importance of proper namespace validation.
How Validation Prevents These Attacks
The validation system prevents these types of attacks through multiple layers of security:
1. Element and Namespace Validation
// Prevents XSS, unsafe embedded content, and namespace abuse
final options = SvgValidationOptions(
validateElements: true, // Only allows safe SVG elements
validateStructure: true, // Ensures proper namespace usage
);
This prevents:
- Script tags like used in the WordPress XSS attack
- Multimedia content that led to the Electron RCE
- Namespace manipulation seen in the ModSecurity bypass
2. Content Structure Validation
// Ensures SVG content is well-formed and safe
final options = SvgValidationOptions(
validateStructure: true,
validateAttributes: true,
);
This validates:
- SVG namespace and structure
- Attribute values that could contain malicious content
- Embedded content references
- Proper namespace declarations and usage
3. Comprehensive Protection
// Maximum security for untrusted SVGs
final options = SvgValidationOptions.strict;
This enables all validations:
- Element whitelist checking
- Attribute validation
- Structure verification
- Size constraints
- Content validation
- Namespace validation
Best Practices
To ensure maximum security when working with SVGs:
- Always Enable Validation
// Minimum recommended configuration
final options = SvgValidationOptions.basic;
2. Use Strict Validation for Untrusted Sources
// For network or user-provided SVGs
Image(
image: SvgProvider(
'https://example.com/icon.svg',
source: SvgSource.network,
validationOptions: SvgValidationOptions.strict,
),
);
3. Set Appropriate Size Constraints
// Prevent DoS through oversized SVGs
final options = SvgValidationOptions(
validateDimensions: true,
maxDimension: 1000,
minDimension: 1,
);
4. Validate All External Sources
// For any external SVG source
final options = SvgValidationOptions(
validateStructure: true,
validateAttributes: true,
validateElements: true,
);
Error Handling

The improved implementation includes comprehensive error handling for common failure scenarios:
- Network failures with specific timeout and status code handling
- Missing or invalid files
- Invalid package configurations
- Malformed SVG content
- Resource loading failures
Each error case provides detailed error messages to aid in debugging:
case SvgSource.file:
try {
final file = File(key.path);
if (!await file.exists()) {
throw Exception('SVG file not found: ${key.path}');
}
return await file.readAsString();
} catch (e) {
throw Exception('Failed to load SVG file: $e');
}
Unit Tests
The new package has a comprehensive suite of 18 unit tests covering:
1. SVG validation functionality
2. Different source types (asset
, network
, file
, package
, raw
)
3. Error handling scenarios
4. Configuration options
Here’s a summary of what I’ve tested:
SVG Validation:
- Structure validation
- ViewBox validation
- Dimension constraints
- Element validation (security)
Source Types:
- Loading from assets
- Loading from network (with timeout and error handling)
- Package assets
- Raw SVG strings
Error Handling:
- Missing package parameters
- Invalid SVG content
- Network errors
Configuration:
- Default and custom sizes
- Color tinting
- Scale factors
Conclusion
When working with SVGs in Flutter, it’s crucial to handle the rendering process carefully to maintain quality and proper sizing. By taking control of the rendering pipeline using Canvas
operations and improving the source
options, I’ve created a robust SVG rendering solution that maintains the quality and precision expected from vector graphics.
This solution demonstrates how diving deeper into Flutter’s rendering capabilities can help solve what initially appears to be a simple scaling issue. The result is a more robust and feature-complete SVG provider that maintains the ease of use of the original package while delivering superior rendering quality.
The security enhancements in this implementation help prevent common SVG-based attacks while maintaining the flexibility and ease of use of the original package. By providing configurable validation options, developers can choose the appropriate level of security for their specific use case.
The complete implementation is available as an open-source package at https://pub.dev/packages/svg_provider and released under the same Apache 2.0 License as the original work by yang-f.