52 return absl::FailedPreconditionError(
53 "Open Graphics from a supported object in this session");
56 if (!ValidDimensions(source.pattern_width, source.pattern_height) ||
57 source.pattern_cells.empty()) {
58 return absl::FailedPreconditionError(
59 "The object's source pattern geometry is unavailable");
61 if (asset.
bpp != 3 || !ValidDimensions(asset.
width, asset.
height) ||
65 [](uint8_t pixel) { return pixel > 7; })) {
66 return absl::InvalidArgumentError(
67 "Object artwork requires a complete 3bpp image with indices 0..7");
69 if (crop.
x < 0 || crop.
y < 0 || !ValidDimensions(crop.
width, crop.
height) ||
70 crop.
x % kTileSize || crop.
y % kTileSize || crop.
width > asset.
width ||
73 return absl::InvalidArgumentError(
74 "Select a tile-aligned crop inside the source artwork");
76 if (crop.
width > source.pattern_width ||
77 crop.
height > source.pattern_height) {
78 return absl::InvalidArgumentError(
79 "Selected artwork exceeds the object's source pattern");
83 for (
const auto& cell : source.pattern_cells) {
84 if (cell.x < 0 || cell.y < 0 || cell.x >= source.pattern_width / 8 ||
85 cell.y >= source.pattern_height / 8 ||
86 cell.target >= source.targets.size() ||
87 !cells.emplace(std::make_pair(cell.x, cell.y), &cell).second) {
88 return absl::FailedPreconditionError(
89 "The object's source pattern has invalid or ambiguous cells");
91 const auto& target = source.targets[cell.target];
92 if (target.tile_index >= kTilesPerSheet ||
93 !store->HasSheet(target.sheet_id)) {
94 return absl::FailedPreconditionError(
95 "An object source tile is unavailable in this session");
99 for (
int y = 0; y < crop.
height; ++y) {
100 for (
int x = 0; x < crop.
width; ++x) {
102 !cells.contains({x / 8, y / 8})) {
103 return absl::FailedPreconditionError(
104 "Selected artwork needs a tile in a hole of the object pattern");
111 std::map<TileKey, TilePixels> demands;
112 std::set<TileKey> selected;
113 for (
const auto& [position, cell] : cells) {
114 const auto& target = source.targets[cell->target];
115 const TileKey key{target.sheet_id, target.tile_index};
116 const bool inside = cell->x < crop.
width / 8 && cell->y < crop.
height / 8;
118 const auto before = store->Sheet(target.sheet_id);
119 for (
int y = 0; y < kTileSize; ++y) {
120 for (
int x = 0; x < kTileSize; ++x) {
122 const int raw_x = cell->hflip ? 7 - x : x;
123 const int raw_y = cell->vflip ? 7 - y : y;
124 desired[raw_y * 8 + raw_x] =
126 crop.
x + cell->x * 8 + x];
128 desired[y * 8 + x] = before[SheetOffset(target.tile_index, x, y)];
132 const auto [prior, inserted] = demands.emplace(key, desired);
133 if (!inserted && prior->second != desired) {
134 return absl::FailedPreconditionError(
135 "Selected artwork conflicts with another use of a shared source "
136 "tile in this object");
139 selected.insert(key);
142 if (selected.empty()) {
143 return absl::FailedPreconditionError(
144 "Selected artwork does not address an object source tile");
147 std::map<uint16_t, std::vector<uint8_t>> after_sheets;
148 std::vector<PixelIndexTarget> targets;
149 for (
const auto& [sheet, tile] : selected) {
150 if (!after_sheets.contains(sheet)) {
151 const auto before = store->Sheet(sheet);
152 after_sheets.emplace(sheet,
153 std::vector<uint8_t>(before.begin(), before.end()));
155 const auto& desired = demands.at({sheet, tile});
156 auto& after = after_sheets.at(sheet);
157 for (
int y = 0; y < kTileSize; ++y) {
158 for (
int x = 0; x < kTileSize; ++x) {
159 after[SheetOffset(tile, x, y)] = desired[y * 8 + x];
162 targets.push_back({sheet, tile});
165 "Apply catalog art to object source");