yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
color_id_permutation.cc
Go to the documentation of this file.
2
3#include <algorithm>
4#include <map>
5#include <set>
6#include <string>
7#include <utility>
8
9#include "absl/strings/str_format.h"
10
11namespace yaze::editor {
12namespace {
13
14constexpr int kPageWidth = 128;
15constexpr int kPageHeight = 32;
16constexpr size_t kPagePixels = kPageWidth * kPageHeight;
17constexpr int kTilesPerRow = kPageWidth / 8;
18constexpr int kTilesPerPage = static_cast<int>(kPagePixels / 64);
19
20bool SupportedDepth(int bpp) {
21 return bpp == 2 || bpp == 3 || bpp == 4;
22}
23
24size_t TileOffset(uint16_t tile) {
25 return (tile / kTilesPerRow) * 8 * kPageWidth + (tile % kTilesPerRow) * 8;
26}
27
28} // namespace
29
31 ColorIdPermutation permutation;
32 permutation.bpp = bpp;
33 return permutation;
34}
35
36absl::Status ValidateColorIdPermutation(const ColorIdPermutation& permutation) {
37 if (!SupportedDepth(permutation.bpp)) {
38 return absl::InvalidArgumentError(
39 "Color-ID permutations support 2, 3 or 4 bpp");
40 }
41 if (permutation.ids[0] != 0) {
42 return absl::InvalidArgumentError(
43 "Color-ID swaps keep ID 0 transparent; replacing transparency is a "
44 "separate action");
45 }
46 const int colors = permutation.colors();
47 std::array<bool, 16> seen{};
48 for (int i = 0; i < 16; ++i) {
49 const uint8_t value = permutation.ids[i];
50 if (i >= colors) {
51 if (value != i) {
52 return absl::InvalidArgumentError(
53 absl::StrFormat("Color-ID permutation names ID %d outside %dbpp", i,
54 permutation.bpp));
55 }
56 continue;
57 }
58 if (value >= colors) {
59 return absl::InvalidArgumentError(
60 absl::StrFormat("Color ID %d does not fit %dbpp (IDs 0-%d)", value,
61 permutation.bpp, colors - 1));
62 }
63 if (seen[value]) {
64 return absl::InvalidArgumentError(
65 "Color-ID permutation must map each ID exactly once");
66 }
67 seen[value] = true;
68 }
69 return absl::OkStatus();
70}
71
73 const ColorIdPermutation& permutation, int backing_bpp) {
74 if (auto status = ValidateColorIdPermutation(permutation); !status.ok()) {
75 return status;
76 }
77 if (permutation.bpp != backing_bpp) {
78 return absl::InvalidArgumentError(absl::StrFormat(
79 "A %dbpp color-ID permutation cannot edit a %dbpp backing",
80 permutation.bpp, backing_bpp));
81 }
82 return absl::OkStatus();
83}
84
85absl::StatusOr<ColorIdPermutation> MakeColorIdSwap(int bpp, int from, int to) {
86 if (!SupportedDepth(bpp)) {
87 return absl::InvalidArgumentError(
88 "Color-ID permutations support 2, 3 or 4 bpp");
89 }
90 const int colors = 1 << bpp;
91 if (from == 0 || to == 0) {
92 return absl::InvalidArgumentError(
93 "Color-ID swaps keep ID 0 transparent; replacing transparency is a "
94 "separate action");
95 }
96 if (from < 0 || to < 0 || from >= colors || to >= colors) {
97 return absl::InvalidArgumentError(
98 absl::StrFormat("Swap IDs must be 1-%d for %dbpp", colors - 1, bpp));
99 }
100 auto permutation = IdentityColorIdPermutation(bpp);
101 std::swap(permutation.ids[from], permutation.ids[to]);
102 return permutation;
103}
104
105uint64_t PhysicalPixelMask(uint64_t displayed_mask, bool hflip, bool vflip) {
106 uint64_t mask = 0;
107 for (int y = 0; y < 8; ++y) {
108 for (int x = 0; x < 8; ++x) {
109 if ((displayed_mask & (uint64_t{1} << (y * 8 + x))) == 0) {
110 continue;
111 }
112 const int raw_x = hflip ? 7 - x : x;
113 const int raw_y = vflip ? 7 - y : y;
114 mask |= uint64_t{1} << (raw_y * 8 + raw_x);
115 }
116 }
117 return mask;
118}
119
120absl::StatusOr<ColorIdEditPlan> PlanColorIdEdit(
121 const std::vector<ColorIdBacking>& backings,
122 const std::vector<ColorIdTarget>& targets,
123 const ColorIdPermutation& permutation) {
124 if (auto status = ValidateColorIdPermutation(permutation); !status.ok()) {
125 return status;
126 }
127 std::map<uint32_t, const ColorIdBacking*> by_range;
128 for (const auto& backing : backings) {
129 if (!by_range.emplace(backing.range, &backing).second) {
130 return absl::InvalidArgumentError("Duplicate color-ID backing range");
131 }
132 }
133 if (targets.empty()) {
134 return absl::InvalidArgumentError("Select pixels to remap");
135 }
136 std::map<std::pair<uint32_t, uint16_t>, uint64_t> masks;
137 for (const auto& target : targets) {
138 const auto backing = by_range.find(target.range);
139 if (backing == by_range.end()) {
140 return absl::NotFoundError("Selected color-ID range is unavailable");
141 }
142 if (target.tile_index >= kTilesPerPage) {
143 return absl::OutOfRangeError("Selected tile is outside its page");
144 }
145 const uint64_t mask =
146 PhysicalPixelMask(target.pixel_mask, target.hflip, target.vflip);
147 if (mask != 0) {
148 masks[{target.range, target.tile_index}] |= mask;
149 }
150 }
151 if (masks.empty()) {
152 return absl::InvalidArgumentError("Select at least one pixel");
153 }
154
155 std::set<uint32_t> used_ranges;
156 for (const auto& [key, mask] : masks) {
157 used_ranges.insert(key.first);
158 }
159 for (const auto range : used_ranges) {
160 const auto& backing = *by_range.at(range);
161 if (auto status =
162 ValidateColorIdPermutationForBacking(permutation, backing.bpp);
163 !status.ok()) {
164 return status;
165 }
166 const int colors = 1 << backing.bpp;
167 if (backing.pixels.size() != kPagePixels ||
168 std::any_of(backing.pixels.begin(), backing.pixels.end(),
169 [colors](uint8_t pixel) { return pixel >= colors; })) {
170 return absl::FailedPreconditionError(
171 absl::StrFormat("Backing range %u is not a 128x32 page of %dbpp IDs",
172 range, backing.bpp));
173 }
174 }
175
176 ColorIdEditPlan plan;
177 plan.permutation = permutation;
178 std::map<uint32_t, ColorIdRangeEdit> edits;
179 for (const auto& [key, mask] : masks) {
180 const auto [range, tile] = key;
181 const auto& pixels = by_range.at(range)->pixels;
182 plan.targets.push_back({range, tile, mask});
183 const size_t start = TileOffset(tile);
184 for (int y = 0; y < 8; ++y) {
185 for (int x = 0; x < 8; ++x) {
186 if ((mask & (uint64_t{1} << (y * 8 + x))) == 0) {
187 continue;
188 }
189 ++plan.selected_pixels;
190 const size_t offset = start + y * kPageWidth + x;
191 const uint8_t before = pixels[offset];
192 const uint8_t after = permutation.ids[before];
193 if (before == after) {
194 continue;
195 }
196 auto& edit = edits[range];
197 edit.range = range;
198 edit.offsets.push_back(static_cast<uint16_t>(offset));
199 edit.before.push_back(before);
200 edit.after.push_back(after);
201 }
202 }
203 }
204 plan.physical_tiles = masks.size();
205 for (auto& [range, edit] : edits) {
206 plan.ranges.push_back(std::move(edit));
207 }
208 return plan;
209}
210
211absl::Status ApplyColorIdRangeEdit(const ColorIdRangeEdit& edit, int bpp,
212 bool forward, std::vector<uint8_t>& pixels) {
213 if (!SupportedDepth(bpp) || pixels.size() != kPagePixels ||
214 edit.offsets.empty() || edit.offsets.size() != edit.before.size() ||
215 edit.offsets.size() != edit.after.size()) {
216 return absl::InvalidArgumentError("Invalid color-ID range edit");
217 }
218 const int colors = 1 << bpp;
219 auto next = pixels;
220 std::set<uint16_t> seen;
221 for (size_t i = 0; i < edit.offsets.size(); ++i) {
222 const auto offset = edit.offsets[i];
223 if (offset >= next.size() || !seen.insert(offset).second ||
224 edit.before[i] >= colors || edit.after[i] >= colors ||
225 edit.before[i] == edit.after[i] || edit.before[i] == 0 ||
226 edit.after[i] == 0) {
227 return absl::InvalidArgumentError("Invalid color-ID pixel edit");
228 }
229 const uint8_t expected = forward ? edit.before[i] : edit.after[i];
230 if (next[offset] != expected) {
231 return absl::FailedPreconditionError(
232 "Pixels changed since this color-ID edit");
233 }
234 next[offset] = forward ? edit.after[i] : edit.before[i];
235 }
236 pixels = std::move(next);
237 return absl::OkStatus();
238}
239
240} // namespace yaze::editor
Editors are the view controllers for the application.
absl::Status ValidateColorIdPermutation(const ColorIdPermutation &permutation)
absl::StatusOr< ColorIdPermutation > MakeColorIdSwap(int bpp, int from, int to)
absl::StatusOr< ColorIdEditPlan > PlanColorIdEdit(const std::vector< ColorIdBacking > &backings, const std::vector< ColorIdTarget > &targets, const ColorIdPermutation &permutation)
ColorIdPermutation IdentityColorIdPermutation(int bpp)
absl::Status ValidateColorIdPermutationForBacking(const ColorIdPermutation &permutation, int backing_bpp)
uint64_t PhysicalPixelMask(uint64_t displayed_mask, bool hflip, bool vflip)
absl::Status ApplyColorIdRangeEdit(const ColorIdRangeEdit &edit, int bpp, bool forward, std::vector< uint8_t > &pixels)
std::vector< ColorIdRangeEdit > ranges
std::vector< ColorIdTarget > targets