По моему третий способ было бы логично дополнить некими ключевыми кадрами (по аналогии с видео), по сути bitmap, которые бы сохранялись после тяжелых фильтров и хранились бы не в оперативе, а на винте (во временной папке). Т.е. как Вы и описали, несколько последних действий в оперативке в виде bitmap. История назад в виде списка действий от ключевых кадров (тяжелых фильтров). Да и такие ключевые кадры было бы логично хранить не в сыром виде, а хоть в том же png. Это позволит не так значительно тормозить при откате на далекую историю и экономить место на винте за счет сжатия png.
А вообще, идею с ключевыми кадрами можно было бы вообще полностью применить вместо третьего варианта. Т.е. мелкие действия хранить именно как действия, а более тяжелые, как ключевые кадры в виде bitmap в оперативке. Если более тяжелое действие отодвигается в прошлое, то паковать его в png и сбрасывать на винт.
И… по поводу слоёв… у нас же undo обычно применяется к конкретному действию на конкретном слое, а не ко всем слоям одновременно. Конечно есть live-фильтры, но это отдельная история. Я к тому, что число слоёв не увеличивает кол-во bitmap пропорционально.