Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Thumbnail Formats Option Ported from PrusaSlicer and add BIQU/BTT format #2405

Merged
merged 7 commits into from
Oct 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ add_subdirectory(semver)
add_subdirectory(libigl)
add_subdirectory(hints)
add_subdirectory(mcut)
add_subdirectory(qoi)

# Adding libnest2d project for bin packing...
add_subdirectory(libnest2d)
Expand Down
6 changes: 6 additions & 0 deletions src/libslic3r/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,8 @@ set(lisbslic3r_sources
Shape/TextShape.cpp
calib.hpp
calib.cpp
GCode/Thumbnails.cpp
GCode/Thumbnails.hpp
)

if (APPLE)
Expand Down Expand Up @@ -473,6 +475,8 @@ set(OpenCASCADE_DIR "${CMAKE_PREFIX_PATH}/lib/cmake/occt")
find_package(OpenCASCADE REQUIRED)
target_include_directories(libslic3r PUBLIC ${OpenCASCADE_INCLUDE_DIR})

find_package(JPEG REQUIRED)

set(OCCT_LIBS
TKXDESTEP
TKSTEP
Expand Down Expand Up @@ -525,6 +529,8 @@ target_link_libraries(libslic3r
${OCCT_LIBS}
Clipper2
mcut
JPEG::JPEG
qoi
)

if(NOT WIN32)
Expand Down
64 changes: 16 additions & 48 deletions src/libslic3r/GCode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "EdgeGrid.hpp"
#include "Geometry/ConvexHull.hpp"
#include "GCode/PrintExtents.hpp"
#include "GCode/Thumbnails.hpp"
#include "GCode/WipeTower.hpp"
#include "ShortestPath.hpp"
#include "Print.hpp"
Expand Down Expand Up @@ -1663,50 +1664,6 @@ namespace DoExport {
}
}

//BBS: add plate id for thumbnail generate param
Ocraftyone marked this conversation as resolved.
Show resolved Hide resolved
template<typename WriteToOutput, typename ThrowIfCanceledCallback>
static void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb, int plate_id, const std::vector<Vec2d> &sizes, WriteToOutput output, ThrowIfCanceledCallback throw_if_canceled)
{
// Write thumbnails using base64 encoding
if (thumbnail_cb != nullptr)
{
const size_t max_row_length = 78;
//BBS: add plate id for thumbnail generate param
ThumbnailsList thumbnails = thumbnail_cb(ThumbnailsParams{ sizes, true, true, true, true, plate_id });
for (const ThumbnailData& data : thumbnails)
{
if (data.is_valid())
{
size_t png_size = 0;
void* png_data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &png_size, MZ_DEFAULT_LEVEL, 1);
if (png_data != nullptr)
{
std::string encoded;
encoded.resize(boost::beast::detail::base64::encoded_size(png_size));
encoded.resize(boost::beast::detail::base64::encode((void*)&encoded[0], (const void*)png_data, png_size));

output("; THUMBNAIL_BLOCK_START\n");
output((boost::format("; thumbnail begin %dx%d %d\n") % data.width % data.height % encoded.size()).str().c_str());

unsigned int row_count = 0;
//BBS: optimize performance ,reduce too much memeory operation
size_t current_index = 0;
while(current_index<encoded.size()){
output((boost::format("; %s\n") % encoded.substr(current_index, max_row_length)).str().c_str());
current_index+=std::min(max_row_length,encoded.size()-current_index);
++row_count;
}
output("; thumbnail end\n");
output("; THUMBNAIL_BLOCK_END\n\n");

mz_free(png_data);
}
}
throw_if_canceled();
}
}
}

// Fill in print_statistics and return formatted string containing filament statistics to be inserted into G-code comment section.
static std::string update_print_stats_and_format_filament_stats(
const bool has_wipe_tower,
Expand Down Expand Up @@ -1918,6 +1875,15 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
} else
m_enable_extrusion_role_markers = false;

// if thumbnail type of BTT_TFT, insert above header
// if not, it is inserted under the header in its normal spot
const GCodeThumbnailsFormat m_gcode_thumbnail_format = print.full_print_config().opt_enum<GCodeThumbnailsFormat>("thumbnails_format");
Ocraftyone marked this conversation as resolved.
Show resolved Hide resolved
if (m_gcode_thumbnail_format == GCodeThumbnailsFormat::BTT_TFT)
GCodeThumbnails::export_thumbnails_to_file(
thumbnail_cb, print.get_plate_index(), print.full_print_config().option<ConfigOptionPoints>("thumbnails")->values,
m_gcode_thumbnail_format,
[&file](const char *sz) { file.write(sz); },
[&print]() { print.throw_if_canceled(); });

file.write_format("; HEADER_BLOCK_START\n");
// Write information on the generator.
Expand Down Expand Up @@ -1977,10 +1943,12 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
print.config().nozzle_temperature_initial_layer.get_at(0));
file.write("; CONFIG_BLOCK_END\n\n");
} else {
DoExport::export_thumbnails_to_file(
thumbnail_cb, print.get_plate_index(), print.full_print_config().option<ConfigOptionPoints>("thumbnails")->values,
[&file](const char *sz) { file.write(sz); },
[&print]() { print.throw_if_canceled(); });
if (m_gcode_thumbnail_format != GCodeThumbnailsFormat::BTT_TFT)
GCodeThumbnails::export_thumbnails_to_file(
thumbnail_cb, print.get_plate_index(), print.full_print_config().option<ConfigOptionPoints>("thumbnails")->values,
m_gcode_thumbnail_format,
[&file](const char *sz) { file.write(sz); },
[&print]() { print.throw_if_canceled(); });
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/libslic3r/GCode/GCodeProcessor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,10 @@ void GCodeProcessor::TimeProcessor::post_process(const std::string& filename, st
if (eol) {
++line_id;

gcode_line += "\n";
// determine the end of line character and pass to output
Ocraftyone marked this conversation as resolved.
Show resolved Hide resolved
gcode_line += *it_end;
if(*it_end == '\r' && *(++ it_end) == '\n')
gcode_line += '\n';
// replace placeholder lines
auto [processed, lines_added_count] = process_placeholders(gcode_line);
if (processed && lines_added_count > 0)
Expand Down
196 changes: 196 additions & 0 deletions src/libslic3r/GCode/Thumbnails.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
///|/ Copyright (c) Prusa Research 2022 Enrico Turri @enricoturri1966, Vojtěch Bubník @bubnikv
///|/
///|/ PrusaSlicer is released under the terms of the AGPLv3 or higher
///|/
#include "Thumbnails.hpp"
#include "../miniz_extension.hpp"

#include <qoi/qoi.h>
#include <jpeglib.h>
#include <jerror.h>

namespace Slic3r::GCodeThumbnails {

using namespace std::literals;

struct CompressedPNG : CompressedImageBuffer
{
~CompressedPNG() override { if (data) mz_free(data); }
std::string_view tag() const override { return "thumbnail"sv; }
};

struct CompressedJPG : CompressedImageBuffer
{
~CompressedJPG() override { free(data); }
std::string_view tag() const override { return "thumbnail_JPG"sv; }
};

struct CompressedQOI : CompressedImageBuffer
{
~CompressedQOI() override { free(data); }
std::string_view tag() const override { return "thumbnail_QOI"sv; }
};

struct CompressedBIQU : CompressedImageBuffer
{
~CompressedBIQU() override { free(data); }
std::string_view tag() const override { return "thumbnail_BIQU"sv; }
};

std::unique_ptr<CompressedImageBuffer> compress_thumbnail_png(const ThumbnailData &data)
{
auto out = std::make_unique<CompressedPNG>();
out->data = tdefl_write_image_to_png_file_in_memory_ex((const void*)data.pixels.data(), data.width, data.height, 4, &out->size, MZ_DEFAULT_LEVEL, 1);
return out;
}

std::unique_ptr<CompressedImageBuffer> compress_thumbnail_jpg(const ThumbnailData& data)
{
// Take vector of RGBA pixels and flip the image vertically
std::vector<unsigned char> rgba_pixels(data.pixels.size());
const unsigned int row_size = data.width * 4;
for (unsigned int y = 0; y < data.height; ++y) {
::memcpy(rgba_pixels.data() + (data.height - y - 1) * row_size, data.pixels.data() + y * row_size, row_size);
}

// Store pointers to scanlines start for later use
std::vector<unsigned char*> rows_ptrs;
rows_ptrs.reserve(data.height);
for (unsigned int y = 0; y < data.height; ++y) {
rows_ptrs.emplace_back(&rgba_pixels[y * row_size]);
}

std::vector<unsigned char> compressed_data(data.pixels.size());
unsigned char* compressed_data_ptr = compressed_data.data();
unsigned long compressed_data_size = data.pixels.size();

jpeg_error_mgr err;
jpeg_compress_struct info;
info.err = jpeg_std_error(&err);
jpeg_create_compress(&info);
jpeg_mem_dest(&info, &compressed_data_ptr, &compressed_data_size);

info.image_width = data.width;
info.image_height = data.height;
info.input_components = 4;
info.in_color_space = JCS_EXT_RGBA;

jpeg_set_defaults(&info);
jpeg_set_quality(&info, 85, TRUE);
jpeg_start_compress(&info, TRUE);

jpeg_write_scanlines(&info, rows_ptrs.data(), data.height);
jpeg_finish_compress(&info);
jpeg_destroy_compress(&info);

// FIXME -> Add error checking

auto out = std::make_unique<CompressedJPG>();
out->data = malloc(compressed_data_size);
out->size = size_t(compressed_data_size);
::memcpy(out->data, (const void*)compressed_data.data(), out->size);
return out;
}

std::unique_ptr<CompressedImageBuffer> compress_thumbnail_qoi(const ThumbnailData &data)
{
qoi_desc desc;
desc.width = data.width;
desc.height = data.height;
desc.channels = 4;
desc.colorspace = QOI_SRGB;

// Take vector of RGBA pixels and flip the image vertically
std::vector<uint8_t> rgba_pixels(data.pixels.size() * 4);
size_t row_size = data.width * 4;
for (size_t y = 0; y < data.height; ++ y)
memcpy(rgba_pixels.data() + (data.height - y - 1) * row_size, data.pixels.data() + y * row_size, row_size);

auto out = std::make_unique<CompressedQOI>();
int size;
out->data = qoi_encode((const void*)rgba_pixels.data(), &desc, &size);
out->size = size;
return out;
}

std::unique_ptr<CompressedImageBuffer> compress_thumbnail_btt_tft(const ThumbnailData &data) {

// Take vector of RGBA pixels and flip the image vertically
std::vector<unsigned char> rgba_pixels(data.pixels.size());
const unsigned int row_size = data.width * 4;
for (unsigned int y = 0; y < data.height; ++y) {
::memcpy(rgba_pixels.data() + (data.height - y - 1) * row_size, data.pixels.data() + y * row_size, row_size);
}

auto out = std::make_unique<CompressedBIQU>();

// get the output size of the data
// add 4 bytes to the row_size to account for end of line (\r\n)
// add 1 byte for the 0 of the c_str
out->size = data.height * (row_size + 4) + 1;
out->data = malloc(out->size);

std::stringstream out_data;
typedef struct {unsigned char r, g, b, a;} pixel;
pixel px;
for (int ypos = 0; ypos < data.height; ypos++) {
std::stringstream line;
line << ";";
for (int xpos = 0; xpos < row_size; xpos+=4) {
px.r = rgba_pixels[ypos * row_size + xpos];
px.g = rgba_pixels[ypos * row_size + xpos + 1];
px.b = rgba_pixels[ypos * row_size + xpos + 2];
px.a = rgba_pixels[ypos * row_size + xpos + 3];

// calculate values for RGB with alpha
const uint8_t rv = ((px.a * px.r) / 255);
const uint8_t gv = ((px.a * px.g) / 255);
const uint8_t bv = ((px.a * px.b) / 255);

// convert the RGB values to RGB565 hex that is right justified (same algorithm BTT firmware uses)
auto color_565 = rjust(get_hex(((rv >> 3) << 11) | ((gv >> 2) << 5) | (bv >> 3)), 4, '0');

//BTT original converter specifies these values should be '0000'
if (color_565 == "0020" || color_565 == "0841" || color_565 == "0861")
color_565 = "0000";
//add the color to the line
line << color_565;
}
// output line and end line (\r\n is important. BTT firmware requires it)
out_data << line.str() << "\r\n";
line.clear();
}
::memcpy(out->data, (const void*) out_data.str().c_str(), out->size);
return out;
}

std::string get_hex(const unsigned int input) {
std::stringstream stream;
stream << std::hex << input;
return stream.str();
}

std::string rjust(std::string input, unsigned int width, char fill_char) {
std::stringstream stream;
stream.fill(fill_char);
stream.width(width);
stream << input;
return stream.str();
}

std::unique_ptr<CompressedImageBuffer> compress_thumbnail(const ThumbnailData &data, GCodeThumbnailsFormat format)
{
switch (format) {
case GCodeThumbnailsFormat::PNG:
default:
return compress_thumbnail_png(data);
case GCodeThumbnailsFormat::JPG:
return compress_thumbnail_jpg(data);
case GCodeThumbnailsFormat::QOI:
return compress_thumbnail_qoi(data);
case GCodeThumbnailsFormat::BTT_TFT:
return compress_thumbnail_btt_tft(data);
}
}

} // namespace Slic3r::GCodeThumbnails
Loading