diff --git a/src/libslic3r/PNGReadWrite.cpp b/src/libslic3r/PNGReadWrite.cpp index 1f29de646b..40f88a9447 100644 --- a/src/libslic3r/PNGReadWrite.cpp +++ b/src/libslic3r/PNGReadWrite.cpp @@ -211,6 +211,35 @@ bool decode_colored_png(const ReadBuf &in_buf, ImageColorscale &out_img) return decode_colored_png(stream, out_img); } +bool is_decodable_8bit_png(const ReadBuf &pngbuf) +{ + static const constexpr int PNG_SIG_BYTES = 8; + + struct ReadBufStream stream{pngbuf}; + std::vector sig(PNG_SIG_BYTES, 0); + stream.read(sig.data(), PNG_SIG_BYTES); + if (!png_check_sig(sig.data(), PNG_SIG_BYTES)) + return false; + + PNGDescr dsc; + dsc.png = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!dsc.png) + return false; + dsc.info = png_create_info_struct(dsc.png); + if (!dsc.info) + return false; + + if (!png_read_header_guarded(dsc.png, dsc.info, &stream, PNG_SIG_BYTES)) + return false; + + const size_t color_type = png_get_color_type(dsc.png, dsc.info); + const size_t bit_depth = png_get_bit_depth(dsc.png, dsc.info); + const bool grey = color_type == PNG_COLOR_TYPE_GRAY; + const bool colour = color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA; + return png_get_image_width(dsc.png, dsc.info) > 0 && png_get_image_height(dsc.png, dsc.info) > 0 && + bit_depth == 8 && (grey || colour); +} + // Down to earth function to store a packed RGB image to file. Mostly useful for debugging purposes. // Based on https://www.lemoda.net/c/write-png/ diff --git a/src/libslic3r/PNGReadWrite.hpp b/src/libslic3r/PNGReadWrite.hpp index c2d2149e1f..6b8ed75cae 100644 --- a/src/libslic3r/PNGReadWrite.hpp +++ b/src/libslic3r/PNGReadWrite.hpp @@ -50,6 +50,9 @@ struct ReadBuf { const void *buf = nullptr; const size_t sz = 0; }; bool is_png(const ReadBuf &pngbuf); +// True if decode_png() or decode_colored_png() would accept these bytes, judged from the header alone. +bool is_decodable_8bit_png(const ReadBuf &pngbuf); + struct ReadBufStream: public IStream { const ReadBuf &rbuf_ref; size_t pos = 0; diff --git a/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.cpp b/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.cpp index 4597e98fd2..be1f72e520 100644 --- a/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.cpp +++ b/src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.cpp @@ -174,6 +174,10 @@ constexpr int HEIGHT_TEX_MAX_PX = 2048; // The full-size image is only ever adjusted once, on commit. constexpr int ADJUST_PREVIEW_MAX_PX = 384; +// Library textures the texture picker decodes per frame. Each decode runs on the GUI thread, so the +// rest of the visible tiles fill in over the following frames instead of in one stall. +constexpr int PICKER_LOADS_PER_FRAME = 2; + // How many rings of vertex-adjacent triangles either side of the paint's edge join the border refine // band (see collect_paint_region()). Two is enough to grade the size change without the band's own // cost growing to matter: it is a ring around a perimeter, not an area. @@ -4331,6 +4335,8 @@ void GLGizmoTextureDisplacement::render_texture_library_popup(const ImVec2 &pane std::optional to_remove; bool import_clicked = false; bool download_clicked = false; + int loads_left = PICKER_LOADS_PER_FRAME; + bool loads_deferred = false; // Tiles are placed on the four-column grid explicitly rather than flowed with SameLine(): a user tile // overlays a remove button on its corner, and that button would otherwise be "the previous item" the @@ -4348,11 +4354,30 @@ void GLGizmoTextureDisplacement::render_texture_library_popup(const ImVec2 &pane }; // One section's tiles, in the order the section holds them (shipped alphabetically, pack textures by // name, the user's own most recently used first - the order texture_library() produced); a user tile - // carries a remove button on hover. Returns how many it drew. + // carries a remove button on hover. Returns how many tiles it laid out, drawn or not. const auto grid = [&](const ImVec2 &origin, const std::vector &entries, bool removable) { int n = 0; for (const TextureLibraryEntry *entry : entries) { - ImGui::SetCursorScreenPos(tile_pos(origin, n++)); + const ImVec2 rect_min = tile_pos(origin, n++); + const ImVec2 rect_max = ImVec2(rect_min.x + tile, rect_min.y + tile); + const bool current = entry->path == layer->path; + // The selected tile is centred even when it is off screen, so it is drawn and loaded next frame. + if (current && m_picker_reveal_selected) { + ImGui::SetScrollFromPosY(0.5f * (rect_min.y + rect_max.y) - ImGui::GetWindowPos().y, 0.5f); + m_parent.set_as_dirty(); + } + if (!ImGui::IsRectVisible(rect_min, rect_max)) + continue; + // Thumbnails decode on this thread, so only a few per frame; the rest fill in on later frames. + const bool cached = m_library_textures.count(entry->path) != 0; + if (!cached && loads_left == 0) { + loads_deferred = true; + dl->AddRectFilled(rect_min, rect_max, ImGui::GetColorU32(ImGuiCol_FrameBg), style.FrameRounding); + continue; + } + if (!cached) + --loads_left; + ImGui::SetCursorScreenPos(rect_min); ImGui::PushID(entry->path.c_str()); const LibraryTexture *tex = get_library_texture(entry->path); GLTexture *thumb = tex != nullptr ? tex->thumbnail.get() : nullptr; @@ -4375,7 +4400,6 @@ void GLGizmoTextureDisplacement::render_texture_library_popup(const ImVec2 &pane if (removable) ImGui::SetItemAllowOverlap(); // the remove button drawn over its corner takes its own clicks const ImVec2 a = ImGui::GetItemRectMin(), b = ImGui::GetItemRectMax(); - const bool current = entry->path == layer->path; const bool hovered = ImGui::IsItemHovered(); // The layer's current texture carries an accent ring, and the tile under the pointer a thinner, // fainter one. Both are drawn inside the tile, where the grid's clip rect never cuts them. @@ -4384,9 +4408,6 @@ void GLGizmoTextureDisplacement::render_texture_library_popup(const ImVec2 &pane dl->AddRect(ImVec2(a.x + 0.5f * w, a.y + 0.5f * w), ImVec2(b.x - 0.5f * w, b.y - 0.5f * w), ImGui::GetColorU32(current ? orca : ImVec4(orca.x, orca.y, orca.z, 0.6f)), 0.f, 0, w); } - // Centred, as far as the scroll range allows, so the textures around it are in view too. - if (current && m_picker_reveal_selected) - ImGui::SetScrollFromPosY(0.5f * (a.y + b.y) - ImGui::GetWindowPos().y, 0.5f); if (hovered) panel_tooltip(entry->name); @@ -4575,6 +4596,8 @@ void GLGizmoTextureDisplacement::render_texture_library_popup(const ImVec2 &pane ImGui::TextDisabled("%s", _u8L("PNG, JPG or BMP - converted to a height map on import. Hover one of yours to remove it.").c_str()); ImGui::PopTextWrapPos(); } + if (loads_deferred) + m_parent.set_as_dirty(); // Done whether or not the current texture was drawn: one in a collapsed section stays where it is. m_picker_reveal_selected = false; ImGui::EndChild(); diff --git a/src/slic3r/GUI/TextureLibrary.cpp b/src/slic3r/GUI/TextureLibrary.cpp index 0388ed4f21..3d76791027 100644 --- a/src/slic3r/GUI/TextureLibrary.cpp +++ b/src/slic3r/GUI/TextureLibrary.cpp @@ -223,23 +223,11 @@ std::vector read_file_bytes(const std::string &path) return std::vector(std::istreambuf_iterator(ifs), std::istreambuf_iterator()); } -// True if these bytes are a PNG libslic3r can decode, i.e. can be stored on a layer as-is. Mirrors -// exactly what decode_height_texture() accepts: 8-bit grayscale, or 8-bit colour (whose luminance is -// the height and whose RGB is available to colour the model). Anything else is converted on load. +// True if decode_height_texture() can read these bytes, so they can be stored on a layer as-is. +// Anything else is converted on load. bool is_supported_height_map(const std::vector &bytes) { - if (bytes.empty()) - return false; - const png::ReadBuf rbuf{ bytes.data(), bytes.size() }; - if (!png::is_png(rbuf)) - return false; - png::ImageGreyscale gray; - if (png::decode_png(rbuf, gray) && gray.cols > 0 && gray.rows > 0) - return true; - png::ImageColorscale color; - return png::decode_colored_png(rbuf, color) && color.cols > 0 && color.rows > 0 && - color.bytes_per_pixel >= 3 && - color.buf.size() == color.cols * color.rows * size_t(color.bytes_per_pixel); + return !bytes.empty() && png::is_decodable_8bit_png(png::ReadBuf{ bytes.data(), bytes.size() }); } std::vector g_library;