Skip to content

Commit db314e4

Browse files
committed
spreites and rotations
1 parent b2e1977 commit db314e4

File tree

10 files changed

+1353
-195
lines changed

10 files changed

+1353
-195
lines changed

AGENTS.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Codex Notes: ColorizingDMD Editor + libserum
2+
3+
## Scope
4+
- This file documents development context and non-obvious legacy-format behavior.
5+
- It must not change cROM/cROMc data formats.
6+
- The editor must remain a drop-in replacement for the legacy app.
7+
- The new editor targets Serum V2 only; libserum still supports V1 for other clients.
8+
9+
## Data format constraints (legacy compatibility)
10+
- Do not alter the binary layout of cROM/cROMc files.
11+
- All editor changes must map to existing fields in the legacy format.
12+
13+
## Key serum relationships (from libserum)
14+
- Dynamic masks are per-frame pixel maps:
15+
- `dynamasks[frame][pixel]` (SD) and `dynamasks_extra[frame][pixel]` (HD).
16+
- Each pixel stores a dynacouche index (0..31) or 255 for none.
17+
- Dynamic colors are per-frame sets:
18+
- `dyna4cols_v2[frame][dynacouche * nocolors + originalPixel]` (SD)
19+
- `dyna4cols_v2_extra[frame][...]` (HD)
20+
- Sprite dynamic colors are per-sprite:
21+
- `dynaspritemasks[sprite][pixel]` selects a dynacouche index.
22+
- `dynasprite4cols[sprite][dynacouche * nocolors + originalPixel]`.
23+
- Sprite detection:
24+
- Frame detection zones: `framespriteBB[frame][slot*4..]`.
25+
- Per-sprite detection areas: `spritedetareas[sprite][area*4..]`.
26+
- Matching + placement logic is in libserum `Check_Spritesv2`.
27+
- Backgrounds:
28+
- `backgroundIDs[frame][0]` selects background.
29+
- `backgroundmask[frame][pixel]` masks background visibility.
30+
- Applied before dynamic masks; sprites applied after.
31+
32+
## Rendering order (v2, runtime behavior)
33+
1) Background (if original pixel is 0 and mask allows).
34+
2) Dynamic mask replacement (dyna4cols).
35+
3) Color rotations (if enabled).
36+
4) Sprites overlay (spritecolored / dynasprite4cols).
37+
38+
## Editor goals
39+
- WYSIWYG: render canvas + preview using libserum logic.
40+
- Avoid data duplication for large projects.
41+
- Preview row only renders the visible subset of frames.
42+
43+
## Editor mask behavior
44+
- Frame uses exactly one comparison mask and one dynamic mask assignment.
45+
- Mask editing happens on the original (orange) frame.
46+
- Background mask is independent from mask/dynamic mask toggles.
47+
48+
## HD/SD rules
49+
- HD is 256x64 (SD is 128x32).
50+
- HD previews are double size vs SD previews.
51+
- When HD is absent, treat all-black HD backgrounds as non-existent.
52+
- HD background masks should be double-scaled when generated from SD.
53+
54+
## Open integration task
55+
- Add libserum editor API (non-owning view) to render frames + sprite matches.
56+
- Keep libserum free of new dependencies (no Qt/OpenCV).

README.editor.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# Editor + libserum Development Notes
2+
3+
This document captures legacy-format behavior and editor migration decisions.
4+
It complements `readme.md` (user-facing info) and must not change file formats.
5+
6+
## Guiding constraints
7+
- Keep cROM/cROMc formats 100% compatible with existing tools.
8+
- The editor should be a drop-in replacement for the legacy app.
9+
- Avoid new dependencies in libserum (no Qt/OpenCV).
10+
- The editor targets Serum V2; libserum retains V1 for other consumers.
11+
12+
## Current direction
13+
- Use libserum for canvas + preview rendering (WYSIWYG).
14+
- Add a libserum editor API with non-owning data views to avoid RAM blowups.
15+
- Preview row renders only the visible subset of frames.
16+
17+
## libserum editor API concept
18+
- New header: `serum-editor.h` (separate from `serum-decode.h`).
19+
- Core functions (names TBD):
20+
- Create/destroy editor context.
21+
- Attach a non-owning `SerumDataView` (raw pointers + sizes).
22+
- Render a frame by ID using project originals.
23+
- Match sprites for a frame and return positions.
24+
- Optional: render with precomputed matches to avoid repeat matching.
25+
- Color rotations should be off by default, enabled only on demand.
26+
27+
## Legacy data relationships (v2)
28+
- Dynamic masks: `dynamasks[frame][pixel]` -> dynacouche index.
29+
- Dynamic colors: `dyna4cols_v2[frame][dynacouche * nocolors + originalPixel]`.
30+
- Sprite dynamic colors:
31+
- `dynaspritemasks[sprite][pixel]` -> dynacouche index.
32+
- `dynasprite4cols[sprite][dynacouche * nocolors + originalPixel]`.
33+
- Sprite detection:
34+
- Frame detection zones: `framespriteBB[frame][slot*4..]`.
35+
- Per-sprite detection areas: `spritedetareas[sprite][area*4..]`.
36+
37+
## Rendering order (runtime behavior)
38+
1) Background (when original pixel is 0 and mask allows).
39+
2) Dynamic mask replacement.
40+
3) Color rotations (if enabled).
41+
4) Sprites overlay.
42+
43+
## UI conventions implemented so far
44+
- Masks and dynamic masks show semi-transparent overlays + outlines on grid.
45+
- Background mask is independent from mask/dynamic mask toggles.
46+
- HD frames render as same absolute size with higher internal resolution.
47+
48+
## Open tasks
49+
- Add libserum editor API and wire canvas + preview rendering.
50+
- Use libserum sprite matching to fix sprite placement in editor.
51+
- Add on-demand rotation preview button.

app/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ target_sources(ColorizingDMD PRIVATE
88
CanvasWidget.h
99
GLCanvasWidget.cpp
1010
GLCanvasWidget.h
11+
../libserum/src/serum-editor.cpp
1112
ProjectState.cpp
1213
ProjectState.h
1314
ProjectIO.cpp
@@ -47,4 +48,5 @@ target_link_libraries(ColorizingDMD PRIVATE
4748

4849
target_include_directories(ColorizingDMD PRIVATE
4950
${CMAKE_SOURCE_DIR}
51+
${CMAKE_SOURCE_DIR}/libserum/src
5052
)

app/CanvasWidget.cpp

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ CanvasWidget::CanvasWidget(const QString& title, QWidget* parent)
2121
, m_backgroundMaskButton(new QToolButton(m_canvas))
2222
, m_zoneButton(new QToolButton(m_canvas))
2323
, m_hdButton(new QToolButton(m_canvas))
24+
, m_rotateButton(new QToolButton(m_canvas))
2425
{
2526
auto* layout = new QVBoxLayout(this);
2627
m_canvas->setOverlayText(title);
@@ -93,6 +94,13 @@ CanvasWidget::CanvasWidget(const QString& title, QWidget* parent)
9394
m_hdButton->setToolTip("Toggle HD frame");
9495
m_hdButton->setCursor(Qt::PointingHandCursor);
9596
connect(m_hdButton, &QToolButton::toggled, this, &CanvasWidget::hdToggled);
97+
98+
m_rotateButton->setText("Rotate");
99+
m_rotateButton->setCheckable(true);
100+
m_rotateButton->setAutoRaise(true);
101+
m_rotateButton->setToolTip("Preview color rotations");
102+
m_rotateButton->setCursor(Qt::PointingHandCursor);
103+
connect(m_rotateButton, &QToolButton::toggled, this, &CanvasWidget::rotateToggled);
96104
}
97105

98106
void CanvasWidget::setTitle(const QString& title)
@@ -236,6 +244,23 @@ void CanvasWidget::setZoneButtonVisible(bool visible)
236244
m_zoneButton->setVisible(visible);
237245
}
238246

247+
void CanvasWidget::setRotateChecked(bool enabled)
248+
{
249+
if (!m_rotateButton) {
250+
return;
251+
}
252+
QSignalBlocker blocker(m_rotateButton);
253+
m_rotateButton->setChecked(enabled);
254+
}
255+
256+
void CanvasWidget::setRotateEnabled(bool enabled)
257+
{
258+
if (!m_rotateButton) {
259+
return;
260+
}
261+
m_rotateButton->setEnabled(enabled);
262+
}
263+
239264
void CanvasWidget::setBackgroundChecked(bool enabled)
240265
{
241266
if (!m_backgroundButton) {
@@ -282,7 +307,8 @@ void CanvasWidget::resizeEvent(QResizeEvent* event)
282307
{
283308
QWidget::resizeEvent(event);
284309
if (!m_fitButton || !m_gridButton || !m_originalButton || !m_backgroundButton ||
285-
!m_maskButton || !m_dynamicButton || !m_backgroundMaskButton || !m_zoneButton || !m_hdButton) {
310+
!m_maskButton || !m_dynamicButton || !m_backgroundMaskButton || !m_zoneButton || !m_hdButton ||
311+
!m_rotateButton) {
286312
return;
287313
}
288314
const int margin = 8;
@@ -331,4 +357,9 @@ void CanvasWidget::resizeEvent(QResizeEvent* event)
331357
const int hdX = bgX - hdSize.width() - 6;
332358
m_hdButton->move(hdX, y);
333359
m_hdButton->raise();
360+
361+
const QSize rotateSize = m_rotateButton->sizeHint();
362+
const int rotateX = hdX - rotateSize.width() - 6;
363+
m_rotateButton->move(rotateX, y);
364+
m_rotateButton->raise();
334365
}

app/CanvasWidget.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class CanvasWidget : public QWidget
3737
void setZoneButtonChecked(bool enabled);
3838
void setZoneButtonEnabled(bool enabled);
3939
void setZoneButtonVisible(bool visible);
40+
void setRotateChecked(bool enabled);
41+
void setRotateEnabled(bool enabled);
4042

4143
signals:
4244
void fitRequested();
@@ -48,6 +50,7 @@ class CanvasWidget : public QWidget
4850
void backgroundMaskToggled(bool enabled);
4951
void zoneToggled(bool enabled);
5052
void hdToggled(bool enabled);
53+
void rotateToggled(bool enabled);
5154

5255
protected:
5356
void resizeEvent(QResizeEvent* event) override;
@@ -64,4 +67,5 @@ class CanvasWidget : public QWidget
6467
QToolButton* m_backgroundMaskButton;
6568
QToolButton* m_zoneButton;
6669
QToolButton* m_hdButton;
70+
QToolButton* m_rotateButton;
6771
};

0 commit comments

Comments
 (0)