#853 apply scale when decoding SVG
This commit is contained in:
parent
eb004d8eca
commit
8981900d6b
7 changed files with 24 additions and 13 deletions
|
@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## <a id="unreleased"></a>[Unreleased]
|
## <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
|
## <a id="v1.10.1"></a>[v1.10.1] - 2023-12-21
|
||||||
|
|
||||||
- Cataloguing: detect/filter `Ultra HDR`
|
- Cataloguing: detect/filter `Ultra HDR`
|
||||||
|
|
|
@ -89,6 +89,7 @@ class MediaFetchBytesHandler(private val context: Context) : MethodCallHandler {
|
||||||
MimeTypes.SVG -> SvgRegionFetcher(context).fetch(
|
MimeTypes.SVG -> SvgRegionFetcher(context).fetch(
|
||||||
uri = uri,
|
uri = uri,
|
||||||
sizeBytes = sizeBytes,
|
sizeBytes = sizeBytes,
|
||||||
|
scale = sampleSize,
|
||||||
regionRect = regionRect,
|
regionRect = regionRect,
|
||||||
imageWidth = imageWidth,
|
imageWidth = imageWidth,
|
||||||
imageHeight = imageHeight,
|
imageHeight = imageHeight,
|
||||||
|
|
|
@ -25,6 +25,7 @@ class SvgRegionFetcher internal constructor(
|
||||||
suspend fun fetch(
|
suspend fun fetch(
|
||||||
uri: Uri,
|
uri: Uri,
|
||||||
sizeBytes: Long?,
|
sizeBytes: Long?,
|
||||||
|
scale: Int,
|
||||||
regionRect: Rect,
|
regionRect: Rect,
|
||||||
imageWidth: Int,
|
imageWidth: Int,
|
||||||
imageHeight: Int,
|
imageHeight: Int,
|
||||||
|
@ -36,14 +37,6 @@ class SvgRegionFetcher internal constructor(
|
||||||
return
|
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
|
var currentSvgRef = lastSvgRef
|
||||||
if (currentSvgRef != null && currentSvgRef.uri != uri) {
|
if (currentSvgRef != null && currentSvgRef.uri != uri) {
|
||||||
currentSvgRef = null
|
currentSvgRef = null
|
||||||
|
@ -75,8 +68,8 @@ class SvgRegionFetcher internal constructor(
|
||||||
val viewBox = svg.documentViewBox
|
val viewBox = svg.documentViewBox
|
||||||
val svgWidth = viewBox.width()
|
val svgWidth = viewBox.width()
|
||||||
val svgHeight = viewBox.height()
|
val svgHeight = viewBox.height()
|
||||||
val xf = imageWidth / ceil(svgWidth)
|
val xf = imageWidth / scale / ceil(svgWidth)
|
||||||
val yf = imageHeight / ceil(svgHeight)
|
val yf = imageHeight / scale / ceil(svgHeight)
|
||||||
// some SVG paths do not respect the rendering viewbox and do not reach its edges
|
// 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,
|
// so we render to a slightly larger bitmap, using a slightly larger viewbox,
|
||||||
// and crop that bitmap to the target region size
|
// and crop that bitmap to the target region size
|
||||||
|
@ -95,6 +88,15 @@ class SvgRegionFetcher internal constructor(
|
||||||
|
|
||||||
val targetBitmapWidth = regionRect.width()
|
val targetBitmapWidth = regionRect.width()
|
||||||
val targetBitmapHeight = regionRect.height()
|
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(
|
var bitmap = Bitmap.createBitmap(
|
||||||
targetBitmapWidth + bleedX * 2,
|
targetBitmapWidth + bleedX * 2,
|
||||||
targetBitmapHeight + bleedY * 2,
|
targetBitmapHeight + bleedY * 2,
|
||||||
|
|
|
@ -3,7 +3,9 @@ import 'dart:ui';
|
||||||
|
|
||||||
int highestPowerOf2(num x) => x < 1 ? 0 : pow(2, (log(x) / ln2).floor()).toInt();
|
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);
|
double roundToPrecision(final double value, {required final int decimals}) => (value * pow(10, decimals)).round() / pow(10, decimals);
|
||||||
|
|
||||||
|
|
|
@ -439,7 +439,7 @@ class _GeoMapState extends State<GeoMap> {
|
||||||
if (points.length != geoEntry.pointsSize) {
|
if (points.length != geoEntry.pointsSize) {
|
||||||
// `Fluster.points()` method does not always return all the points contained in a cluster
|
// `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`)
|
// 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);
|
points = _slowMarkerCluster!.points(clusterId);
|
||||||
assert(points.length == geoEntry.pointsSize, 'got ${points.length}/${geoEntry.pointsSize} for geoEntry=$geoEntry');
|
assert(points.length == geoEntry.pointsSize, 'got ${points.length}/${geoEntry.pointsSize} for geoEntry=$geoEntry');
|
||||||
}
|
}
|
||||||
|
|
|
@ -286,7 +286,7 @@ class _VectorImageViewState extends State<VectorImageView> {
|
||||||
required double scale,
|
required double scale,
|
||||||
required double devicePixelRatio,
|
required double devicePixelRatio,
|
||||||
}) =>
|
}) =>
|
||||||
smallestPowerOf2(scale * devicePixelRatio).toDouble();
|
smallestPowerOf2(scale * devicePixelRatio, allowNegativePower: true).toDouble();
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef _BackgroundFrameBuilder = Widget Function(Widget child, int? frame, Rect tileRect);
|
typedef _BackgroundFrameBuilder = Widget Function(Widget child, int? frame, Rect tileRect);
|
||||||
|
|
|
@ -20,6 +20,8 @@ void main() {
|
||||||
expect(smallestPowerOf2(-42), 1);
|
expect(smallestPowerOf2(-42), 1);
|
||||||
expect(smallestPowerOf2(.5), 1);
|
expect(smallestPowerOf2(.5), 1);
|
||||||
expect(smallestPowerOf2(1.5), 2);
|
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', () {
|
test('rounding to a given precision after the decimal', () {
|
||||||
|
|
Loading…
Reference in a new issue