Skip to content

Commit

Permalink
Thumbnail Formats Option Ported from PrusaSlicer and add BIQU/BTT for…
Browse files Browse the repository at this point in the history
…mat (#2405)

* Add needed src files and update CMake files

* Implementation of GCodeThumbnailsFormat for PNG, JPG, and QOI complete

* Implement BIQU (Big Tree Tech) Thumbnail Format

* have GCodeProcessor.post_process pass through original line end characters

* fix biqu thumbnail output
use \r\n for new lines in the biqu thumbnail portion. the firmware requires these end characters to function properly.
update names of variables and add comments to be more descriptive
replace modified Qt pixel algorithm with much simpler algorithm from BTT TFT firmware

* rename BiQU to BTT_TFT for better clarity

* remove underscore from GUI option

---------

Co-authored-by: SoftFever <[email protected]>
  • Loading branch information
Ocraftyone and SoftFever authored Oct 17, 2023
1 parent 25c9b43 commit 4d14ee1
Show file tree
Hide file tree
Showing 15 changed files with 1,124 additions and 50 deletions.
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
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");
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
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

0 comments on commit 4d14ee1

Please sign in to comment.