【问题标题】:PGraphics seems to get cleared or frozen whenever drawn to main context每当绘制到主要上下文时,PGraphics 似乎都会被清除或冻结
【发布时间】:2017-01-19 12:59:18
【问题描述】:

我的最终目标是创建一个“隧道效应”,因为我将矩形绘制到缓冲区,将缓冲区复制到另一个缓冲区,然后在随后的 draw() 中,将第二个缓冲区复制回第一个缓冲区,只是稍微小一点,然后在上面绘制并重复。

我完全被这里发生的事情难住了。

首先,考虑这段代码,它完全按照预期工作了 1 次(没有绘制循环):

PGraphics canvas;
PGraphics buffer;

void setup(){
  size(500, 500);
  canvas = createGraphics(width, height);
  buffer = createGraphics(canvas.width, canvas.height);

  canvas.beginDraw();
  canvas.background(255);
  canvas.noFill();
  canvas.stroke(0);
  canvas.rect(100 + random(-50, 50), 100 + random(-50, 50), 350 + random(-50, 50), 350 + random(-50, 50));
  canvas.endDraw();

  buffer.beginDraw();
  buffer.image(canvas, 0, 0);
  buffer.endDraw();

  canvas.beginDraw();
  canvas.image(buffer, 100, 100, width-200, height-200);
  canvas.endDraw();

  image(canvas, 0, 0);

  noLoop();
}

这是一个非常愚蠢的例子,但它证明了这个概念是正确的:我绘制到canvas,复制到buffer,将buffer 复制回canvas,缩小比例,然后输出到主上下文。

但是看看当我尝试在 draw() 循环中执行此操作时会发生什么:

PGraphics canvas;
PGraphics buffer;

void setup(){
  size(500, 500);
  canvas = createGraphics(width, height);
  buffer = createGraphics(canvas.width, canvas.height);

  canvas.beginDraw();
  canvas.background(255);
  canvas.noFill();
  canvas.stroke(0);
  canvas.rect(100 + random(-50, 50), 100 + random(-50, 50), 350 + random(-50, 50), 350 + random(-50, 50));
  canvas.endDraw();

  buffer.beginDraw();
  buffer.image(canvas, 0, 0);
  buffer.endDraw();
}

void draw(){
  canvas.beginDraw();
  canvas.image(buffer, 0, 0);
  canvas.rect(100 + random(-50, 50), 100 + random(-50, 50), 350 + random(-50, 50), 350 + random(-50, 50));
  canvas.endDraw();

  image(canvas, 0, 0);

  buffer.beginDraw();
  buffer.image(canvas, 0, 0);
  buffer.endDraw();
}

在这里,最终发生的事情是,在 setup() 中创建的原始矩形每帧都被复制到 canvas。所以效果是有一个不移动的矩形,然后每帧都会绘制和替换第二个矩形。

它变得更奇怪了。观察当我简单地将绘制到主上下文的image() 函数时会发生什么:

PGraphics canvas;
PGraphics buffer;

void setup(){
  size(500, 500);
  canvas = createGraphics(width, height);
  buffer = createGraphics(canvas.width, canvas.height);

  canvas.beginDraw();
  canvas.background(255);
  canvas.noFill();
  canvas.stroke(0);
  canvas.rect(100 + random(-50, 50), 100 + random(-50, 50), 350 + random(-50, 50), 350 + random(-50, 50));
  canvas.endDraw();

  buffer.beginDraw();
  buffer.image(canvas, 0, 0);
  buffer.endDraw();
}

void draw(){
  canvas.beginDraw();
  canvas.image(buffer, 0, 0);
  canvas.rect(100 + random(-50, 50), 100 + random(-50, 50), 350 + random(-50, 50), 350 + random(-50, 50));
  canvas.endDraw();

  buffer.beginDraw();
  buffer.image(canvas, 0, 0);
  buffer.endDraw();

  image(canvas, 0, 0);
}

这不应该改变任何事情,但结果是图像“冻结”了屏幕上的两个矩形。出于某种原因,它似乎只是一遍又一遍地绘制相同的东西,即使 canvas 每次都被重写。

将最后一行改为阅读

image(buffer, 0, 0);

相反,它会回到之前“冻结”缓冲区的行为,但每次都会在其顶部绘制一个新的矩形。

任何人都可以了解正在发生的事情吗?

【问题讨论】:

  • set(0, 0, buffer) 替换image() 可以解决问题,但不能回答我的问题。仍然对 image() 正在做什么感到好奇,但没有记录在案......

标签: processing pgraphics


【解决方案1】:

想想你在每个PGraphics 图像中到底有什么。

每个PGraphics 是一个 500x500 的图像,上面有一个白色背景和一个黑色矩形。

然后您拍摄一张图像并将其绘制在另一张图像上。它们仍然是带有黑色矩形的白色图像。需要注意的重要一点是,由于它们都有白色背景,因此您将无法“通过”新图像看到“旧”图像。所以你只是来回绘制同一个矩形。

您可以通过在第二个代码块中删除对canvas.background() 的调用来证明这一点。然后你会看到矩形堆叠在一起。它仍然不是隧道效应,因为您每次都在绘制相同的矩形,但这是一个单独的问题。

因此,要解决您的问题,您需要注意每张图片中的确切内容。特别注意背景是否透明。

我还要注意,您可能只需要一个缓冲区图像就可以实现此效果,您只需将其绘制得越来越小,甚至根本不需要缓冲区图像,只需对主画布执行相同的操作即可。

【讨论】:

  • 嗨,凯文,感谢您的回答,但我认为您的看法不同。从不透明的白色背景开始,并在其上绘制一个矩形。然后获取整个(不透明)图像并复制它。然后“清除”原始画布并将副本绘制回它,稍微小一点。现在你有一个白色背景和一个(较小的)黑色矩形。然后在上面画一个新的(更大的)矩形。现在你应该有两个矩形。然后将其复制到缓冲区,清除画布并重复。所以,是的,这个理论是有效的。 image() 做的事情比文档说的要多,我正试图找出是什么。
  • @TomAuger 我明白你在试图做什么,但这不是你在现实中所做的。尝试删除canvas.background(),就像我建议的那样,看看我的意思。你会发现你实际上并没有把矩形画得越来越小。您只是在随机坐标处绘制它,但不会更小。您的代码的行为完全符合我的预期,因此image() 绝对不是这里做错了什么。
  • 感谢您的额外 cmets 凯文。您是否尝试过运行我上面列出的 3 个示例?在第二个和第三个示例中,请注意我在绘制缓冲区之前如何将缓冲区复制回画布。所以是的,缓冲区用不透明的白色背景 + 1 个矩形“破坏”了画布,但随后我们在顶部绘制了第二个矩形。然后我们将其复制到缓冲区并重复。第二次迭代我们用不透明的 bg + 2 个正方形(来自缓冲区)破坏画布,并在顶部绘制第三个正方形,然后继续。有趣的是,如果你用 set() 替换 image() ,整个事情就像宣传的那样工作
  • @Tom 是的,我已经运行了您的所有示例。您是否尝试过我的建议,即删除对canvas.background() 的调用以查看您实际上并没有每次都将画布画得更小?你能指出哪一行代码使它变小了吗?
  • 抱歉,Kevin,我想我实际上并没有把代码画得更小。我会使用image(buffer, 20, 20, width-40, height-40) 或类似的东西。
【解决方案2】:

查看源代码,问题在于image()image() 设置 PGraphics 纹理,通过调用 imageImpl(),恰好在 PGraphics 中被覆盖。通过设置纹理,而不是直接设置像素,纹理参考被保留和缓存,这解释了(至少在一定程度上满足我的好奇心)使用PGraphics.image()(至少在主绘图上下文中)的原因似乎将缓冲区 PGraphics 对象“锁定”到对后续 draw() 操作无用的点。

有两种解决方案可以避免这种情况:

  1. 仍然使用两个屏幕外缓冲区(在我的示例中为canvasbuffer),继续使用canvas.image() 以便能够将缓冲区写入图像并可能对其进行缩放;但就将画布写入主绘图上下文而言,请改用set(x, y, canvas)PGraphics.set() 继承自 PImage.set() 并且没有被覆盖,并且直接逐像素设置像素,因此没有对原始对象的引用。它在 Java2D 上下文中也更快(尽管在 GL 上下文中可能更慢),因为它不绘制纹理。

  2. 另一个选项(至少在我的情况下)是完全绕过canvas 对象,而是使用g.copy() 直接处理主绘图上下文的像素,它返回一个新的PImage包含主绘图画布的完整副本的对象(g 实际上是 this.gPApplet.g,所有绘图函数都会影响的主 PGraphics 上下文)。因为这是像素的副本,而不是 PGraphics 对象,所以您可以不受惩罚地使用image(),利用该函数允许的缩放。

这里有一些例子:

PGraphics canvas;
PGraphics buffer;

void setup(){
  size(500, 500);
  canvas = createGraphics(width, height);
  buffer = createGraphics(canvas.width, canvas.height);

  canvas.beginDraw();
  canvas.background(255);
  canvas.noFill();
  canvas.stroke(0);
  canvas.rect(100 + random(-50, 50), 100 + random(-50, 50), 350 + random(-50, 50), 350 + random(-50, 50));
  canvas.endDraw();

  buffer.beginDraw();
  buffer.image(canvas, 0, 0);
  buffer.endDraw();
}

void draw(){
  canvas.beginDraw();
  canvas.background(255);
  canvas.image(buffer, 10, 20, width-20, width-20);
  canvas.rect(100 + random(-50, 50), 100 + random(-50, 50), 350 + random(-50, 50), 350 + random(-50, 50));
  canvas.endDraw();

  set(0, 0, canvas);

  buffer.beginDraw();
  buffer.image(canvas, 0, 0);
  buffer.endDraw();
}

以上是“双缓冲”版本。请注意使用set() 而不是image()。与原始示例的唯一区别是,我对image() 应用了一些缩放,以获得我一开始就在寻找的“扭曲”效果。

第二个(短得多)示例仅使用单个屏幕外缓冲区,并通过g.copy() 复制主绘图上下文的 PGraphics 对象:

PImage buffer;

void setup(){
  size(500, 500);

  background(255);
  noFill();
  stroke(0);

  buffer = g.copy();

}

void draw(){
  background(255);
  image(buffer, 10, 20, width-20, width-20);
  rect(100 + random(-50, 50), 100 + random(-50, 50), 350 + random(-50, 50), 350 + random(-50, 50));

  buffer = g.copy();
}

我非常喜欢后者,原因有两个:第一,它的代码明显更少,而且内存可能更少,因此性能应该更高(虽然我还没有测试过这个假设),第二,它允许你继续使用主绘图上下文,更简洁、更惯用,让您可以轻松地将技术调整到任何现有草图,而无需重写每个图形调用,在其前面加上 beginDraw() 和屏幕外缓冲区的名称。

【讨论】:

    猜你喜欢
    • 2020-12-03
    • 1970-01-01
    • 2018-02-22
    • 2019-03-12
    • 1970-01-01
    • 1970-01-01
    • 2019-06-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多