238 if (
const auto status = ValidateRenderScale(req.
scale); !status.ok()) {
242 return absl::FailedPreconditionError(
"ROM not loaded");
245 return absl::FailedPreconditionError(
"GameData not available");
248 return absl::InvalidArgumentError(
249 absl::StrFormat(
"Invalid overworld screen 0x%02X", req.
screen_id));
252 return absl::InvalidArgumentError(
"Sprite phase must be 0, 1, or 2");
255#ifndef YAZE_CLI_HAS_PNG
256 return absl::UnimplementedError(
"PNG encoding unavailable (libpng missing)");
259 std::lock_guard<std::mutex> lock(
mu_);
265 if (requested ==
nullptr) {
266 return absl::InternalError(
"Overworld map missing after load");
268 const int parent = requested->parent();
270 if (parent_map ==
nullptr) {
271 return absl::InternalError(
"Overworld parent map missing after load");
276 std::string area_name =
"small";
277 switch (parent_map->area_size()) {
293 if (area_name ==
"small" && parent_map->is_large_map()) {
298 constexpr int kScreen = 512;
299 const int width = cols * kScreen;
300 const int height = rows * kScreen;
301 std::vector<uint8_t> native(
static_cast<size_t>(width) * height * 4, 0);
309 uint8_t& r, uint8_t& g, uint8_t& b) {
311 if (idx < palette.
size()) {
312 const auto rgb = palette[idx].rgb();
313 r =
static_cast<uint8_t
>(rgb.x);
314 g =
static_cast<uint8_t
>(rgb.y);
315 b =
static_cast<uint8_t
>(rgb.z);
319 for (
int dy = 0; dy < rows; ++dy) {
320 for (
int dx = 0; dx < cols; ++dx) {
321 const int screen = parent + dx + dy * 8;
324 std::vector<uint8_t> overlay_pixels;
325 bool overlay_is_background =
false;
329 if (layer.overlay_screen >= 0) {
330 overlay_pixels = std::move(layer.pixels);
331 overlay_is_background = layer.background;
337 if (map ==
nullptr) {
338 return absl::InternalError(
"Overworld child map missing");
340 const auto& pixels = map->bitmap_data();
341 const auto& palette = map->current_palette();
342 if (pixels.size() <
static_cast<size_t>(kScreen) * kScreen) {
343 return absl::InternalError(
344 absl::StrFormat(
"Map 0x%02X has no bitmap after build", screen));
346 result.
screens.push_back(screen);
347 for (
int y = 0; y < kScreen; ++y) {
348 for (
int x = 0; x < kScreen; ++x) {
349 const uint8_t idx = pixels[y * kScreen + x];
350 uint8_t r = 0, g = 0, b = 0;
351 palette_rgb(palette, idx, r, g, b);
352 if (!overlay_pixels.empty()) {
353 const uint8_t oidx = overlay_pixels[y * kScreen + x];
355 uint8_t orr = 0, og = 0, ob = 0;
356 palette_rgb(palette, oidx, orr, og, ob);
357 if (overlay_is_background) {
364 r =
static_cast<uint8_t
>((r + orr) / 2);
365 g =
static_cast<uint8_t
>((g + og) / 2);
366 b =
static_cast<uint8_t
>((b + ob) / 2);
370 const size_t base = (
static_cast<size_t>(dy * kScreen + y) * width +
373 native[base + 0] = r;
374 native[base + 1] = g;
375 native[base + 2] = b;
376 native[base + 3] = 255;
382 const int out_w =
static_cast<int>(width * req.
scale);
383 const int out_h =
static_cast<int>(height * req.
scale);
384 std::vector<uint8_t> rgba(
static_cast<size_t>(out_w) * out_h * 4, 255);
385 for (
int oy = 0; oy < out_h; ++oy) {
386 const int sy = oy * height / out_h;
387 for (
int ox = 0; ox < out_w; ++ox) {
388 const int sx = ox * width / out_w;
389 std::copy_n(native.begin() + (
static_cast<size_t>(sy) * width + sx) * 4,
390 4, rgba.begin() + (
static_cast<size_t>(oy) * out_w + ox) * 4);
396 const int origin_x = (parent % 8) * kScreen;
397 const int origin_y = ((parent % 64) / 8) * kScreen;
398 auto owned = [&](
int map_id) {
400 return map !=
nullptr && map->parent() == parent;
402 auto in_area = [&](
int lx,
int ly) {
403 return lx >= 0 && ly >= 0 && lx < width && ly < height;
407 for (
int x = 0; x <= width; x += 16) {
408 const bool edge = x % kScreen == 0;
409 draw.
DrawLine(
static_cast<float>(x), 0,
static_cast<float>(x),
410 static_cast<float>(height - 1), 255, 255, 255,
413 for (
int y = 0; y <= height; y += 16) {
414 const bool edge = y % kScreen == 0;
415 draw.
DrawLine(0,
static_cast<float>(y),
static_cast<float>(width - 1),
416 static_cast<float>(y), 255, 255, 255, edge ? 180 : 40);
420 auto mark = [&](
const std::string& kind,
int id,
int lx,
int ly,
421 const std::string& label, uint8_t r, uint8_t g, uint8_t b,
422 int phase = -1,
int list_index = -1) {
423 draw.
DrawFilledRect(
static_cast<float>(lx),
static_cast<float>(ly), 16, 16,
425 draw.
DrawRect(
static_cast<float>(lx),
static_cast<float>(ly), 16, 16, r, g,
427 DrawLabel(draw,
static_cast<float>(lx),
static_cast<float>(ly + 17), label,
429 result.
markers.push_back({kind, id, phase, list_index, lx, ly});
433 for (
const auto& item : overworld.
all_items()) {
434 if (item.deleted || !owned(item.room_map_id_))
436 const int lx = item.x_ - origin_x;
437 const int ly = item.y_ - origin_y;
438 if (!in_area(lx, ly))
440 mark(
"item", item.id_, lx, ly, absl::StrFormat(
"I%02X", item.id_), 255,
445 for (
const auto& entrance : overworld.
entrances()) {
446 if (entrance.deleted || !owned(entrance.map_id_))
448 const int lx = entrance.x_ - origin_x;
449 const int ly = entrance.y_ - origin_y;
450 if (!in_area(lx, ly))
452 mark(
"entrance", entrance.entrance_id_, lx, ly,
453 absl::StrFormat(
"E%02X", entrance.entrance_id_), 255, 255, 255);
457 for (
const auto& hole : overworld.
holes()) {
458 if (hole.deleted || !owned(hole.map_id_))
460 const int lx = hole.x_ - origin_x;
461 const int ly = hole.y_ - origin_y;
462 if (!in_area(lx, ly))
464 mark(
"hole", hole.entrance_id_, lx, ly,
465 absl::StrFormat(
"H%02X", hole.entrance_id_), 255, 150, 40);
469 for (
const auto& exit : *overworld.
exits()) {
470 if (exit.deleted_ || !owned(exit.map_id_))
472 const int lx = exit.x_ - origin_x;
473 const int ly = exit.y_ - origin_y;
474 if (!in_area(lx, ly))
476 mark(
"exit", exit.room_id_, lx, ly,
477 absl::StrFormat(
"X%02X", exit.room_id_), 200, 120, 255);
481 for (
int phase = 0; phase < 3; ++phase) {
484 const auto color = SpritePhaseColor(phase);
486 for (
const auto& sprite : overworld.
sprites(phase)) {
487 if (sprite.map_id() != parent)
489 const int index = list_index++;
490 if (sprite.deleted())
494 const int nudge = req.
phase < 0 ? phase * 2 : 0;
495 const int lx = sprite.x() - origin_x + nudge;
496 const int ly = sprite.y() - origin_y + nudge;
497 if (!in_area(lx, ly))
499 const std::string label =
500 req.
phase < 0 ? absl::StrFormat(
"%d:%02X", phase, sprite.id())
501 : absl::StrFormat(
"%02X", sprite.id());
502 mark(
"sprite", sprite.id(), lx, ly, label, color.r, color.g, color.b,
509 result.
width = out_w;
583 uint32_t flags,
float scale) {
588 for (
int tx = 0; tx < 64; ++tx) {
589 draw.
DrawLine(
static_cast<float>(tx * 8), 0,
static_cast<float>(tx * 8),
590 511, 60, 60, 60, 80);
592 for (
int ty = 0; ty < 64; ++ty) {
593 draw.
DrawLine(0,
static_cast<float>(ty * 8), 511,
594 static_cast<float>(ty * 8), 60, 60, 60, 80);
602 for (
int ty = 0; ty < 64; ++ty) {
603 for (
int tx = 0; tx < 64; ++tx) {
604 const uint8_t val = cc.
tiles[ty * 64 + tx];
609 const bool is_track =
610 (val >= 0xB0 && val <= 0xBE) || (val >= 0xD0 && val <= 0xD3);
614 const auto c = CollisionColor(val);
618 static_cast<float>(ty * 8), 8.0f, 8.0f, c.r, c.g,
631 for (
int ty = 0; ty < 64; ++ty) {
632 for (
int tx = 0; tx < 64; ++tx) {
633 const bool upper = maps.upper(tx, ty) == 0x20;
634 const bool lower = maps.lower(tx, ty) == 0x20;
635 if (!upper && !lower)
638 static_cast<float>(ty * 8), 8.0f, 8.0f, 255,
639 upper ? 0 : 140, 0, 150);
647 const float px =
static_cast<float>(obj.x() * 8);
648 const float py =
static_cast<float>(obj.y() * 8);
650 static_cast<float>(std::max(1,
static_cast<int>(obj.width_)) * 8);
652 static_cast<float>(std::max(1,
static_cast<int>(obj.height_)) * 8);
653 draw.
DrawRect(px, py, pw, ph, 255, 200, 0, 200);
661 const float px =
static_cast<float>(spr.nx() * 16);
662 const float py =
static_cast<float>(spr.ny() * 16);
664 draw.
DrawRect(px, py, 16.0f, 16.0f, 255, 60, 60, 230);
670 draw.
DrawLine(256, 0, 256, 511, 200, 200, 255, 120);
671 draw.
DrawLine(0, 256, 511, 256, 200, 200, 255, 120);