当内存有限时,位图编辑器可以快速撤消/重做吗?

发布于 2024-09-26 20:33:13 字数 1551 浏览 7 评论 0原文

我正在尝试为移动设备(即 Photoshop 的有限版本)编写一个位图编辑器。用户的文档由大约 4 个位图组成,每个位图大小约为 1000x500。

我想要一个尽可能简单、强大且高效的撤消/重做系统。我的目标是大约 0.2 秒来撤消或重做编辑。我正在寻找有关我当前预期方法的一些反馈或一些我可以使用的新想法。我认为我所拥有的太复杂了,所以我对继续进行持谨慎态度,所以只要知道这是我能做的最好的事情就很好了。

我已经尝试将命令模式和备忘录模式组合用于我的撤消/重做系统。到目前为止我得出的一些结论是:

  1. 我没有足够的内存,并且无法足够快地将内存写入磁盘以使用备忘录来支持在许多情况下对前一个命令的“取消执行”操作,例如如果用户很快地完成了几个单独的绘画描边,我将无法存储代表用户绘制内容的位图,而不让用户等待它们被保存。

  2. 如果我将文档恢复到其初始状态并重播除最后一个实施撤消之外的所有命令,那么即使是适度数量的命令(例如重播 10 个绘画笔划或 5 个涂抹笔划)也需要约 1 秒,这也太慢了这太慢了。

  3. 我可以通过定期将整个文档在后台保存到磁盘并在播放命令之前恢复到此检查点来解决上一点。要撤消比上一个检查点更早的内容,我们重新加载之前的检查点并重播命令。

方法 2 和 3 工作正常,只是随着添加更多层,保存整个文档变得越来越慢,并且使用 4 个位图已经很慢(约 5 - 10 秒等待)。因此,我需要修改 3,以便只保存自上次以来更改的内容。

由于许多命令仅在一层上运行,因此仅保存自上一个检查点以来已修改的层是有意义的。例如,如果我有 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 个临时副本。

你怎么认为?它比我想要的要复杂得多,但我看不到任何其他方法。还有其他有用的模式可以让我的生活更轻松吗?

I'm trying to write a bitmap editor for a mobile device (i.e. a limited version of Photoshop). The user's document consists of ~4 bitmaps around 1000x500 in size each.

I want a robust and efficient undo/redo system that's as simple as possible. I'm aiming for about ~0.2s to undo or redo an edit. I'm looking for some feedback on my current intended approach or for some new ideas I can use. I think what I have is too complex so I'm cautious about proceeding so just knowing it's about the best I could do would be good.

I've experimented with combinations of using the Command pattern and the Memento pattern for my undo/redo system. Some conclusions I've come to so far are:

  1. I don't have enough memory and I cannot write memory to disk fast enough to use a memento to support an "unexecute" operation on the previous command in many situations e.g. if the user does several individual paint strokes very quickly, I won't be able to store bitmaps that represent what the user painted over without making the user wait for them to be saved.

  2. If I restore the document to its initial state and replay all the commands except for the last one to implement undo, this is far too slow after even a modest number of commands e.g. replaying 10 paint strokes or 5 smudge strokes takes ~1s which is too sluggish.

  3. I can get around the previous point by saving the whole document in the background periodically to disk and restoring to this checkpoint before playing back commands. To undo further back than the last checkpoint, we reload the checkpoint before this and replay the commands.

Approach 2 with 3 works OK except saving the entire document gets slower and slower as more layers are added and it's already slow with 4 bitmaps (~5 - 10 seconds wait). I therefore need to modify 3 so that I only save what has changed since last time.

Since many commands operate on only one layer, it makes sense to only save the layers that have been modified since the last checkpoint. For example, my command stack might look like this if I have 3 initial layers where I've indicated where checkpoints might be saved.

(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.)

During editing, I keep track of which layers have been modified since the previous checkpoint. When I restore a checkpoint I only restore the layers that have changed e.g. to restore Checkpoint4 after modifying layer 2 and 3, I reload the backups of layer 2 and 3 from disk. When adding a checkpoint, I save only the layer that has been modified so far. I can make all this mostly automatic except there needs to be places in my interface where the user is forced to wait for checkpoints to be saved because I can only keep about 1 temporary copy of a layer in memory at a time.

What do you think? It's much more complex than I'd like it to be but I can't see any other way. Are there any other useful patterns I can use to make my life easier?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

黄昏下泛黄的笔记 2024-10-03 20:33:13

对于使用图像的图层和撤消缓冲区来说,以下内容可能很方便:

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

这具有以下优点:

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

这可能不适用于:

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

This following might be handy for layers and undo buffers using images:

  • Keep the latest image as an image
  • Previous versions are stored as an xor with the next version and then (assuming not everything changed or changed in the same manner) compressed using a simple compression algorithm (like run length encoding)

This has the following advantages

  • previous versions could be easily merged (xor them together).

This might not work well with:

  • color adjustments (hue, brightness etc)
  • coordinate transformations (crop, morphing, etc)
世界等同你 2024-10-03 20:33:13

一种方法是将某些“帧”保留为完整帧,而将其他“帧”保留为从前一帧创建帧所需的命令。你在#2 中提到了这一点。将一些帧保留在内存中可能会有所帮助。

可能有助于平衡性能与可用于保存完整帧的空间/时间的一个技巧是丢弃“旧”帧的一部分,以便在任何给定时间您可能会撤消状态,例如 1、2、4、 8、16、32 和 64 次操作前。撤消一两个操作只需要读取一帧即可。撤消三个操作将需要读取一个检查点并重复一个操作。撤消五个操作将需要读取检查点并重复三个操作。撤消 33 个操作将需要读取检查点并重复 31 个操作。

为了提高应用程序的流畅性,在某些情况下,在撤消操作期间在后台重新计算检查点帧可能会有所帮助。例如,在撤消 17 个操作后,人们可能会在后台开始计算从起点向后退 48、40 和 36 步的状态,因此,如果人们想进一步返回,则可能已经完成了其中的一些操作工作。请注意,我们可能会丢弃前 1、2、4、8 或 16 个操作的帧,因为我们可以通过从当前状态向前重播命令来重新创建它们。

One approach is to keep certain 'frames' as complete frames, and others as the the command necessary to create a frame from the previous one. You allude to this in your #2. It may be helpful to keep some frames in memory.

A trick that may help to balance performance with the space/time available to hold complete frames is to discard a some fraction of the 'old' frames, so that at any given time you might have undo states from e.g. 1, 2, 4, 8, 16, 32, and 64 operations ago. Undoing one or two operations will require simply reading a frame. Undoing three will require reading a checkpoint and repeating one operation. Undoing five will require reading a checkpoint and repeating three operations. Undoing thirty-three will require reading a checkpoint and repeating 31 operations.

To improve application smoothness, it may in some cases be helpful to recompute checkpoint frames in the background during an undo operation. For example, after having undone seventeen operations, one might in the background start working on computing the states for 48, 40, and 36 steps back from the starting point, so that if one wants to go back further one will have already done some of the work. Note that one may jettison the frames that were back 1, 2, 4, 8, or 16 operations, since one can recreate them by replaying commands forward from the current state.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文