#include "./send_image_popup.hpp" #include "./image_loader_sdl_bmp.hpp" #include "./image_loader_stb.hpp" #include "./image_loader_webp.hpp" #include "./image_loader_qoi.hpp" #include // fwd namespace Message { uint64_t getTimeMS(void); } SendImagePopup::SendImagePopup(TextureUploaderI& tu) : _tu(tu) { _image_loaders.push_back(std::make_unique()); _image_loaders.push_back(std::make_unique()); _image_loaders.push_back(std::make_unique()); _image_loaders.push_back(std::make_unique()); } void SendImagePopup::reset(void) { _on_send = [](const auto&, auto){}; _on_cancel = [](){}; // hm original_data.clear(); { original_image.width = 0; original_image.height = 0; original_image.frames.clear(); } // clear preview img for (const auto& tex_id : preview_image.textures) { _tu.destroy(tex_id); } preview_image = {}; cropping = false; dragging_last_frame_ul = false; dragging_last_frame_lr = false; } bool SendImagePopup::load(void) { // try all loaders after another for (auto& il : _image_loaders) { original_image = il->loadFromMemoryRGBA(original_data.data(), original_data.size()); if (original_image.frames.empty() || original_image.height == 0 || original_image.width == 0) { continue; } #if 1 crop_rect.x = 0; crop_rect.y = 0; crop_rect.w = original_image.width; crop_rect.h = original_image.height; #else crop_rect.x = original_image.width * 0.05f; crop_rect.y = original_image.height * 0.05f; crop_rect.w = original_image.width * 0.9f; crop_rect.h = original_image.height * 0.9f; #endif crop_rect = sanitizeCrop(crop_rect, original_image.width, original_image.height); crop_before_drag = crop_rect; original_file_ext = "."; if (original_image.file_ext != nullptr) { original_file_ext += original_image.file_ext; } else { // HACK: manually probe for png if (!original_raw && original_data.size() >= 4 && original_data.at(0) == 0x89 && original_data.at(1) == 'P' && original_data.at(2) == 'N' && original_data.at(3) == 'G' ) { original_file_ext += "png"; } else { original_file_ext += "unk"; // very meh, default to png? } } assert(preview_image.textures.empty()); preview_image.timestamp_last_rendered = Message::getTimeMS(); preview_image.current_texture = 0; for (const auto& [ms, data] : original_image.frames) { const auto n_t = _tu.uploadRGBA(data.data(), original_image.width, original_image.height); preview_image.textures.push_back(n_t); preview_image.frame_duration.push_back(ms); } // redundant preview_image.width = original_image.width; preview_image.height = original_image.height; if (original_image.frames.size() > 1) { std::cout << "SIP: loaded animation\n"; } else { std::cout << "SIP: loaded image\n"; } return true; } return false; } SendImagePopup::Rect SendImagePopup::sanitizeCrop(Rect crop_rect, int32_t image_width, int32_t image_height) { // w and h min is 1 -> x/y need to be smaller so the img is atleast 1px in any dim if (crop_rect.x >= image_width-1) { crop_rect.x = image_width-2; } else if (crop_rect.x < 0) { crop_rect.x = 0; } if (crop_rect.y >= image_height-1) { crop_rect.y = image_height-2; } else if (crop_rect.y < 0) { crop_rect.y = 0; } if (crop_rect.w > image_width - crop_rect.x) { crop_rect.w = image_width - crop_rect.x; } else if (crop_rect.w < 1) { crop_rect.w = 1; // TODO: adjust X } if (crop_rect.h > image_height - crop_rect.y) { crop_rect.h = image_height - crop_rect.y; } else if (crop_rect.h < 1) { crop_rect.h = 1; // TODO: adjust Y } return crop_rect; } void SendImagePopup::sendMemory( const uint8_t* data, size_t data_size, std::function&, std::string_view)>&& on_send, std::function&& on_cancel ) { original_raw = false; if (data == nullptr || data_size == 0) { return; // error } // copy paste data to memory original_data.clear(); original_data.insert(original_data.begin(), data, data+data_size); if (!load()) { std::cerr << "SIP: failed to load image from memory\n"; reset(); return; } _open_popup = true; _on_send = std::move(on_send); _on_cancel = std::move(on_cancel); } void SendImagePopup::render(float time_delta) { if (_open_popup) { _open_popup = false; ImGui::OpenPopup("send image##SendImagePopup"); } // TODO: add cancel shortcut (esc) if (ImGui::BeginPopupModal("send image##SendImagePopup", nullptr/*, ImGuiWindowFlags_NoDecoration*/)) { //const auto TEXT_BASE_WIDTH = ImGui::CalcTextSize("A").x; const auto TEXT_BASE_HEIGHT = ImGui::GetTextLineHeightWithSpacing(); preview_image.doAnimation(Message::getTimeMS()); time += time_delta; time = fmod(time, 1.f); // fract() //ImGui::Text("send file....\n......"); { float width = ImGui::GetWindowContentRegionMax().x - ImGui::GetWindowContentRegionMin().x; float height = crop_rect.h * (width / crop_rect.w); if (cropping) { height = original_image.height * (width / original_image.width); } const float max_height = ImGui::GetWindowContentRegionMax().y - ( ImGui::GetWindowContentRegionMin().y + TEXT_BASE_HEIGHT*(2-1) // row of buttons (-1 bc fh inclues fontsize) + ImGui::GetFrameHeightWithSpacing()*4 ) ; if (height > max_height) { width *= max_height / height; height = max_height; } // TODO: propergate texture id type // save curser pos const auto pre_img_curser_screen = ImGui::GetCursorScreenPos(); const auto pre_img_curser = ImGui::GetCursorPos(); if (cropping) { // if cropping // display full image ImGui::Image( preview_image.getID(), ImVec2{static_cast(width), static_cast(height)} ); const auto post_img_curser = ImGui::GetCursorPos(); ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{0.5f, 0.5f, 0.5f, 0.2f}); ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4{0.5f, 0.5f, 0.5f, 0.4f}); auto ul_clipper_pos = ImVec2{float(crop_rect.x)/original_image.width, float(crop_rect.y)/original_image.height}; { // crop upper left clipper ImGui::SetCursorPos({ pre_img_curser.x + ul_clipper_pos.x * width, pre_img_curser.y + ul_clipper_pos.y * height }); ImGui::Button("##ul_clipper", {TEXT_BASE_HEIGHT, TEXT_BASE_HEIGHT}); if (ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { if (dragging_last_frame_ul) { auto drag_total = ImGui::GetMouseDragDelta(); drag_total.x = (drag_total.x / width) * original_image.width; drag_total.y = (drag_total.y / height) * original_image.height; crop_rect.x = std::max(crop_before_drag.x + drag_total.x, 0.01f); crop_rect.y = std::max(crop_before_drag.y + drag_total.y, 0.01f); crop_rect.x = std::min(crop_rect.x, original_image.width-2); crop_rect.y = std::min(crop_rect.y, original_image.height-2); crop_rect.w = crop_before_drag.w - (crop_rect.x - crop_before_drag.x); crop_rect.h = crop_before_drag.h - (crop_rect.y - crop_before_drag.y); } else { if (ImGui::IsItemActive()) { dragging_last_frame_ul = true; // drag started on button, start drag } } } else if (dragging_last_frame_ul) { // was dragging dragging_last_frame_ul = false; crop_before_drag = crop_rect; } } auto lr_clipper_pos = ImVec2{float(crop_rect.x+crop_rect.w)/original_image.width, float(crop_rect.y+crop_rect.h)/original_image.height}; { // crop lower right clipper ImGui::SetCursorPos({ pre_img_curser.x + lr_clipper_pos.x * width - TEXT_BASE_HEIGHT, pre_img_curser.y + lr_clipper_pos.y * height - TEXT_BASE_HEIGHT }); ImGui::Button("##lr_clipper", {TEXT_BASE_HEIGHT, TEXT_BASE_HEIGHT}); if (ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { if (dragging_last_frame_lr) { auto drag_total = ImGui::GetMouseDragDelta(); drag_total.x = (drag_total.x / width) * original_image.width; drag_total.y = (drag_total.y / height) * original_image.height; crop_rect.w = std::min(crop_before_drag.w + drag_total.x, original_image.width); crop_rect.h = std::min(crop_before_drag.h + drag_total.y, original_image.height); } else { if (ImGui::IsItemActive()) { dragging_last_frame_lr = true; // drag started on button, start drag } } } else if (dragging_last_frame_lr) { // was dragging dragging_last_frame_lr = false; crop_before_drag = crop_rect; } } // sanitzie after tool crop_rect = sanitizeCrop(crop_rect, original_image.width, original_image.height); { // 4 lines delimiting the crop result ImU32 line_color = 0xffffffff; { // calc color auto rgb = [](float x) -> ImVec4 { auto f = [](float x) { while (x < 0.f) { x += 1.f; } x = std::fmod(x, 1.f); // fract() if (x < 1.f/3) { return x * 3; } else if (x < 2.f/3) { return (1 - (x - (1.f/3))) * 3 - 2; } else { return 0.f; } }; float red = f(x); float green = f(x - (1.f/3)); float blue = f(x - (2.f/3)); return {red, green, blue, 1.f}; }; line_color = ImGui::GetColorU32(rgb(time)); } // x vertical ImGui::GetWindowDrawList()->AddLine( {pre_img_curser_screen.x + ul_clipper_pos.x * width, pre_img_curser_screen.y}, {pre_img_curser_screen.x + ul_clipper_pos.x * width, pre_img_curser_screen.y + height}, line_color, 1.f ); // y horizontal ImGui::GetWindowDrawList()->AddLine( {pre_img_curser_screen.x, pre_img_curser_screen.y + ul_clipper_pos.y * height}, {pre_img_curser_screen.x + width, pre_img_curser_screen.y + ul_clipper_pos.y * height}, line_color, 1.f ); // w vertical ImGui::GetWindowDrawList()->AddLine( {pre_img_curser_screen.x + lr_clipper_pos.x * width, pre_img_curser_screen.y}, {pre_img_curser_screen.x + lr_clipper_pos.x * width, pre_img_curser_screen.y + height}, line_color, 1.f ); // h horizontal ImGui::GetWindowDrawList()->AddLine( {pre_img_curser_screen.x, pre_img_curser_screen.y + lr_clipper_pos.y * height}, {pre_img_curser_screen.x + width, pre_img_curser_screen.y + lr_clipper_pos.y * height}, line_color, 1.f ); } // cancel/ok buttons in the img center? ImGui::PopStyleColor(2); ImGui::SetCursorPos(post_img_curser); } else { crop_rect = sanitizeCrop(crop_rect, original_image.width, original_image.height); // display cropped area ImGui::Image( preview_image.getID(), ImVec2{static_cast(width), static_cast(height)}, ImVec2{float(crop_rect.x)/original_image.width, float(crop_rect.y)/original_image.height}, ImVec2{float(crop_rect.x+crop_rect.w)/original_image.width, float(crop_rect.y+crop_rect.h)/original_image.height} ); // transparent crop button on image #if 0 const auto post_img_curser = ImGui::GetCursorPos(); ImGui::SetCursorPos(pre_img_curser); // TODO: fancy cropping toggle ImGui::PushStyleColor(ImGuiCol_Button, ImVec4{0.5f, 0.5f, 0.5f, 0.2f}); if (ImGui::Button("crop", {TEXT_BASE_WIDTH*8, TEXT_BASE_HEIGHT*2})) { cropping = true; } ImGui::PopStyleColor(); ImGui::SetCursorPos(post_img_curser); #endif } } const bool cropped = crop_rect.x != 0 || crop_rect.y != 0 || crop_rect.w != original_image.width || crop_rect.h != original_image.height; if (cropping) { if (ImGui::Button("done")) { cropping = false; } } else { if (ImGui::Button("crop")) { cropping = true; } } ImGui::SameLine(); if (ImGui::Button("reset")) { crop_rect.x = 0; crop_rect.y = 0; crop_rect.w = original_image.width; crop_rect.h = original_image.height; crop_before_drag = crop_rect; } ImGui::SameLine(); ImGui::Text("x:%d y:%d w:%d h:%d", crop_rect.x, crop_rect.y, crop_rect.w, crop_rect.h); bool recalc_size = false; if (cropped) { if (!compress) { // looks like a change recalc_size = true; } compress = true; } recalc_size |= ImGui::Checkbox("compress", &compress); if (cropped && ImGui::IsItemHovered()) { ImGui::SetTooltip("required since cropped!"); } static int current_compressor = 0; if (compress) { ImGui::SameLine(); ImGui::Combo("##compression_type", ¤t_compressor, "webp\0jpeg\0png\0qoi\0"); ImGui::Indent(); // combo "webp""webp-lossless""png""jpg?" // if lossy quality slider (1-100) default 80 if (current_compressor == 0 || current_compressor == 1) { const uint32_t qmin = 1; const uint32_t qmax = 100; recalc_size |= ImGui::SliderScalar("quality", ImGuiDataType_U32, &quality, &qmin, &qmax); } if (current_compressor == 2) { const uint32_t qmin = 0; const uint32_t qmax = 9; recalc_size |= ImGui::SliderScalar("compression_level", ImGuiDataType_U32, &compression_level, &qmin, &qmax); } if (recalc_size) { // compress and save temp? cooldown? async? save size only? // print size where? } ImGui::Unindent(); } //if (ImGui::Button("X cancel", {ImGui::GetWindowContentRegionWidth()/2.f, TEXT_BASE_HEIGHT*2})) { if (ImGui::Button("X cancel", {ImGui::GetContentRegionAvail().x/2.f, TEXT_BASE_HEIGHT*2})) { _on_cancel(); ImGui::CloseCurrentPopup(); reset(); } ImGui::SameLine(); if (ImGui::Button("send ->", {-FLT_MIN, TEXT_BASE_HEIGHT*2})) { if (compress || cropped) { // TODO: copy bad ImageLoaderI::ImageResult tmp_img; if (cropped) { std::cout << "SIP: CROP!!!!!\n"; tmp_img = original_image.crop( crop_rect.x, crop_rect.y, crop_rect.w, crop_rect.h ); } else { tmp_img = original_image; } std::vector new_data; // HACK: generic list if (current_compressor == 0) { new_data = ImageEncoderWebP{}.encodeToMemoryRGBA(tmp_img, {{"quality", quality}}); if (!new_data.empty()) { _on_send(new_data, ".webp"); } } else if (current_compressor == 1) { new_data = ImageEncoderSTBJpeg{}.encodeToMemoryRGBA(tmp_img, {{"quality", quality}});; if (!new_data.empty()) { _on_send(new_data, ".jpg"); } } else if (current_compressor == 2) { new_data = ImageEncoderSTBPNG{}.encodeToMemoryRGBA(tmp_img, {{"png_compression_level", compression_level}});; if (!new_data.empty()) { _on_send(new_data, ".png"); } } else if (current_compressor == 3) { new_data = ImageEncoderQOI{}.encodeToMemoryRGBA(tmp_img, {});; if (!new_data.empty()) { _on_send(new_data, ".qoi"); } } // error } else { _on_send(original_data, original_file_ext); } ImGui::CloseCurrentPopup(); reset(); } ImGui::EndPopup(); } }