【问题标题】:Fast undo facility for bitmap editor application位图编辑器应用程序的快速撤消工具
【发布时间】:2011-03-15 08:38:55
【问题描述】:

我正在尝试为 iphone 制作一个类似于画笔或图层或 Photoshop 精简版的位图编辑器应用程序。如果可能的话,我希望能够支持大约 4 层的 1000x1000 分辨率图像。

我正在尝试在编写太多代码之前设计我的撤消/重做系统,但由于移动设备的限制以及位图编辑器操作通常是破坏性的。我所知道的最常见的撤消/重做设计是:

  1. 使用命令模式。您存储初始状态和用于将其转换为当前状态的命令。要撤消,您需要重新加载初始状态并重播除最后一个命令之外的所有命令。

  2. 使用备忘录模式。每次操作后,您都会存储足够的信息以恢复该操作。

我预见的问题是:

  1. 命令模式:在 500 次编辑操作后我想撤消最后一次怎么办?加载初始状态并应用 499 可能会很耗时,特别是如果其中一些是昂贵的东西,例如应用模糊滤镜。我不喜欢在不同场景下撤消花费不同时间的方式。

  2. 备忘录模式:保存已修改的位图部分会占用大量内存。将这些位图缓存到磁盘也可能很慢(因此,如果用户进行大量快速编辑,我可能无法缓存位图)并且我不确定电池使用量的影响。

我能想到的唯一解决方案是:

  1. 使用命令模式和备忘录模式,每 10 条左右的命令或在昂贵的操作之后,还会保存整个状态(它为您提供免费的自动保存功能)。要撤消,我重新加载最近的快照,然后重播命令。不过,我宁愿避免这种复杂性。

  2. 使用备忘录模式并强制用户等待位图被缓存。如果我将这个时间构建到例如,这还不错。等待过滤器应用,但在画笔描边之间效果不佳。

是建议吗?我很想知道一些现有的应用程序是如何做到这一点的。

我能想到上述各种奇怪的混合体,但它们都有明显的问题。我能想到的就是解决其中的一些问题或妥协应用程序以使问题更简单(例如减小最大位图大小的大小)。我注意到一些应用程序的最大位图大小和图层限制非常低。

【问题讨论】:

  • +1:很高兴看到如此深入调查(和书面)的问题!欢迎来到 StackOverflow,Radent :-)
  • 谢谢,您可能会说这个问题现在让我发疯了。 :-)
  • 你测量过缓存位图需要多长时间吗?
  • 快速实验大约需要 0.5 - 1 秒。只要在下一个操作完成时缓存完成,一切都OK。但是,我的主要问题是如果例如会发生什么。用户非常快速地点击屏幕 5 次,每次点击都是您想要撤消的一项操作。我要么需要强制用户等待,要么将 5 个操作合并为 1 个操作(即撤消将撤消 1 秒的操作),要么设置一些系统以高效内存的方式在后台缓存每个操作。不过,我为后者提供的所有解决方案都非常复杂。
  • @Mau:我很确定他们使用的是混合方法。他们有一个撤消命令树,但我认为它们也在昂贵的操作之后存储状态。但是在桌面上要容易得多,因为我可以轻松地将 X 许多撤消层保留在内存中,并根据需要将它们保存到磁盘。在移动设备上,我很幸运能够拥有 2 个全尺寸的撤消层。嗯,看起来我将不得不针对常见情况进行优化,并接受不太常见的情况下的不良性能。

标签: iphone objective-c algorithm design-patterns optimization


【解决方案1】:

我想到了第三个选项:每个操作都在自己的层上执行,撤消会删除该层。它需要快速渲染机制和“动作层”的智能表示,您不存储“透明”(未触及)像素。

如果您假设 u 级别的撤消,您可以将早于 u 步骤的操作层合并到后台。

您也可以采用混合方法。如果动作是“小”,则将其表示为一个层。如果它很大,作为记录的动作,则需要重播。正如你所说,你需要一个启发式来决定这两种情况。您可以在设置后首次运行应用程序时测试渲染/保存性能,并确定启发式的一些参数值。

【讨论】:

  • 谢谢。我考虑过这个,但内存是个问题。实际上渲染图层很好,但更糟糕的情况是有人在一次操作中在整个画布上涂鸦,然后您需要为该操作存储 1000x1000 位图。由于每个位图的大小约为 2Mb,因此您会很快耗尽内存。
  • @Radent:我明白了。我猜你的撤销链的任何“位图”表示都会遇到同样的问题。您可以采用混合方法。如果动作是“小”,则将其表示为一个层。如果它很大,作为记录的动作,需要重播。
  • 是的,我也有类似的想法。您真的需要一种启发式方法,该方法可以衡量重播操作的速度(例如,画笔笔触很快,但模糊整个图像的速度很慢),然后使用它来确定何时缓存图层。我仍然有一个问题,当我需要缓存另一层而最后一个层还没有完成保存时该怎么办。
【解决方案2】:

对我来说,momento 模式看起来最好。这意味着我可能会尝试想办法以最佳方式存储这些信息。您提到它可能是一个 1000x1000 位图可能用于一个动作 - 例如,您能否将位图表示为一个 1 位位图,并在其他地方存储一个单独的颜色字段?现在您可以存储 125KB 而不是 2MB。

我可能会研究的另一件事是在用户执行操作时构建信息 - 即,当他们在涂鸦时,您可以在发生时将这些位写入您的数据结构。然后当它们完成后,您可以将操作提交到您的撤消历史记录。

您应该能够对这些撤消数据结构应用一些算法逻辑,以减少它们对内存的影响,同时在等待用户时隐藏 CPU 使用率。

【讨论】:

    【解决方案3】:

    我会采用混合方法。最重要的是,将数据组织成部分(例如图块),这样您只需存储修改部分的状态(使用延迟复制机制)。

    【讨论】:

      【解决方案4】:

      使用内存映射检查每个位图 iOS 设备上的闪存足够快,可以支持大型位图上的撤消/重做操作。使用 mmap 系统调用,我轻松地映射了 1024x768 ABGR 位图,从而使我的程序免于使用宝贵的 DRAM。我不知道您想如何抽象撤消/重做模式,但避免任何高开销复制操作的方法是让撤消和重做位图的指针交换每个撤消/重做。您已经指定了多个撤消级别,但我敢打赌您可以通过一些指针交换来摆脱困境(我现在正遭受一些失眠的困扰,并且试图证明指针交换被证明太多了——这是一些非常垃圾的伪代码)。

      另外,我不建议将您的 mmap 页面标记为 F_NOCACHE。最好让 iOS 缓存写入 DRAM 中的位图,因为:

      • 不刷机会更快
      • 闪存是为固定数量的写入而设计的——烧坏用户的闪存并不好(我认为在 iOS 设备中使用高质量闪存大约需要 500 万次写入)
      • 我相信 iOS 在内存管理方面足够激进,它会在内存危机期间取消映射缓存的写入并刷新它们(不过我从来没有足够在意检查)

      向 John Carmack 大声疾呼 iDevice 闪存非常快(不过,他使用 F_NOCACHE 来获得可预测的读取性能)。

      请注意,在调用 fd 上的 mmap 之前,我必须使文件成为实际位图的大小。不过,不要发疯内存映射 100 个位图(开个玩笑——做吧!)我的意思是,用户可以执行多少次撤消?他们很虚弱,只按几秒钟按钮就会感到疲倦。

      【讨论】:

      • 这是2010年7月问的??!! StackOverflow AGGGG!
      【解决方案5】:

      对我来说,您面临的似乎是资源限制,所以这与您的 UI/应用程序设计有关。获取更多内存、更快的“磁盘”访问等没有什么妙招。

      我会采用混合方法,在环形缓冲区中使用 memeton,并在下一个空闲的预分配槽中处理每个过滤器。这将为您提供有限的快速撤消/重做,而不会出现堆损坏。对于无限的撤消/重做步骤,您有您的命令日志。如果你真的需要 400 个撤消步骤,你也可以像每 10 个操作一样保存一个带有命令的快照,并将这些快照中的 10 个压缩成一个更大的快照。

      最重要的是让用户清楚地知道他们在设备的限制范围内操作,就像当您的快速撤消用完时发出的警告信号。

      另外,在后台准备快速撤消/重做缓冲区可能会有所帮助,因此当用户撤消 2 次时,您已经准备好第 3 次撤消。大多数情况下,用户会在执行下一次撤消之前查看图像几毫秒(大约 200-400 秒)。

      您还可以为使用最少内存的可逆操作实施不同的撤消/重做策略。后台的快速无损压缩算法(lzma 等)可以进一步减少内存消耗。

      但这似乎是一个资源限制(IO vs. Memory vs. CPU),您所有的选择都是忍受它,并尽量让用户体验达到最佳。这有时会成为一项艰巨的任务。

      【讨论】:

        猜你喜欢
        • 2011-04-23
        • 1970-01-01
        • 2010-12-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-01-12
        • 2020-01-14
        • 2010-09-13
        相关资源
        最近更新 更多