#pragma once #include "rtt.h" #include "texture.h" #include "shader.h" #include "shape.h" #include "brush.h" #include "canvas_layer.h" #include "canvas_actions.h" #include "canvas_modes.h" #include #include "mp4enc.h" #if __WEB__ #define CANVAS_RES 512 #else #define CANVAS_RES 1536 #endif struct PPIThumb { int width = 128; int height = 128; int comp = 4; bool valid() const { return (width == 128 && height == 128 && comp == 4); } }; struct PPIDocVersion { int major = 0; // version 1: initial // version 2: added blend mode, alpha and visibility // version 3: added animation frames // version 4: (released in 0.2.3) add info struct int minor = 4; }; struct PPISoftVersion { int major = g_version_major; int minor = g_version_minor; int fix = g_version_fix; int build = g_version_build; }; struct PPIHeader { char magic[4]{ 'P', 'P', 'I', 0 }; PPIDocVersion doc_version; PPISoftVersion soft_version; PPIThumb thumb_header; bool valid() { if (strcmp(magic, "PPI") != 0) return false; if (doc_version.major != 0 || (doc_version.minor < 1 && doc_version.minor > 2)) return false; if (!thumb_header.valid()) return false; return true; } }; class Canvas { struct StrokeFrame { glm::vec4 col; float flow; float opacity; std::array, 6> shapes; glm::vec4 m_mixer_rect; }; public: struct FloodData { std::shared_ptr layer; std::unique_ptr mask[6]; std::unique_ptr rgb[6] = SIXPLETTE(0); std::array dirty = SIXPLETTE(false); glm::vec4 bb[6]; void apply(); }; Plane m_plane; Plane m_plane_brush; DynamicShape m_brush_shape; bool m_unsaved = false; bool m_newdoc = true; bool m_dirty = false; bool m_commit_delayed = false; bool m_dirty_stroke = false; std::unique_ptr m_encoder; std::chrono::time_point m_disrty_stroke_time; std::stack m_camera_stack; static Canvas* I; NodeCanvas* m_node = nullptr; bool m_alpha_lock = false; bool m_touch_lock = false; glm::mat4 m_mv{ 1 }; glm::mat4 m_proj{ 1 }; glm::vec4 m_box{ 0 }; glm::vec4 m_vp{ 0 }; glm::vec2 m_pan{ 0 }; glm::vec2 m_size{ 0 }; int m_width = 0; int m_height = 0; bool m_use_instanced = false; int m_current_layer_idx = 0; std::unique_ptr m_current_stroke; std::unique_ptr m_dual_stroke; bool m_show_tmp = false; std::vector> m_layers; int m_anim_frame = 0; Layer m_layers_merge; std::vector m_plane_shape[6]; // screen space projection of the plane glm::mat4 m_plane_unproject[6] = SIXPLETTE(glm::mat4(1)); glm::vec3 m_plane_dir[6] = SIXPLETTE(glm::vec3(0)); glm::vec4 m_dirty_box[6] = SIXPLETTE(glm::vec4(0)); bool m_dirty_face[6] = SIXPLETTE(false); Layer m_smask; // selection mask bool m_smask_active = false; // mode 0=none, 1=free, 2=line int m_smask_mode = 0; RTT m_tmp[6]; RTT m_tmp_dual[6]; RTT m_mixer; float m_mixer_scale = 1; Texture2D m_tex[6]; Texture2D m_tex2[6]; RTT m_merge_rtt; Texture2D m_merge_tex; bool m_pick_ready[6] = SIXPLETTE(false); std::unique_ptr m_pick_data[6] = SIXPLETTE(nullptr); static glm::vec3 m_plane_origin[6]; static glm::vec3 m_plane_normal[6]; static glm::vec3 m_plane_tangent[6]; static glm::mat4 m_plane_transform[6]; glm::vec2 m_pattern_offset{ 0 }; Sampler m_sampler; Sampler m_sampler_nearest; Sampler m_sampler_linear; Sampler m_sampler_brush; Sampler m_sampler_stencil; Sampler m_sampler_mix; glm::mat4 m_cam_rot = glm::mat4(1); glm::vec3 m_cam_pos{ 0 }; float m_cam_fov = 85.f; const float m_cam_fov_min = 5.f; const float m_cam_fov_max = 150.f; glm::vec2 m_cur_pos{ 0 }; std::shared_ptr m_current_brush; static std::vector modes[]; std::vector* m_mode = nullptr; kCanvasMode m_current_mode = kCanvasMode::Draw; std::function on_mode_changed; static void set_mode(kCanvasMode mode) { auto prev = I->m_current_mode; if (I->m_mode) for (auto& m : *I->m_mode) m->leave(mode); I->m_mode = &modes[(int)mode]; I->m_current_mode = mode; if (I->m_mode) for (auto& m : *I->m_mode) m->enter(prev); if (I->on_mode_changed) I->on_mode_changed(prev, mode); } template static T* get_mode(int index = 0) { return dynamic_cast(modes[(int)I->m_current_mode][index]); } std::vector m_layers_snapshot; Canvas() { I = this; } ~Canvas() { destroy(); } void destroy(); bool create(int width, int height); void resize(int width, int height); Layer& layer() { return *m_layers[m_current_layer_idx]; } std::shared_ptr layer_with_id(uint32_t id); void layer_remove(int idx); void layer_add(std::string name, std::shared_ptr layer = nullptr, int index = 0); void layer_order(int idx, int pos); void layer_merge(int source_idx, int dest_idx); int anim_duration() const noexcept; void anim_update() noexcept; void anim_goto_frame(int frame) noexcept; void anim_goto_next() noexcept; void anim_goto_prev() noexcept; void flood_fill(int layer, int plane, std::vector pos, FloodData& plane_data, float threshold, glm::vec4 dest_color, std::unique_ptr& source_color); void stroke_start(glm::vec3 point, float pressure); void stroke_update(glm::vec3 point, float pressure); void stroke_draw_mix(const glm::vec2& bb_min, const glm::vec2& bb_sz); std::array, 6> stroke_draw_project(std::array& B, bool project_3d = false, glm::mat4 mv = glm::mat4(1)) const; // return rect {origin, size} glm::vec4 stroke_draw_samples(int i, std::vector& P, bool copy_stroke_destination); std::vector stroke_draw_compute(Stroke& stroke) const; void stroke_draw(); void stroke_end(); void stroke_cancel(); void stroke_commit(); void stroke_commit_timelapse(); void draw_merge(bool draw_checkerboard, std::array faces = SIXPLETTE(true)); void clear(const glm::vec4& color = { 1, 1, 1, 0 }); void clear_all(); void pick_start(); void pick_update(int plane); glm::vec4 pick_get(glm::vec2 canvas_loc); void pick_end(); void snapshot_save(); void snapshot_restore(); void snap_history(const std::vector& planes); void clear_context(); void import_equirectangular(std::string file_path, std::shared_ptr layer = nullptr); void import_equirectangular_thread(std::string file_path, std::shared_ptr layer = nullptr, int frame = -1); void export_equirectangular(std::string file_path, std::function on_complete = nullptr); void export_equirectangular_thread(std::string file_path); void export_layers(std::string path, std::function on_complete = nullptr); void export_layers_thread(std::string path); void export_anim_frames(std::string path, std::function on_complete = nullptr); void export_anim_frames_thread(std::string path); void export_anim_mp4(std::string path, std::function on_complete = nullptr); void export_anim_mp4_thread(std::string path); void export_depth(std::string file_name, std::function on_complete = nullptr); void export_depth_thread(std::string file_name); void export_cube_faces(std::string file_name, std::function on_complete); void export_cube_faces_thread(std::string file_name); void project_save(std::function on_complete = nullptr); void project_save(std::string file_path, std::function on_complete = nullptr); bool project_save_thread(std::string file_path, bool show_progress); void project_open(std::string file_path, std::function on_complete = nullptr); bool project_open_thread(std::string file_path); void inject_xmp(std::string jpg_path); Image thumbnail_generate(int w, int h); Image thumbnail_read(std::string file_path); void draw_objects(std::function, int frame, bool save_history); void draw_objects(std::function, Layer& layer, int frame, bool save_history); void draw_objects_direct(std::function, Layer& layer, int frame); void point_unproject(glm::vec2 loc, glm::vec4 vp, glm::mat4 camera, glm::mat4 proj, glm::vec3 &out_origin, glm::vec3 &out_dir); void point_unproject(glm::vec2 loc, glm::vec3 &out_origin, glm::vec3 &out_dir); glm::vec3 point_trace(glm::vec2 loc); bool point_trace(glm::vec2 loc, glm::vec3& ray_origin, glm::vec3& ray_dir, glm::vec3& hit_pos, glm::vec2& fb_pos, glm::vec3& hit_normal, int& out_plane_id); bool point_trace_plane(glm::vec2 loc, glm::vec3& ray_origin, glm::vec3& ray_dir, glm::vec3& hit_pos, glm::vec3& hit_normal, glm::vec2& hit_fb_pos, int plane_id); void project2Dpoints(std::vector& vertices); glm::vec3 project2Dpoint(glm::vec2 pt); std::vector face_to_shape2D(int plane_index); void push_camera(); void pop_camera(); CameraData get_camera(); void set_camera(const CameraData& c); void timelapse_reset_encoder() noexcept; };