当内存有限时,位图编辑器如何实现快速的撤销/重做功能?

13

我正在为移动设备编写位图编辑器(即 Photoshop 的简化版本)。用户的文档由 ~4 个大小约为 1000x500 的位图组成。

我想要一个尽可能简单的、健壮且高效的撤销/重做系统。我希望每次撤销或重做编辑所需时间大约为 ~0.2 秒。我正在寻求对我的当前方法的反馈或一些新的想法。我认为我现在的方法过于复杂了,所以我很谨慎地进行下一步操作,只要知道这是我能做到的最好的就可以了。

我已经尝试了使用命令模式和备忘录模式的组合来实现我的撤销/重做系统。我得出了一些结论:

  1. 在许多情况下,我没有足够的内存,也无法快速将内存写入磁盘来支持“取消上一个命令”操作,例如如果用户非常快地进行了几个单独的绘画笔划,则我将无法存储代表用户绘制的位图而不使用户等待它们被保存。

  2. 如果要还原文档到其初始状态,并重放除最后一个命令之外的所有命令以实现撤销,则即使有少量命令,如重新播放 10 次绘画笔划或 5 次涂抹笔划,也会变得非常缓慢(大约需要 1 秒钟)。

  3. 我可以通过定期在后台将整个文档保存到磁盘中并在播放命令之前还原到此检查点来解决前一个问题。要向更远处撤销,我们会在当前检查点之前重新加载检查点并重放命令。

第二种方法和第三种方法相结合效果还不错,但随着添加的图层数越来越多,保存整个文档所需的时间也会越来越长,即使只有 4 个位图,等待时间也要 5-10 秒钟。因此,我需要修改第三步,以便仅保存自上次检查点以来发生了哪些更改。

由于许多命令仅对一层进行操作,所以只保存自上次检查点以来已被修改的层是有意义的。例如,如果我有 3 个初始层,并指示可能保存检查点的位置,则我的命令堆栈可能如下所示。

(Checkpoint1: Save layer 1, 2 and 3.)
Paint on layer 1
Paint on layer 1
(Checkpoint2: Save layer 1. Reuse saved layers 2 and 3 from Checkpoint1.)
Paint on layer 2
Paint on layer 2
(Checkpoint3: Save layer 2. Reuse saved layers 1 and 3 from Checkpoint2.)
Paint on layer 3
Paint on layer 3
Flip layer 3 horizontally.
(Checkpoint4: Save layer 3. Reuse saved layers 1 and 2 from Checkpoint3.)
Resize layer 1, 2 and 3.
(Checkpoint5: Save layer 1, 2, 3.)

在编辑过程中,我会跟踪哪些层自上一个检查点以来已被修改。当我恢复检查点时,我只恢复已更改的层,例如,在修改了第2和第3层后恢复Checkpoint4,我会从磁盘重新加载第2和第3层的备份。添加检查点时,我仅保存到目前为止已被修改的层。我可以使这一切大部分自动化,但必须在我的界面中有需要等待检查点保存的位置,因为我一次只能在内存中保留1个图层的临时副本。

你认为呢?这比我想象的要复杂得多,但我看不到其他办法。是否还有其他有用的模式可以让我的生活变得更轻松?

2个回答

2
一种方法是将某些“帧”保留为完整的帧,而将其他帧保留为从前一个帧创建帧所需的命令。您在#2中提到了这一点。保留一些帧可能会有所帮助。
一个技巧,可以帮助平衡性能和可用空间/时间来保存完整帧的方法是丢弃一些“旧”的帧的一部分,以便在任何给定时间,您可能具有来自1、2、4、8、16、32和64个操作之前的撤消状态。撤消一两个操作将只需要读取一个帧。撤消三个操作将需要读取一个检查点并重复一个操作。撤消五个操作将需要读取一个检查点并重复三个操作。撤消33个操作将需要读取一个检查点并重复31个操作。
为了提高应用程序的流畅性,在撤销操作期间重新计算检查点帧可能会有所帮助。例如,在撤销了十七个操作之后,可以在后台开始计算从起始点开始向后48、40和36步的状态,以便如果需要进一步回退,则已经完成了部分工作。请注意,可以放弃1、2、4、8或16个操作之前的帧,因为可以通过从当前状态向前重放命令来重新创建它们。

在具有非常量间隔(f0 = 1,f1 = 2,f2 = 4,f3 = 8等)的撤消帧之间的困难部分是维护这些间隔。例如,为了确保f2始终在新笔画之前四个笔画,您需要转到f3,并呈现中间的每个笔画。相同的逻辑适用于f3、f4等。是的,您应该从fmax(即空画布)开始,但这实际上意味着您需要每次重建整个撤消堆栈。 - jlukanta
@jlukanta:如果某些检查点距离当前帧的距离是从未来的某一帧开始计算的,那么它们并不需要与当前帧具有完全相同的距离。如果我们将所有帧从编辑开始的第一帧编号为1,那么我们应该尽量选择最后一个奇数帧、最后一个奇数倍数的2、最后一个奇数倍数的4等等。总体方案有点难以描述,但基本上会使用lgN的时间。 - supercat

2

对于使用图像的图层和撤销缓冲区,以下内容可能会很有用:

  • 将最新的图像保留为图像
  • 以异或方式存储先前版本与下一个版本,然后(假设未全部更改或以相同方式更改)使用简单的压缩算法(如行程长度编码)进行压缩

这样做有以下优点:

  • 可以轻松合并以前的版本(将它们异或在一起)。

这种方法可能不适用于以下情况:

  • 颜色调整(色调、亮度等)
  • 坐标变换(裁剪、变形等)

谢谢。这将使保存检查点稍微更快,更节省空间,但是恢复检查点会稍微慢一些,因为我需要加载和合并多个检查点以恢复先前的状态。不过,我很感激对我的整体撤销/重做方案的评论,以及如何使其更简单的建议。 - memcom

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