Skip to content

Commit ef5c352

Browse files
committed
EditPicture: Rework the code
1 parent 2d07c7b commit ef5c352

7 files changed

Lines changed: 153 additions & 113 deletions

File tree

src/game_interpreter.cpp

Lines changed: 47 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
#include "game_interpreter_control_variables.h"
4848
#include "game_windows.h"
4949
#include "json_helper.h"
50+
#include "lcf/rpg/savepicture.h"
5051
#include "maniac_patch.h"
5152
#include "memory_management.h"
5253
#include "pixel_format.h"
@@ -4722,13 +4723,11 @@ bool Game_Interpreter::CommandManiacGetPictureInfo(lcf::rpg::EventCommand const&
47224723
// Type 3: Pixel Data Extraction
47234724
if (info_type == 3) {
47244725
auto* sprite = pic.sprite.get();
4725-
if (!sprite) return true;
4726-
47274726
auto bitmap = sprite->GetBitmap();
47284727

47294728
// If this is a Window (String Picture), the visual content is generated by the Window class.
47304729
// We force a refresh/draw cycle here to ensure we read the actual text/window graphics.
4731-
if (data.easyrpg_type == lcf::rpg::SavePicture::EasyRpgType_window) {
4730+
if (pic.IsWindowAttached()) {
47324731
const auto& window = Main_Data::game_windows->GetWindow(pic_id);
47334732
if (window.window) {
47344733
bitmap->Clear();
@@ -4746,7 +4745,6 @@ bool Game_Interpreter::CommandManiacGetPictureInfo(lcf::rpg::EventCommand const&
47464745
// Bit 0: Ignore Alpha (return 0x00RRGGBB instead of 0xFFRRGGBB)
47474746
bool ignore_alpha = (com.parameters[2] & 2) != 0;
47484747

4749-
// Creates a snapshot of the current frame
47504748
Rect frame_rect{pic_x, pic_y, pic_w, pic_h};
47514749

47524750
if (!ManiacPatch::WritePixelsToVariable(*bitmap, frame_rect, dst_var_id, ignore_alpha, *Main_Data::game_variables)) {
@@ -5372,121 +5370,59 @@ bool Game_Interpreter::CommandManiacEditPicture(lcf::rpg::EventCommand const& co
53725370
return true;
53735371
}
53745372

5375-
auto& pic = Main_Data::game_pictures->GetPicture(pic_id);
5373+
auto& picture = Main_Data::game_pictures->GetPicture(pic_id);
53765374

5377-
if (pic.IsRequestPending()) {
5378-
pic.MakeRequestImportant();
5375+
if (picture.IsRequestPending()) {
5376+
picture.MakeRequestImportant();
53795377
_async_op = AsyncOp::MakeYieldRepeat();
53805378
return true;
53815379
}
53825380

5383-
auto* sprite = pic.sprite.get();
5384-
if (!sprite) return true;
5385-
5381+
auto* sprite = picture.sprite.get();
53865382
auto bitmap = sprite->GetBitmap();
5387-
if (!bitmap) return true;
53885383

5389-
// 1. Calculate Spritesheet Offset
5384+
// Calculate Spritesheet Offset
53905385
// Maniacs operations are relative to the currently active cell.
5391-
int offset_x = 0;
5392-
int offset_y = 0;
5393-
5394-
const auto& data = pic.data;
5395-
if (data.spritesheet_cols > 1 || data.spritesheet_rows > 1) {
5396-
int frame_width = bitmap->GetWidth() / data.spritesheet_cols;
5397-
int frame_height = bitmap->GetHeight() / data.spritesheet_rows;
5398-
5399-
// Map current frame index to X/Y coords
5400-
offset_x = (data.spritesheet_frame % data.spritesheet_cols) * frame_width;
5401-
offset_y = (data.spritesheet_frame / data.spritesheet_cols) * frame_height;
5402-
}
5403-
5404-
// 2. COW & Window Detach Logic
5405-
BitmapRef writeable_bitmap = bitmap;
54065386

5407-
bool is_cached = !bitmap->GetId().empty() && !StartsWith(bitmap->GetId(), "Canvas:");
5408-
bool wrong_format = bitmap->bpp() != 4;
5409-
bool is_window = data.easyrpg_type == lcf::rpg::SavePicture::EasyRpgType_window;
5387+
// Determine Spritesheet frame
5388+
Rect src_rect = sprite->GetSrcRect();
54105389

5411-
if (is_cached || wrong_format || is_window) {
5412-
writeable_bitmap = Bitmap::Create(bitmap->GetWidth(), bitmap->GetHeight());
5413-
writeable_bitmap->BlitFast(0, 0, *bitmap, bitmap->GetRect(), 255);
5390+
BitmapRef writable_bitmap = bitmap;
54145391

5415-
writeable_bitmap->SetId("Canvas:" + std::to_string(pic_id));
5416-
5417-
sprite->SetBitmap(writeable_bitmap);
5418-
5419-
if (is_window) {
5420-
pic.data.easyrpg_type = lcf::rpg::SavePicture::EasyRpgType_default;
5392+
if (picture.IsWindowAttached()) {
5393+
// If this is a Window (String Picture), the visual content is generated by the Window class.
5394+
// We force a refresh/draw cycle here to ensure we read the actual text/window graphics.
5395+
const auto& window = Main_Data::game_windows->GetWindow(pic_id);
5396+
if (window.window) {
5397+
bitmap->Clear();
5398+
window.window->Draw(*bitmap);
54215399
}
5422-
5423-
// Force sprite to recalculate its clipping rectangle immediately.
5424-
// Otherwise, SetBitmap resets src_rect to full size, showing the whole sheet.
5425-
sprite->OnPictureShow();
5400+
} else if (picture.IsCanvas()) {
5401+
// no-op
5402+
} else {
5403+
// Must be copied to avoid modifiying the original picture
5404+
writable_bitmap = Bitmap::Create(*bitmap, src_rect, true);
54265405
}
54275406

5428-
// 3. Parameters
5429-
int x = ValueOrVariableBitfield(com.parameters[0], 1, com.parameters[2]);
5430-
int y = ValueOrVariableBitfield(com.parameters[0], 2, com.parameters[3]);
5431-
int w = ValueOrVariableBitfield(com.parameters[0], 3, com.parameters[4]);
5432-
int h = ValueOrVariableBitfield(com.parameters[0], 4, com.parameters[5]);
5407+
picture.data.easyrpg_type = lcf::rpg::SavePicture::EasyRpgType_canvas;
54335408

5434-
int src_var_start = ValueOrVariableBitfield(com.parameters[0], 5, com.parameters[6]);
5409+
// Packing: x pos, y pos, width, height, var_id
5410+
int pic_x = ValueOrVariableBitfield(com.parameters[0], 1, com.parameters[2]);
5411+
int pic_y = ValueOrVariableBitfield(com.parameters[0], 2, com.parameters[3]);
5412+
int pic_w = ValueOrVariableBitfield(com.parameters[0], 3, com.parameters[4]);
5413+
int pic_h = ValueOrVariableBitfield(com.parameters[0], 4, com.parameters[5]);
5414+
int start_var_id = ValueOrVariableBitfield(com.parameters[0], 5, com.parameters[6]);
54355415

54365416
int flags = com.parameters[7];
5437-
bool flag_opaq = (flags & 1) != 0;
5438-
bool flag_skip_trans = (flags & 2) != 0;
5439-
5440-
// 4. Drawing Loop
5441-
int bmp_w = writeable_bitmap->GetWidth();
5442-
int bmp_h = writeable_bitmap->GetHeight();
5443-
5444-
if (w <= 0 || h <= 0) return true;
5417+
// When no flag is set the area is cleared and a OP_OVER blit occurs
5418+
bool flag_opaq = (flags & 1) != 0; // Blit with OP_SRC
5419+
bool flag_skip_trans = (flags & 2) != 0; // Blit with OP_OVER
54455420

5446-
uint32_t* pixels = static_cast<uint32_t*>(writeable_bitmap->pixels());
5447-
int pitch = writeable_bitmap->pitch() / 4;
5421+
bool clear_dst = !flag_opaq && !flag_skip_trans;
5422+
bool ignore_alpha = flag_opaq;
54485423

5449-
int current_var = src_var_start;
5450-
auto& vars = *Main_Data::game_variables;
5451-
5452-
for (int iy = 0; iy < h; ++iy) {
5453-
// Apply Y Offset here
5454-
int py = y + iy + offset_y;
5455-
5456-
if (py < 0 || py >= bmp_h) {
5457-
current_var += w;
5458-
continue;
5459-
}
5460-
5461-
for (int ix = 0; ix < w; ++ix) {
5462-
int32_t color_val = vars.Get(current_var++);
5463-
5464-
// Apply X Offset here
5465-
int px = x + ix + offset_x;
5466-
5467-
if (px < 0 || px >= bmp_w) continue;
5468-
5469-
uint8_t a = (color_val >> 24) & 0xFF;
5470-
uint8_t r = (color_val >> 16) & 0xFF;
5471-
uint8_t g = (color_val >> 8) & 0xFF;
5472-
uint8_t b = (color_val) & 0xFF;
5473-
5474-
if (flag_skip_trans && a == 0) continue;
5475-
5476-
if (flag_opaq) {
5477-
a = 0xFF;
5478-
}
5479-
5480-
if (a < 255) {
5481-
r = (r * a) / 255;
5482-
g = (g * a) / 255;
5483-
b = (b * a) / 255;
5484-
}
5485-
5486-
uint32_t final_pixel = Bitmap::pixel_format.rgba_to_uint32_t(r, g, b, a);
5487-
pixels[py * pitch + px] = final_pixel;
5488-
}
5489-
}
5424+
Rect frame_rect{pic_x, pic_y, pic_w, pic_h};
5425+
ManiacPatch::ReadPixelsFromVariable(*writable_bitmap, frame_rect, start_var_id, clear_dst, ignore_alpha, *Main_Data::game_variables);
54905426

54915427
return true;
54925428
}
@@ -5553,9 +5489,17 @@ bool Game_Interpreter::CommandManiacWritePicture(lcf::rpg::EventCommand const& c
55535489
const auto sprite = picture.sprite.get();
55545490

55555491
// Retrieve bitmap
5556-
if (picture.IsWindowAttached() || picture.IsCanvas()) {
5557-
// Cannot change transparency of images that are not reloadable from a file
5558-
// Appears to match Maniacs behaviour
5492+
// Cannot change transparency of images that are not reloadable from a file (window and canvas)
5493+
// Appears to match Maniacs behaviour
5494+
if (picture.IsWindowAttached()) {
5495+
// If this is a Window (String Picture), the visual content is generated by the Window class.
5496+
// We force a refresh/draw cycle here to ensure we read the actual text/window graphics.
5497+
const auto& window = Main_Data::game_windows->GetWindow(pic_id);
5498+
if (window.window) {
5499+
bitmap->Clear();
5500+
window.window->Draw(*bitmap);
5501+
}
5502+
} else if (picture.IsCanvas()) {
55595503
bitmap = picture.sprite->GetBitmap();
55605504
} else if (picture.data.name.empty()) {
55615505
// Not much we can do here (also shouldn't happen normally)

src/game_pictures.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,10 @@ void Game_Pictures::Picture::AttachWindow(const Window_Base& window) {
503503
ApplyOrigin(false);
504504
}
505505

506+
bool Game_Pictures::Picture::IsNormalPicture() const {
507+
return data.easyrpg_type == lcf::rpg::SavePicture::EasyRpgType_default;
508+
}
509+
506510
bool Game_Pictures::Picture::IsWindowAttached() const {
507511
return data.easyrpg_type == lcf::rpg::SavePicture::EasyRpgType_window;
508512
}

src/game_pictures.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,9 @@ class Game_Pictures {
127127
void OnMapScrolled(int dx, int dy);
128128

129129
void AttachWindow(const Window_Base& window);
130-
bool IsWindowAttached() const;
131130

131+
bool IsNormalPicture() const;
132+
bool IsWindowAttached() const;
132133
bool IsCanvas() const;
133134
};
134135

src/game_variables.cpp

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -210,12 +210,9 @@ void Game_Variables::WriteArray(const int first_id_a, const int last_id_a, const
210210
}
211211
}
212212

213-
std::vector<Var_t> Game_Variables::GetRange(int variable_id, int length) const {
214-
std::vector<Var_t> vars;
215-
for (int i = 0; i < length; ++i) {
216-
vars.push_back(Get(variable_id + i));
217-
}
218-
return vars;
213+
lcf::Span<const Var_t> Game_Variables::GetRange(int variable_id, int length) {
214+
PrepareRange(variable_id, variable_id + length, "Invalid write var[{},{}]!");
215+
return lcf::Span<const Var_t>(&_variables[variable_id - 1], length);
219216
}
220217

221218
lcf::Span<Var_t> Game_Variables::GetWritableRange(int variable_id, int length) {

src/game_variables.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class Game_Variables {
5050
Var_t Get(int variable_id) const;
5151
Var_t GetIndirect(int variable_id) const;
5252
Var_t GetWithMode(int id, int mode) const;
53-
std::vector<Var_t> GetRange(int variable_id, int length) const;
53+
lcf::Span<const Var_t> GetRange(int variable_id, int length);
5454
lcf::Span<Var_t> GetWritableRange(int variable_id, int length);
5555

5656
Var_t Set(int variable_id, Var_t value);

src/maniac_patch.cpp

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,87 @@ bool ManiacPatch::CheckString(std::string_view str_l, std::string_view str_r, in
713713
return check(str_l, str_r);
714714
}
715715

716+
bool ManiacPatch::ReadPixelsFromVariable(Bitmap& dst, Rect dst_rect, int start_var_id, bool clear_dst, bool ignore_alpha, Game_Variables& variables) {
717+
int pic_x = dst_rect.x;
718+
int pic_y = dst_rect.y;
719+
int pic_w = dst_rect.width;
720+
int pic_h = dst_rect.height;
721+
722+
if (pic_w <= 0 || pic_h <= 0) {
723+
return false;
724+
}
725+
726+
// Format expected by Maniacs
727+
auto format = format_B8G8R8A8_a().format();
728+
if (ignore_alpha) {
729+
format = format_B8G8R8A8_n().format();
730+
}
731+
732+
// Allocate an image as large as the requested dimensions (ignoring out of bounds)
733+
Rect bmp_rect = dst.GetRect();
734+
Rect frame_rect = bmp_rect.GetSubRect(dst_rect);
735+
736+
if (frame_rect.width <= 0 || frame_rect.height <= 0) {
737+
return false;
738+
}
739+
740+
BitmapRef frame = Bitmap::Create(nullptr, frame_rect.width, frame_rect.height, frame_rect.width * format.bytes, format);
741+
742+
uint32_t* pixels = static_cast<uint32_t*>(frame->pixels());
743+
int px_per_row = frame->pitch() / sizeof(uint32_t);
744+
uint32_t* dst_row = pixels;
745+
746+
// Rowwise memcpy
747+
int x_l = std::min(0, pic_x);
748+
int x_r = std::max(0, pic_x + pic_w - bmp_rect.width) + frame_rect.width;
749+
int y_t = std::min(0, pic_y);
750+
int y_b = std::max(0, pic_y + pic_h - bmp_rect.height) + frame_rect.height;
751+
752+
int src_var_id = start_var_id;
753+
dst_row = pixels;
754+
for (int y = y_t; y < y_b; ++y) {
755+
// When row out of bounds skip
756+
if (y < 0 || y >= frame_rect.height) {
757+
src_var_id += pic_w;
758+
continue;
759+
}
760+
761+
for (int x = x_l; x < x_r;) {
762+
if (x < 0) {
763+
// OOB to the left (skip)
764+
src_var_id += -x;
765+
x = 0;
766+
} else if (x >= frame_rect.width) {
767+
// OOB to the right (skip)
768+
int len = x_r - frame_rect.width;
769+
src_var_id += len;
770+
break;
771+
} else {
772+
auto in_range = variables.GetRange(src_var_id, frame_rect.width);
773+
std::copy(in_range.begin(), in_range.end(), dst_row);
774+
dst_row += px_per_row;
775+
src_var_id += frame_rect.width;
776+
x += frame_rect.width;
777+
}
778+
}
779+
}
780+
781+
if (clear_dst) {
782+
dst.ClearRect({pic_x, pic_y, frame_rect.width, frame_rect.height});
783+
}
784+
785+
dst.Blit(pic_x, pic_y, *frame, frame->GetRect(), Opacity::Opaque(),
786+
ignore_alpha ? Bitmap::BlendMode::NormalWithoutAlpha : Bitmap::BlendMode::Normal);
787+
788+
return true;
789+
}
790+
716791
bool ManiacPatch::WritePixelsToVariable(const Bitmap& src, Rect src_rect, int start_var_id, bool ignore_alpha, Game_Variables& variables) {
792+
// FIXME: Because we use premultiplied alpha the colors of transparent pixels are lost (always 0)
793+
// Maniacs appears to preserve them
794+
// E.g. when reading a transparent pixel from Chara1 (which was green) then Maniac will read green and we read 0
795+
// This is noticable e.g. when using the EditPicture command with the opaque flag when reading from a transparent image
796+
717797
int pic_x = src_rect.x;
718798
int pic_y = src_rect.y;
719799
int pic_w = src_rect.width;
@@ -740,7 +820,8 @@ bool ManiacPatch::WritePixelsToVariable(const Bitmap& src, Rect src_rect, int st
740820
BitmapRef frame = Bitmap::Create(nullptr, frame_rect.width, frame_rect.height, frame_rect.width * format.bytes, format);
741821

742822
// Then blit the screen (converts to the correct format)
743-
frame->BlitFast(0, 0, src, frame_rect, Opacity::Opaque());
823+
frame->Blit(0, 0, src, frame_rect, Opacity::Opaque(),
824+
ignore_alpha ? Bitmap::BlendMode::NormalWithoutAlpha : Bitmap::BlendMode::Default);
744825

745826
uint32_t* pixels = static_cast<uint32_t*>(frame->pixels());
746827
int px_per_row = frame->pitch() / sizeof(uint32_t);

src/maniac_patch.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,26 @@ namespace ManiacPatch {
3737

3838
bool CheckString(std::string_view str_l, std::string_view str_r, int op, bool ignore_case);
3939

40+
/**
41+
* Reads pixel data in ARGB format out of variables and writes them into a bitmap
42+
*
43+
* @param dst Bitmap to write
44+
* @param dst_rect Bitmap area to write to
45+
* @param start_var_id Begin of variable range
46+
* @param clear_dst Clears the target area before blitting
47+
* @param ignore_alpha Blit as if no alpha channel exists (faster)
48+
* @param variables Variable list
49+
* @return true when any pixels were modified, false otherwise
50+
*/
51+
bool ReadPixelsFromVariable(Bitmap& dst, Rect dst_rect, int start_var_id, bool clear_dst, bool ignore_alpha, Game_Variables& variables);
52+
4053
/**
4154
* Extracts pixel data out of a bitmap writing it into a range of variables in ARGB format
4255
*
4356
* @param src Bitmap
4457
* @param src_rect Bitmap area to extract
4558
* @param start_var_id Begin of variable range
46-
* @param ignore_alpha Sets the alpha bits in all pixels to 0 (slow)
59+
* @param ignore_alpha Sets the alpha channel in all pixel to 0 (slow)
4760
* @param variables Variable list
4861
* @return true when any variables were modified, false otherwise
4962
*/

0 commit comments

Comments
 (0)