#853 apply scale when decoding SVG

This commit is contained in:
Thibault Deckers 2023-12-24 16:10:26 +01:00
parent eb004d8eca
commit 8981900d6b
7 changed files with 24 additions and 13 deletions

View file

@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
## <a id="unreleased"></a>[Unreleased]
### Fixed
- crash when loading SVG defined with large dimensions
## <a id="v1.10.1"></a>[v1.10.1] - 2023-12-21
- Cataloguing: detect/filter `Ultra HDR`

View file

@ -89,6 +89,7 @@ class MediaFetchBytesHandler(private val context: Context) : MethodCallHandler {
MimeTypes.SVG -> SvgRegionFetcher(context).fetch(
uri = uri,
sizeBytes = sizeBytes,
scale = sampleSize,
regionRect = regionRect,
imageWidth = imageWidth,
imageHeight = imageHeight,

View file

@ -25,6 +25,7 @@ class SvgRegionFetcher internal constructor(
suspend fun fetch(
uri: Uri,
sizeBytes: Long?,
scale: Int,
regionRect: Rect,
imageWidth: Int,
imageHeight: Int,
@ -36,14 +37,6 @@ class SvgRegionFetcher internal constructor(
return
}
// use `Long` as rect size could be unexpectedly large and go beyond `Int` max
val targetBitmapSizeBytes: Long = ARGB_8888_BYTE_SIZE.toLong() * regionRect.width() * regionRect.height()
if (!MemoryUtils.canAllocate(targetBitmapSizeBytes)) {
// decoding a region that large would yield an OOM when creating the bitmap
result.error("fetch-read-large-region", "SVG region too large for uri=$uri regionRect=$regionRect", null)
return
}
var currentSvgRef = lastSvgRef
if (currentSvgRef != null && currentSvgRef.uri != uri) {
currentSvgRef = null
@ -75,8 +68,8 @@ class SvgRegionFetcher internal constructor(
val viewBox = svg.documentViewBox
val svgWidth = viewBox.width()
val svgHeight = viewBox.height()
val xf = imageWidth / ceil(svgWidth)
val yf = imageHeight / ceil(svgHeight)
val xf = imageWidth / scale / ceil(svgWidth)
val yf = imageHeight / scale / ceil(svgHeight)
// some SVG paths do not respect the rendering viewbox and do not reach its edges
// so we render to a slightly larger bitmap, using a slightly larger viewbox,
// and crop that bitmap to the target region size
@ -95,6 +88,15 @@ class SvgRegionFetcher internal constructor(
val targetBitmapWidth = regionRect.width()
val targetBitmapHeight = regionRect.height()
// use `Long` as rect size could be unexpectedly large and go beyond `Int` max
val targetBitmapSizeBytes: Long = ARGB_8888_BYTE_SIZE.toLong() * targetBitmapWidth * targetBitmapHeight
if (!MemoryUtils.canAllocate(targetBitmapSizeBytes)) {
// decoding a region that large would yield an OOM when creating the bitmap
result.error("fetch-read-large-region", "SVG region too large for uri=$uri regionRect=$regionRect", null)
return
}
var bitmap = Bitmap.createBitmap(
targetBitmapWidth + bleedX * 2,
targetBitmapHeight + bleedY * 2,

View file

@ -3,7 +3,9 @@ import 'dart:ui';
int highestPowerOf2(num x) => x < 1 ? 0 : pow(2, (log(x) / ln2).floor()).toInt();
int smallestPowerOf2(num x) => x < 1 ? 1 : pow(2, (log(x) / ln2).ceil()).toInt();
num smallestPowerOf2(num x, {bool allowNegativePower = false}) {
return x < 1 && !allowNegativePower ? 1 : pow(2, (log(x) / ln2).ceil());
}
double roundToPrecision(final double value, {required final int decimals}) => (value * pow(10, decimals)).round() / pow(10, decimals);

View file

@ -439,7 +439,7 @@ class _GeoMapState extends State<GeoMap> {
if (points.length != geoEntry.pointsSize) {
// `Fluster.points()` method does not always return all the points contained in a cluster
// the higher `nodeSize` is, the higher the chance to get all the points (i.e. as many as the cluster `pointsSize`)
_slowMarkerCluster ??= _buildFluster(nodeSize: smallestPowerOf2(entries.length));
_slowMarkerCluster ??= _buildFluster(nodeSize: smallestPowerOf2(entries.length).toInt());
points = _slowMarkerCluster!.points(clusterId);
assert(points.length == geoEntry.pointsSize, 'got ${points.length}/${geoEntry.pointsSize} for geoEntry=$geoEntry');
}

View file

@ -286,7 +286,7 @@ class _VectorImageViewState extends State<VectorImageView> {
required double scale,
required double devicePixelRatio,
}) =>
smallestPowerOf2(scale * devicePixelRatio).toDouble();
smallestPowerOf2(scale * devicePixelRatio, allowNegativePower: true).toDouble();
}
typedef _BackgroundFrameBuilder = Widget Function(Widget child, int? frame, Rect tileRect);

View file

@ -20,6 +20,8 @@ void main() {
expect(smallestPowerOf2(-42), 1);
expect(smallestPowerOf2(.5), 1);
expect(smallestPowerOf2(1.5), 2);
expect(smallestPowerOf2(0.5, allowNegativePower: true), 0.5);
expect(smallestPowerOf2(0.1, allowNegativePower: true), 0.125);
});
test('rounding to a given precision after the decimal', () {