Canvas.clipPath中的SIGSEGV在第二个clipPath发生了。

7

我有一台运行Android 4.2.2的ASUS Nexus 7。我的应用在运行以下代码时会在sk_malloc_flags中生成SIGSEGV:

static Picture createDrawing() {

    Path firstPath = new Path();
    firstPath.moveTo(3058, 12365);
    firstPath.lineTo(8499, 3038);
    firstPath.lineTo(9494, 3619);
    firstPath.lineTo(4053, 12946);
    firstPath.close();

    Path fourthPath = new Path();
    fourthPath.moveTo(3065, 12332);
    fourthPath.lineTo(4053, 12926);
    fourthPath.lineTo(9615, 3669);
    fourthPath.lineTo(8628, 3075);
    fourthPath.close();

    Picture picture = new Picture();
    Canvas canvas = picture.beginRecording(12240, 15840);
    canvas.clipPath(firstPath);
    canvas.clipPath(fourthPath); << SIGSEGV occurs here
    picture.endRecording();
    return picture;
}

SIGSEGV的报告如下:
    I/DEBUG   (  124): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr deadbaad
    I/DEBUG   (  124):     r0 00000027  r1 deadbaad  r2 4017f258  r3 00000000
    I/DEBUG   (  124):     r4 00000000  r5 bed72434  r6 bed72508  r7 1be773bc
    I/DEBUG   (  124):     r8 1be730f9  r9 000042c3  sl 00000001  fp 67185010
    I/DEBUG   (  124):     ip 40443f3c  sp bed72430  lr 401522f9  pc 4014e992  cpsr 60000030
...
    I/DEBUG   (  124): backtrace:
    I/DEBUG   (  124):     #00  pc 0001a992  /system/lib/libc.so
    I/DEBUG   (  124):     #01  pc 00018070  /system/lib/libc.so (abort+4)
    I/DEBUG   (  124):     #02  pc 000be4b4  /system/lib/libskia.so (sk_malloc_flags(unsigned int, unsigned int)+28)
    I/DEBUG   (  124):     #03  pc 0008afc0  /system/lib/libskia.so (SkRegion::op(SkRegion const&, SkRegion const&, SkRegion::Op)+1716)
    I/DEBUG   (  124):     #04  pc 00089448  /system/lib/libskia.so (SkRasterClip::op(SkRasterClip const&, SkRegion::Op)+128)

显然,我已经简化了上面显示的代码。完整的应用程序使用基于某些输入数据的变换等来生成值。有没有建议可以在不实现自己的剪辑代码的情况下解决这个问题?


如果只保留 canvas.clipPath(firstPath);,并删除对 fourthPath 的调用,会发生什么? - David Jashi
尝试使用canvas.clipPath(fourthPath, , Region.Op.REPLACE);,看看是否是问题的原因,如果不是,则像David Jashi所说,尝试删除调用以查看是否存在问题。 - g00dy
我能想到的另一件事是,如果你将它们分别绘制:Picture picture = new Picture(); Canvas canvas = picture.beginRecording(12240, 15840); canvas.clipPath(firstPath); picture.endRecording(); canvas = picture.beginRecording(12240, 15840); canvas.clipPath(fourthPath, Region.Op.INTERSECT); /你也可以尝试不使用 Region.Op.INTERSECT/ picture.endRecording(); - g00dy
3个回答

5

这似乎是一个与clipPath处理相关的不幸边缘情况。

canvas.clipPath(fourthPath);

导致与前一firstPath合并,但由于它们是复杂的(非矩形)形状,系统尝试将它们绘制为扫描线,然后进行合并。为了进行此合并,需要分配一些内存,但是如您在 SkRegion.cpp中所见 ,它选择 启发式最坏情况

static int compute_worst_case_count(int a_count, int b_count) {
    int a_intervals = count_to_intervals(a_count);
    int b_intervals = count_to_intervals(b_count);
    // Our heuristic worst case is ai * (bi + 1) + bi * (ai + 1)
    int intervals = 2 * a_intervals * b_intervals + a_intervals + b_intervals;
    // convert back to number of RunType values
    return intervals_to_count(intervals);
}

对于你的路径,worst_case_count 变得接近 2GB,因此由于无法从 malloc 获取如此大的内存而导致中止。
我无法看到使用不同参数的任何出路。任何避免合并 clipPath 的东西都应该有所帮助,例如使用 Region.Op.REPLACE 调用 clipPath。 Region.Op.INTERSECT 也应该失败。 我建议集中精力避免在复杂路径之上调用 clipPath。 如果适合您的用例,则可以使用相同的Path对象来设置canvas.clipPath()。例如:
Picture picture = new Picture();
Canvas canvas = picture.beginRecording(12240, 15840);
Path path = new Path();
path.moveTo(3058, 12365);
path.lineTo(8499, 3038);
path.lineTo(9494, 3619);
path.lineTo(4053, 12946);
path.close();
canvas.clipPath(path);
// do stuff with canvas
path.moveTo(3065, 12332);
path.lineTo(4053, 12926);
path.lineTo(9615, 3669);
path.lineTo(8628, 3075);
path.close();
canvas.clipPath(path, Region.Op.REPLACE);
// do more stuff with canvas
picture.endRecording();
return picture;

由于 path 包含先前的绘图,因此您可以继续更新它。如果这不适用于您的情况,则需要将这些数字变小或将复杂区域分成较小的区域,以避免 最坏情况启发式算法 变得太大。


1

好的,既然这对我来说看起来非常合乎逻辑,那就让我把它放在一个答案中:

要查看问题是否来自连续调用 clipPath(Path),请先尝试删除第一个调用或将 canvas.clipPath(fourthPath); 放在 canvas.clipPath(fourthPath, Region.Op.REPLACE); 的位置,并查看是否是这个原因。

我能想到的另一件事情是,如果你单独绘制它们:

Picture picture = new Picture();
Canvas canvas = picture.beginRecording(12240, 15840);
canvas.clipPath(firstPath);
picture.endRecording();

canvas = picture.beginRecording(12240, 15840);
canvas.clipPath(fourthPath);
picture.endRecording();

0

看起来Canvas.clipPath()不支持硬件加速,至少文档是这么说的: http://developer.android.com/guide/topics/graphics/hardware-accel.html#unsupported

我能想到的唯一解决方法是关闭硬件加速。

你可以在以下三个级别上进行操作:

  • 应用程序级别
    <application android:hardwareAccelerated="true" ...>
    在清单文件中

  • 活动级别
    android:hardwareAccelerated="false"
    在清单文件中的活动中

  • 视图级别
    view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
    在运行时为单个视图设置

Docs:
http://developer.android.com/guide/topics/graphics/hardware-accel.html#controlling


打开或关闭硬件加速不会影响崩溃。画布与视图无关联。 - Winnie
“12240 x 15840” 这个尺寸对于一张图片来说不是有点太大了吗?你试过用更小的尺寸吗? - pawelzieba

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接