diff --git a/CHANGELOG.md b/CHANGELOG.md
index 71011bf79..e75d2d440 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
+### Fixed
+
+- crash when loading SVG defined with large dimensions
+
## [v1.10.1] - 2023-12-21
- Cataloguing: detect/filter `Ultra HDR`
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchBytesHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchBytesHandler.kt
index 2c96888b6..232fa09cd 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchBytesHandler.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MediaFetchBytesHandler.kt
@@ -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,
diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/SvgRegionFetcher.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/SvgRegionFetcher.kt
index e453c7ffc..882844c6e 100644
--- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/SvgRegionFetcher.kt
+++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/fetchers/SvgRegionFetcher.kt
@@ -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,
diff --git a/lib/utils/math_utils.dart b/lib/utils/math_utils.dart
index 82be6858d..68a3f1ec9 100644
--- a/lib/utils/math_utils.dart
+++ b/lib/utils/math_utils.dart
@@ -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);
diff --git a/lib/widgets/common/map/geo_map.dart b/lib/widgets/common/map/geo_map.dart
index 2f4cea000..d59476297 100644
--- a/lib/widgets/common/map/geo_map.dart
+++ b/lib/widgets/common/map/geo_map.dart
@@ -439,7 +439,7 @@ class _GeoMapState extends State {
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');
}
diff --git a/lib/widgets/viewer/visual/vector.dart b/lib/widgets/viewer/visual/vector.dart
index eae983d37..b76cbb5ab 100644
--- a/lib/widgets/viewer/visual/vector.dart
+++ b/lib/widgets/viewer/visual/vector.dart
@@ -286,7 +286,7 @@ class _VectorImageViewState extends State {
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);
diff --git a/test/utils/math_utils_test.dart b/test/utils/math_utils_test.dart
index 0d067bcd6..7d5989d42 100644
--- a/test/utils/math_utils_test.dart
+++ b/test/utils/math_utils_test.dart
@@ -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', () {