diff --git a/src/app.cpp b/src/app.cpp index 70b2640..0b4fc9f 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -54,7 +54,7 @@ void App::create() void App::open_document(std::string path) { - std::regex r(R"((.*)[\\/]?([^\\/]+)\.(\w+)$)"); + std::regex r(R"((.*)[\\/]([^\\/]+)\.(\w+)$)"); std::smatch m; if (!std::regex_search(path, m, r)) return; diff --git a/src/canvas.cpp b/src/canvas.cpp index c45c1e4..eef1954 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -5,6 +5,8 @@ #include "texture.h" #include "node_progress_bar.h" #include +#include +#include #ifdef __APPLE__ #include @@ -2117,8 +2119,13 @@ bool Canvas::project_save_thread(std::string file_path, bool show_progress) int n_layers = (int)m_layers.size(); fwrite(&n_layers, sizeof(int), 1, fp); + int n_frames = std::accumulate(m_layers.begin(), m_layers.end(), 0, + [](int tot, auto& l) { return tot + l->m_frames.size(); }); + if (ppi_header.doc_version.minor >= 3) + fwrite(&n_frames, sizeof(int), 1, fp); + int progress = 0; - int total = (int)m_layers.size() * 6; + int total = n_frames * 6; for (int i = 0; i < (int)m_layers.size(); i++) { @@ -2132,44 +2139,57 @@ bool Canvas::project_save_thread(std::string file_path, bool show_progress) fwrite(&name_len, sizeof(int), 1, fp); fwrite(m_layers[i]->m_name.data(), name_len, 1, fp); - if (ppi_header.doc_version.minor > 1) + if (ppi_header.doc_version.minor >= 2) { fwrite(&m_layers[i]->m_blend_mode, sizeof(int), 1, fp); fwrite(&m_layers[i]->m_alpha_locked, sizeof(bool), 1, fp); fwrite(&m_layers[i]->m_visible, sizeof(bool), 1, fp); } - - m_layers[i]->optimize(); - auto snap = m_layers[i]->snapshot(); - for (int plane_index = 0; plane_index < 6; plane_index++) + + int frames = 1; + if (ppi_header.doc_version.minor >= 3) { - int has_data = snap.m_dirty_face[plane_index] ? 1 : 0; - fwrite(&has_data, sizeof(int), 1, fp); - if (has_data) + frames = (int)m_layers[i]->m_frames.size(); + fwrite(&frames, sizeof(int), 1, fp); + } + + for (int fi = 0; fi < frames; fi++) + { + if (ppi_header.doc_version.minor >= 3) + fwrite(&m_layers[i]->m_frames[fi].m_duration, sizeof(int), 1, fp); + + m_layers[i]->optimize(fi); + auto snap = m_layers[i]->snapshot(fi); + for (int plane_index = 0; plane_index < 6; plane_index++) { - glm::ivec4 b = snap.m_dirty_box[plane_index]; - glm::vec2 sz = zw(b) - xy(b); - int box[4] = { b.x, b.y, b.z, b.w }; - fwrite(&box, sizeof(box), 1, fp); - - std::vector compressed; - auto callback = [](void *context, void *data, int size) + int has_data = snap.m_dirty_face[plane_index] ? 1 : 0; + fwrite(&has_data, sizeof(int), 1, fp); + if (has_data) { - std::vector* buffer = static_cast*>(context); - buffer->insert(buffer->end(), (uint8_t*)data, (uint8_t*)data + size); - }; - int ret = stbi_write_png_to_func(callback, &compressed, sz.x, sz.y, 4, snap.image[plane_index].get(), sz.x * 4); - - int data_size = (int)compressed.size(); - fwrite(&data_size, sizeof(int), 1, fp); - - fwrite(compressed.data(), 1, compressed.size(), fp); + glm::ivec4 b = snap.m_dirty_box[plane_index]; + glm::vec2 sz = zw(b) - xy(b); + int box[4] = { b.x, b.y, b.z, b.w }; + fwrite(&box, sizeof(box), 1, fp); + + std::vector compressed; + auto callback = [](void* context, void* data, int size) + { + std::vector* buffer = static_cast*>(context); + buffer->insert(buffer->end(), (uint8_t*)data, (uint8_t*)data + size); + }; + int ret = stbi_write_png_to_func(callback, &compressed, sz.x, sz.y, 4, snap.image[plane_index].get(), sz.x * 4); + + int data_size = (int)compressed.size(); + fwrite(&data_size, sizeof(int), 1, fp); + + fwrite(compressed.data(), 1, compressed.size(), fp); + } + progress++; + float p = (float)progress / total * 100.f; + if (show_progress) + pb->m_progress->SetWidthP(p); + LOG("progress: %f", p); } - progress++; - float p = (float)progress / total * 100.f; - if (show_progress) - pb->m_progress->SetWidthP(p); - LOG("progress: %f", p); } } fclose(fp); @@ -2273,13 +2293,16 @@ bool Canvas::project_open_thread(std::string file_path) int n_layers = 0; fread(&n_layers, sizeof(int), 1, fp); - + int n_frames = 1; + if (ppi_header.doc_version.minor >= 3) + fread(&n_frames, sizeof(int), 1, fp); + const int bytes = m_width * m_height * 4; Layer::Snapshot snap; snap.create(m_width, m_height); // allocate single data, no box should be bigger int progress = 0; - int total = n_layers * 6; + int total = n_frames * 6; for (auto& l : m_layers) l->destroy(); @@ -2307,52 +2330,63 @@ bool Canvas::project_open_thread(std::string file_path) std::string name(name_len, '\0'); fread((char*)name.data(), name_len, 1, fp); - if (ppi_header.doc_version.minor > 1) + if (ppi_header.doc_version.minor >= 2) { fread(&layer->m_blend_mode, sizeof(int), 1, fp); fread(&layer->m_alpha_locked, sizeof(bool), 1, fp); fread(&layer->m_visible, sizeof(bool), 1, fp); } - snap.clear(); - for (int plane_index = 0; plane_index < 6; plane_index++) - { - int has_data; - fread(&has_data, sizeof(int), 1, fp); - snap.m_dirty_face[plane_index] = has_data; - if (has_data) - { - int b[4]; - fread(&b, sizeof(b), 1, fp); - snap.m_dirty_box[plane_index] = glm::vec4(b[0], b[1], b[2], b[3]); - glm::vec2 sz = zw(snap.m_dirty_box[plane_index]) - xy(snap.m_dirty_box[plane_index]); - - int data_size; - fread(&data_size, sizeof(int), 1, fp); - std::vector compressed(data_size); - - fread(compressed.data(), 1, data_size, fp); - int imgw, imgh, imgc; - uint8_t* rgba = stbi_load_from_memory(compressed.data(), data_size, &imgw, &imgh, &imgc, 4); - if (rgba) - { - std::copy(rgba, rgba + (imgw*imgh * 4), snap.image[plane_index].get()); - delete rgba; - } - } - - progress++; - float p = (float)progress / total * 100.f; - LOG("progress: %f", p); - - if (App::I->layout.m_loaded) - { - pb->m_progress->SetWidthP(p); - } - } + int frames = 1; + if (ppi_header.doc_version.minor >= 3) + fread(&frames, sizeof(int), 1, fp); layer->create(m_width, m_height, name.c_str()); - layer->restore(snap); + + for (int fi = 0; fi < frames; fi++) + { + if (fi > 0) + layer->add_frame(); + if (ppi_header.doc_version.minor >= 3) + fread(&layer->m_frames[fi].m_duration, sizeof(int), 1, fp); + snap.clear(); + for (int plane_index = 0; plane_index < 6; plane_index++) + { + int has_data; + fread(&has_data, sizeof(int), 1, fp); + snap.m_dirty_face[plane_index] = has_data; + if (has_data) + { + int b[4]; + fread(&b, sizeof(b), 1, fp); + snap.m_dirty_box[plane_index] = glm::vec4(b[0], b[1], b[2], b[3]); + glm::vec2 sz = zw(snap.m_dirty_box[plane_index]) - xy(snap.m_dirty_box[plane_index]); + + int data_size; + fread(&data_size, sizeof(int), 1, fp); + std::vector compressed(data_size); + + fread(compressed.data(), 1, data_size, fp); + int imgw, imgh, imgc; + uint8_t* rgba = stbi_load_from_memory(compressed.data(), data_size, &imgw, &imgh, &imgc, 4); + if (rgba) + { + std::copy(rgba, rgba + (imgw * imgh * 4), snap.image[plane_index].get()); + delete rgba; + } + } + + progress++; + float p = (float)progress / total * 100.f; + LOG("progress: %f", p); + + if (App::I->layout.m_loaded) + { + pb->m_progress->SetWidthP(p); + } + } + layer->restore(snap, fi); + } } std::swap(tmp_layers, m_layers); @@ -2377,6 +2411,7 @@ bool Canvas::project_open_thread(std::string file_path) { pb->destroy(); App::I->title_update(); + App::I->animation->load_layers(); } return true; } diff --git a/src/canvas.h b/src/canvas.h index 0f6cb37..2ec36b4 100644 --- a/src/canvas.h +++ b/src/canvas.h @@ -29,7 +29,11 @@ struct PPIThumb struct PPIDocVersion { int major = 0; - int minor = 2; + + // version 1: initial + // version 2: added blend mode, alpha and visibility + // version 3: added animation frames + int minor = 3; }; struct PPISoftVersion diff --git a/src/canvas_layer.cpp b/src/canvas_layer.cpp index 649f655..b728060 100644 --- a/src/canvas_layer.cpp +++ b/src/canvas_layer.cpp @@ -101,15 +101,17 @@ void Layer::destroy() rtt(i).destroy(); } -void Layer::optimize() +void Layer::optimize(int frame /*= -1*/) { + if (frame == -1) + frame = m_frame_index; int saved_bytes = 0; for (int i = 0; i < 6; i++) { - if (!face(i)) + if (!face(i, frame)) continue; - auto data = std::unique_ptr(reinterpret_cast(rtt(i).readTextureData())); + auto data = std::unique_ptr(reinterpret_cast(rtt(i, frame).readTextureData())); glm::ivec2 bbmin(w, h); glm::ivec2 bbmax(0); for (int y = 0; y < h; y++) @@ -124,17 +126,17 @@ void Layer::optimize() } } glm::vec2 bbsz = bbmax - bbmin; - glm::vec2 old_size = zw(box(i)) - xy(box(i)); + glm::vec2 old_size = zw(box(i, frame)) - xy(box(i, frame)); glm::vec2 diff; if (bbsz.x <= 0 || bbmax.y <= 0) { - face(i) = false; - box(i) = glm::vec4(0); + face(i, frame) = false; + box(i, frame) = glm::vec4(0); diff = old_size; } else { - box(i) = { bbmin, bbmax }; + box(i, frame) = { bbmin, bbmax }; diff = old_size - bbsz; } @@ -143,73 +145,79 @@ void Layer::optimize() LOG("optimized %d bytes", saved_bytes); } -void Layer::restore(const Snapshot& snap) +void Layer::restore(const Snapshot& snap, int frame /*= -1*/) { - clear({ 0, 0, 0, 0 }); + if (frame == -1) + frame = m_frame_index; + clear({ 0, 0, 0, 0 }, frame); for (int i = 0; i < 6; i++) { if (snap.image[i] == nullptr || snap.m_dirty_face[i] == false || box_area(snap.m_dirty_box[i]) <= 0) { - box(i) = glm::vec4(snap.width, snap.height, 0, 0); - face(i) = false; + box(i, frame) = glm::vec4(snap.width, snap.height, 0, 0); + face(i, frame) = false; continue; } - box(i) = snap.m_dirty_box[i]; - face(i) = snap.m_dirty_face[i]; + box(i, frame) = snap.m_dirty_box[i]; + face(i, frame) = snap.m_dirty_face[i]; // TODO: this should not be recreated here! // Sorry I messed up with this, // it's just a quick fix DON'T SHIP!! //m_rtt[i].recreate(); - App::I->render_task_async([this, i, &snap] + App::I->render_task_async([this, i, &snap, frame] { - rtt(i).bindTexture(); - glm::vec2 box_sz = zw(box(i)) - xy(box(i)); + rtt(i, frame).bindTexture(); + glm::vec2 box_sz = zw(box(i, frame)) - xy(box(i, frame)); glTexSubImage2D(GL_TEXTURE_2D, 0, - box(i).x, box(i).y, + box(i, frame).x, box(i, frame).y, box_sz.x, box_sz.y, GL_RGBA, GL_UNSIGNED_BYTE, snap.image[i].get()); - rtt(i).unbindTexture(); - LOG("restore face %d - %d bytes (%dx%d)", i, + rtt(i, frame).unbindTexture(); + LOG("restore frame %d face %d - %d bytes (%dx%d)", frame, i, (int)box_sz.x * (int)box_sz.y * 4, (int)box_sz.x, (int)box_sz.y); }); } App::I->render_sync(); } -Layer::Snapshot Layer::snapshot(std::array* dirty_box /*= nullptr*/, std::array* dirty_face /*= nullptr*/) +Layer::Snapshot Layer::snapshot(int frame /*= -1*/, std::array* dirty_box /*= nullptr*/, std::array* dirty_face /*= nullptr*/) { + if (frame == -1) + frame = m_frame_index; Snapshot snap; snap.width = w; snap.height = h; for (int i = 0; i < 6; i++) { - snap.m_dirty_box[i] = dirty_box ? dirty_box->at(i) : box(i); - snap.m_dirty_face[i] = dirty_face ? dirty_face->at(i) : face(i); + snap.m_dirty_box[i] = dirty_box ? dirty_box->at(i) : box(i, frame); + snap.m_dirty_face[i] = dirty_face ? dirty_face->at(i) : face(i, frame); if (!snap.m_dirty_face[i]) continue; - snap.image[i] = std::make_unique(rtt(i).bytes()); + snap.image[i] = std::make_unique(rtt(i, frame).bytes()); - App::I->render_task_async([this, i, &snap] + App::I->render_task_async([this, i, &snap, frame] { - rtt(i).bindFramebuffer(); + rtt(i, frame).bindFramebuffer(); glm::vec2 box_sz = zw(snap.m_dirty_box[i]) - xy(snap.m_dirty_box[i]); glReadPixels(snap.m_dirty_box[i].x, snap.m_dirty_box[i].y, box_sz.x, box_sz.y, GL_RGBA, GL_UNSIGNED_BYTE, snap.image[i].get()); - rtt(i).unbindFramebuffer(); + rtt(i, frame).unbindFramebuffer(); }); } App::I->render_sync(); return snap; } -void Layer::clear(const glm::vec4& c) +void Layer::clear(const glm::vec4& c, int frame /*= -1*/) { - frame().clear(c); + if (frame == -1) + frame = m_frame_index; + m_frames[frame].clear(c); } bool Layer::create(int width, int height, std::string name) @@ -218,9 +226,9 @@ bool Layer::create(int width, int height, std::string name) w = width; h = height; m_frame_index = 0; - if (m_frames.empty()) - m_frames.emplace_back(); - frame().create(width, height); + m_frames.clear(); + m_frames.emplace_back(); + m_frames.back().create(width, height); return true; } diff --git a/src/canvas_layer.h b/src/canvas_layer.h index 59173a1..7ff3c92 100644 --- a/src/canvas_layer.h +++ b/src/canvas_layer.h @@ -63,11 +63,11 @@ public: bool add_frame(); int total_duration() const noexcept; void goto_frame(int frame) noexcept; - void clear(const glm::vec4& c); - Snapshot snapshot(std::array* dirty_box = nullptr, std::array* dirty_face = nullptr); + void clear(const glm::vec4& c, int frame = -1); + Snapshot snapshot(int frame = -1, std::array* dirty_box = nullptr, std::array* dirty_face = nullptr); TextureCube gen_cube(); Texture2D gen_equirect(); - void restore(const Snapshot& snap); + void restore(const Snapshot& snap, int frame = -1); void destroy(); - void optimize(); + void optimize(int frame = -1); }; diff --git a/src/node_panel_animation.cpp b/src/node_panel_animation.cpp index 1cce12c..28519b8 100644 --- a/src/node_panel_animation.cpp +++ b/src/node_panel_animation.cpp @@ -37,21 +37,16 @@ void NodePanelAnimation::init_controls() Canvas::I->layer().add_frame(); load_layers(); }; - btn_up->on_click = [this](Node*) { - auto& layers = Canvas::I->m_layers; - auto layer = std::find_if(layers.begin(), layers.end(), - [id = m_selected_frame_layer_id](const auto& l) { return l->id == id; }); - if (layer != layers.end()) - (*layer)->m_frames[m_selected_frame_index].m_duration++; - }; btn_up->on_click = [this](Node*) { if (auto layer = Canvas::I->layer_with_id(m_selected_frame_layer_id)) - layer->m_frames[m_selected_frame_index].m_duration++; + layer->m_frames[m_selected_frame_index].m_duration = + glm::max(layer->m_frames[m_selected_frame_index].m_duration + 1, 1); load_layers(); }; btn_down->on_click = [this](Node*) { if (auto layer = Canvas::I->layer_with_id(m_selected_frame_layer_id)) - layer->m_frames[m_selected_frame_index].m_duration--; + layer->m_frames[m_selected_frame_index].m_duration = + glm::max(layer->m_frames[m_selected_frame_index].m_duration - 1, 1); load_layers(); }; @@ -85,9 +80,9 @@ void NodePanelAnimation::load_layers() } b->on_click = [this, fi, lid=layers[i]->id] (Node* target) { auto frame = static_cast(target); - frame->set_active(true); if (m_selected_frame) m_selected_frame->set_active(false); + frame->set_active(true); m_selected_frame = frame; m_selected_frame_layer_id = lid; m_selected_frame_index = fi; diff --git a/src/node_panel_brush.cpp b/src/node_panel_brush.cpp index 8794115..d729903 100644 --- a/src/node_panel_brush.cpp +++ b/src/node_panel_brush.cpp @@ -83,7 +83,7 @@ void NodePanelBrush::init() m_btn_add->on_click = [this](Node*) { App::I->pick_file({ "JPG", "PNG" }, [this](std::string path) { std::string name, base, ext; - std::regex r(R"((.*)[\\/]?([^\\/]+)\.(\w+)$)"); + std::regex r(R"((.*)[\\/]([^\\/]+)\.(\w+)$)"); std::smatch m; if (!std::regex_search(path, m, r)) return; @@ -708,7 +708,7 @@ bool NodePanelBrushPreset::export_ppbr(const std::string& path_in, const PPBRInf path += ".ppbr"; LOG("export ppbr to: %s", path.c_str()); - std::regex r(R"((.*)[\\/]?([^\\/]+)\.(\w+)?$)"); + std::regex r(R"((.*)[\\/]([^\\/]+)\.(\w+)?$)"); std::smatch m; if (!std::regex_search(path, m, r)) return false; @@ -972,7 +972,7 @@ bool NodePanelBrushPreset::import_abr(const std::string& path) LOG("ABR detected"); std::string name, base, ext; - std::regex r(R"((.*)[\\/]?([^\\/]+)\.(\w+)$)"); + std::regex r(R"((.*)[\\/]([^\\/]+)\.(\w+)$)"); std::smatch m; if (!std::regex_search(path, m, r)) return false; @@ -1042,7 +1042,7 @@ bool NodePanelBrushPreset::import_abr(const std::string& path) bool NodePanelBrushPreset::import_brush(const std::string& path) { - std::regex r(R"((.*)[\\/]?([^\\/]+)\.(\w+)$)"); + std::regex r(R"((.*)[\\/]([^\\/]+)\.(\w+)$)"); std::smatch m; if (!std::regex_search(path, m, r)) return false; diff --git a/src/node_panel_layer.cpp b/src/node_panel_layer.cpp index d37a06a..a8c484f 100644 --- a/src/node_panel_layer.cpp +++ b/src/node_panel_layer.cpp @@ -409,7 +409,7 @@ void NodePanelLayer::merge(int src_index, int dst_index, bool create_history) a->m_dirty_face[i] = Canvas::I->m_layers[dst_index]->face(i); } a->m_snap = std::make_shared(); - *a->m_snap = Canvas::I->m_layers[dst_index]->snapshot( + *a->m_snap = Canvas::I->m_layers[dst_index]->snapshot(-1, &Canvas::I->m_layers[src_index]->frame().m_dirty_box, &Canvas::I->m_layers[src_index]->frame().m_dirty_face); a->m_layer = Canvas::I->m_layers[src_index]; a->m_layer_node = std::static_pointer_cast(m_layers_container->m_children[src_index]); diff --git a/src/rtt.cpp b/src/rtt.cpp index 3a5b8ed..ae8ec62 100644 --- a/src/rtt.cpp +++ b/src/rtt.cpp @@ -93,12 +93,7 @@ bool RTT::resize(int width, int height) oldRFboID = 0; oldDFboID = 0; - fboID = new_rtt.fboID; - rboID = new_rtt.rboID; - texID = new_rtt.texID; - int_fmt = new_rtt.int_fmt; - w = new_rtt.w; - h = new_rtt.h; + *this = std::move(new_rtt); return true; }