Add ColPic encoding for thumbnails(;gimage/;simage) as metadata.

This will enable showing preview images on QIDI/Elegoo machines
This commit is contained in:
SoftFever 2023-12-11 22:27:40 +08:00
parent ae8995fec2
commit dd5fd06356
6 changed files with 372 additions and 12 deletions

View file

@ -1998,12 +1998,15 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato
print.config().nozzle_temperature_initial_layer.get_at(0)); print.config().nozzle_temperature_initial_layer.get_at(0));
file.write("; CONFIG_BLOCK_END\n\n"); file.write("; CONFIG_BLOCK_END\n\n");
} else { } else {
if (m_gcode_thumbnail_format != GCodeThumbnailsFormat::BTT_TFT) if (m_gcode_thumbnail_format != GCodeThumbnailsFormat::BTT_TFT) {
auto thumbnaim_fmt = m_gcode_thumbnail_format;
// Orca: if the thumbnail format is ColPic, we write PNG in the beginning of gcode file and ColPic in the end of gcode file.
if(m_gcode_thumbnail_format == GCodeThumbnailsFormat::ColPic)
thumbnaim_fmt = GCodeThumbnailsFormat::PNG;
GCodeThumbnails::export_thumbnails_to_file( GCodeThumbnails::export_thumbnails_to_file(
thumbnail_cb, print.get_plate_index(), print.full_print_config().option<ConfigOptionPoints>("thumbnails")->values, thumbnail_cb, print.get_plate_index(), print.full_print_config().option<ConfigOptionPoints>("thumbnails")->values,
m_gcode_thumbnail_format, thumbnaim_fmt, [&file](const char* sz) { file.write(sz); }, [&print]() { print.throw_if_canceled(); });
[&file](const char *sz) { file.write(sz); }, }
[&print]() { print.throw_if_canceled(); });
} }
} }
@ -2646,6 +2649,11 @@ this->placeholder_parser().set("z_offset", new ConfigOptionFloat(m_config.z_offs
GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder) GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder)
.c_str()); .c_str());
file.write("\n"); file.write("\n");
if (m_gcode_thumbnail_format == GCodeThumbnailsFormat::ColPic)
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("; CONFIG_BLOCK_START\n"); file.write("; CONFIG_BLOCK_START\n");
std::string full_config; std::string full_config;
append_full_config(print, full_config); append_full_config(print, full_config);

View file

@ -8,6 +8,7 @@
#include <qoi/qoi.h> #include <qoi/qoi.h>
#include <jpeglib.h> #include <jpeglib.h>
#include <jerror.h> #include <jerror.h>
#include <vector>
namespace Slic3r::GCodeThumbnails { namespace Slic3r::GCodeThumbnails {
@ -37,6 +38,12 @@ struct CompressedBIQU : CompressedImageBuffer
std::string_view tag() const override { return "thumbnail_BIQU"sv; } std::string_view tag() const override { return "thumbnail_BIQU"sv; }
}; };
struct CompressedColPic : CompressedImageBuffer
{
~CompressedColPic() override { free(data); }
std::string_view tag() const override { return "thumbnail_QIDI"sv; }
};
std::unique_ptr<CompressedImageBuffer> compress_thumbnail_png(const ThumbnailData &data) std::unique_ptr<CompressedImageBuffer> compress_thumbnail_png(const ThumbnailData &data)
{ {
auto out = std::make_unique<CompressedPNG>(); auto out = std::make_unique<CompressedPNG>();
@ -113,6 +120,64 @@ std::unique_ptr<CompressedImageBuffer> compress_thumbnail_qoi(const ThumbnailDat
return out; return out;
} }
int ColPic_EncodeStr(unsigned short* fromcolor16, int picw, int pich, unsigned char* outputdata, int outputmaxtsize, int colorsmax);
std::unique_ptr<CompressedImageBuffer> compress_thumbnail_colpic(const ThumbnailData &data)
{
const int MAX_SIZE = 512;
int width = int(data.width);
int height = int(data.height);
// Orca: cap data size to MAX_SIZE while maintaining aspect ratio
if (width > MAX_SIZE || height > MAX_SIZE) {
double aspectRatio = static_cast<double>(width) / height;
if (aspectRatio > 1.0) {
width = MAX_SIZE;
height = static_cast<int>(MAX_SIZE / aspectRatio);
} else {
height = MAX_SIZE;
width = static_cast<int>(MAX_SIZE * aspectRatio);
}
}
std::vector<unsigned short> color16_buf(width * height);
std::vector<unsigned char> output_buf(height * width * 10);
std::vector<uint8_t> rgba_pixels(data.pixels.size() * 4);
size_t row_size = width * 4;
for (size_t y = 0; y < height; ++y)
memcpy(rgba_pixels.data() + y * row_size, data.pixels.data() + y * row_size, row_size);
const unsigned char* pixels;
pixels = (const unsigned char*) rgba_pixels.data();
int r = 0, g = 0, b = 0, a = 0, rgb = 0;
int time = width * height - 1;
for (int row = 0; row < height; ++row) {
int rr = row * width;
for (int col = 0; col < width; ++col) {
const int pix_idx = 4 * (rr + width - col - 1);
r = int(pixels[pix_idx]) >> 3;
g = int(pixels[pix_idx + 1]) >> 2;
b = int(pixels[pix_idx + 2]) >> 3;
a = int(pixels[pix_idx + 3]);
if (a == 0) {
r = 239 >> 3;
g = 243 >> 2;
b = 247 >> 3;
}
rgb = (r << 11) | (g << 5) | b;
color16_buf[time--] = rgb;
}
}
ColPic_EncodeStr(color16_buf.data(), width, height, output_buf.data(), output_buf.size(), 1024);
auto out = std::make_unique<CompressedColPic>();
out->size = output_buf.size();
out->data = malloc(out->size);
::memcpy(out->data, output_buf.data(), out->size);
return out;
}
std::unique_ptr<CompressedImageBuffer> compress_thumbnail_btt_tft(const ThumbnailData &data) { std::unique_ptr<CompressedImageBuffer> compress_thumbnail_btt_tft(const ThumbnailData &data) {
// Take vector of RGBA pixels and flip the image vertically // Take vector of RGBA pixels and flip the image vertically
@ -133,10 +198,10 @@ std::unique_ptr<CompressedImageBuffer> compress_thumbnail_btt_tft(const Thumbnai
std::stringstream out_data; std::stringstream out_data;
typedef struct {unsigned char r, g, b, a;} pixel; typedef struct {unsigned char r, g, b, a;} pixel;
pixel px; pixel px;
for (int ypos = 0; ypos < data.height; ypos++) { for (unsigned int ypos = 0; ypos < data.height; ypos++) {
std::stringstream line; std::stringstream line;
line << ";"; line << ";";
for (int xpos = 0; xpos < row_size; xpos+=4) { for (unsigned int xpos = 0; xpos < row_size; xpos+=4) {
px.r = rgba_pixels[ypos * row_size + xpos]; px.r = rgba_pixels[ypos * row_size + xpos];
px.g = rgba_pixels[ypos * row_size + xpos + 1]; px.g = rgba_pixels[ypos * row_size + xpos + 1];
px.b = rgba_pixels[ypos * row_size + xpos + 2]; px.b = rgba_pixels[ypos * row_size + xpos + 2];
@ -190,7 +255,278 @@ std::unique_ptr<CompressedImageBuffer> compress_thumbnail(const ThumbnailData &d
return compress_thumbnail_qoi(data); return compress_thumbnail_qoi(data);
case GCodeThumbnailsFormat::BTT_TFT: case GCodeThumbnailsFormat::BTT_TFT:
return compress_thumbnail_btt_tft(data); return compress_thumbnail_btt_tft(data);
case GCodeThumbnailsFormat::ColPic:
return compress_thumbnail_colpic(data);
} }
} }
typedef struct
{
unsigned short colo16;
unsigned char A0;
unsigned char A1;
unsigned char A2;
unsigned char res0;
unsigned short res1;
unsigned int qty;
} U16HEAD;
typedef struct
{
unsigned char encodever;
unsigned char res0;
unsigned short oncelistqty;
unsigned int PicW;
unsigned int PicH;
unsigned int mark;
unsigned int ListDataSize;
unsigned int ColorDataSize;
unsigned int res1;
unsigned int res2;
} ColPicHead3;
static void colmemmove(unsigned char* dec, unsigned char* src, int lenth)
{
if (src < dec) {
dec += lenth - 1;
src += lenth - 1;
while (lenth > 0) {
*(dec--) = *(src--);
lenth--;
}
} else {
while (lenth > 0) {
*(dec++) = *(src++);
lenth--;
}
}
}
static void colmemcpy(unsigned char* dec, unsigned char* src, int lenth)
{
while (lenth > 0) {
*(dec++) = *(src++);
lenth--;
}
}
static void colmemset(unsigned char* dec, unsigned char val, int lenth)
{
while (lenth > 0) {
*(dec++) = val;
lenth--;
}
}
static void ADList0(unsigned short val, U16HEAD* listu16, int* listqty, int maxqty)
{
unsigned char A0;
unsigned char A1;
unsigned char A2;
int qty = *listqty;
if (qty >= maxqty)
return;
for (int i = 0; i < qty; i++) {
if (listu16[i].colo16 == val) {
listu16[i].qty++;
return;
}
}
A0 = (unsigned char) (val >> 11);
A1 = (unsigned char) ((val << 5) >> 10);
A2 = (unsigned char) ((val << 11) >> 11);
U16HEAD* a = &listu16[qty];
a->colo16 = val;
a->A0 = A0;
a->A1 = A1;
a->A2 = A2;
a->qty = 1;
*listqty = qty + 1;
}
static int Byte8bitEncode(
unsigned short* fromcolor16, unsigned short* listu16, int listqty, int dotsqty, unsigned char* outputdata, int decMaxBytesize)
{
unsigned char tid, sid;
int dots = 0;
int srcindex = 0;
int decindex = 0;
int lastid = 0;
int temp = 0;
while (dotsqty > 0) {
dots = 1;
for (int i = 0; i < (dotsqty - 1); i++) {
if (fromcolor16[srcindex + i] != fromcolor16[srcindex + i + 1])
break;
dots++;
if (dots == 255)
break;
}
temp = 0;
for (int i = 0; i < listqty; i++) {
if (listu16[i] == fromcolor16[srcindex]) {
temp = i;
break;
}
}
tid = (unsigned char) (temp % 32);
sid = (unsigned char) (temp / 32);
if (lastid != sid) {
if (decindex >= decMaxBytesize)
goto IL_END;
outputdata[decindex] = 7;
outputdata[decindex] <<= 5;
outputdata[decindex] += sid;
decindex++;
lastid = sid;
}
if (dots <= 6) {
if (decindex >= decMaxBytesize)
goto IL_END;
outputdata[decindex] = (unsigned char) dots;
outputdata[decindex] <<= 5;
outputdata[decindex] += tid;
decindex++;
} else {
if (decindex >= decMaxBytesize)
goto IL_END;
outputdata[decindex] = 0;
outputdata[decindex] += tid;
decindex++;
if (decindex >= decMaxBytesize)
goto IL_END;
outputdata[decindex] = (unsigned char) dots;
decindex++;
}
srcindex += dots;
dotsqty -= dots;
}
IL_END:
return decindex;
}
static int ColPicEncode(unsigned short* fromcolor16, int picw, int pich, unsigned char* outputdata, int outputmaxtsize, int colorsmax)
{
U16HEAD l0;
int cha0, cha1, cha2, fid, minval;
ColPicHead3* Head0 = nullptr;
U16HEAD Listu16[1024];
int ListQty = 0;
int enqty = 0;
int dotsqty = picw * pich;
if (colorsmax > 1024)
colorsmax = 1024;
for (int i = 0; i < dotsqty; i++) {
int ch = (int) fromcolor16[i];
ADList0(ch, Listu16, &ListQty, 1024);
}
for (int index = 1; index < ListQty; index++) {
l0 = Listu16[index];
for (int i = 0; i < index; i++) {
if (l0.qty >= Listu16[i].qty) {
colmemmove((unsigned char*) &Listu16[i + 1], (unsigned char*) &Listu16[i], (index - i) * sizeof(U16HEAD));
colmemcpy((unsigned char*) &Listu16[i], (unsigned char*) &l0, sizeof(U16HEAD));
break;
}
}
}
while (ListQty > colorsmax) {
l0 = Listu16[ListQty - 1];
minval = 255;
fid = -1;
for (int i = 0; i < colorsmax; i++) {
cha0 = Listu16[i].A0 - l0.A0;
if (cha0 < 0)
cha0 = 0 - cha0;
cha1 = Listu16[i].A1 - l0.A1;
if (cha1 < 0)
cha1 = 0 - cha1;
cha2 = Listu16[i].A2 - l0.A2;
if (cha2 < 0)
cha2 = 0 - cha2;
int chall = cha0 + cha1 + cha2;
if (chall < minval) {
minval = chall;
fid = i;
}
}
for (int i = 0; i < dotsqty; i++) {
if (fromcolor16[i] == l0.colo16)
fromcolor16[i] = Listu16[fid].colo16;
}
ListQty = ListQty - 1;
}
Head0 = ((ColPicHead3*) outputdata);
colmemset(outputdata, 0, sizeof(ColPicHead3));
Head0->encodever = 3;
Head0->oncelistqty = 0;
Head0->mark = 0x05DDC33C;
Head0->ListDataSize = ListQty * 2;
for (int i = 0; i < ListQty; i++) {
unsigned short* l0 = (unsigned short*) &outputdata[sizeof(ColPicHead3)];
l0[i] = Listu16[i].colo16;
}
enqty = Byte8bitEncode(fromcolor16, (unsigned short*) &outputdata[sizeof(ColPicHead3)], Head0->ListDataSize >> 1, dotsqty,
&outputdata[sizeof(ColPicHead3) + Head0->ListDataSize],
outputmaxtsize - sizeof(ColPicHead3) - Head0->ListDataSize);
Head0->ColorDataSize = enqty;
Head0->PicW = picw;
Head0->PicH = pich;
return sizeof(ColPicHead3) + Head0->ListDataSize + Head0->ColorDataSize;
}
int ColPic_EncodeStr(unsigned short* fromcolor16, int picw, int pich, unsigned char* outputdata, int outputmaxtsize, int colorsmax)
{
int qty = 0;
int temp = 0;
int strindex = 0;
int hexindex = 0;
unsigned char TempBytes[4];
qty = ColPicEncode(fromcolor16, picw, pich, outputdata, outputmaxtsize, colorsmax);
if (qty == 0)
return 0;
temp = 3 - (qty % 3);
while (temp > 0) {
outputdata[qty] = 0;
qty++;
temp--;
}
if ((qty * 4 / 3) >= outputmaxtsize)
return 0;
hexindex = qty;
strindex = (qty * 4 / 3);
while (hexindex > 0) {
hexindex -= 3;
strindex -= 4;
TempBytes[0] = (unsigned char) (outputdata[hexindex] >> 2);
TempBytes[1] = (unsigned char) (outputdata[hexindex] & 3);
TempBytes[1] <<= 4;
TempBytes[1] += ((unsigned char) (outputdata[hexindex + 1] >> 4));
TempBytes[2] = (unsigned char) (outputdata[hexindex + 1] & 15);
TempBytes[2] <<= 2;
TempBytes[2] += ((unsigned char) (outputdata[hexindex + 2] >> 6));
TempBytes[3] = (unsigned char) (outputdata[hexindex + 2] & 63);
TempBytes[0] += 48;
if (TempBytes[0] == (unsigned char) '\\')
TempBytes[0] = 126;
TempBytes[0 + 1] += 48;
if (TempBytes[0 + 1] == (unsigned char) '\\')
TempBytes[0 + 1] = 126;
TempBytes[0 + 2] += 48;
if (TempBytes[0 + 2] == (unsigned char) '\\')
TempBytes[0 + 2] = 126;
TempBytes[0 + 3] += 48;
if (TempBytes[0 + 3] == (unsigned char) '\\')
TempBytes[0 + 3] = 126;
outputdata[strindex] = TempBytes[0];
outputdata[strindex + 1] = TempBytes[1];
outputdata[strindex + 2] = TempBytes[2];
outputdata[strindex + 3] = TempBytes[3];
}
qty = qty * 4 / 3;
outputdata[qty] = 0;
return qty;
}
} // namespace Slic3r::GCodeThumbnails } // namespace Slic3r::GCodeThumbnails

View file

@ -7,6 +7,7 @@
#include "../Point.hpp" #include "../Point.hpp"
#include "../PrintConfig.hpp" #include "../PrintConfig.hpp"
#include "PrintConfig.hpp"
#include "ThumbnailData.hpp" #include "ThumbnailData.hpp"
#include <vector> #include <vector>
@ -52,7 +53,15 @@ inline void export_thumbnails_to_file(ThumbnailsGeneratorCallback &thumbnail_cb,
output((char *) compressed->data); output((char *) compressed->data);
if (i == (thumbnails.size() - 1)) if (i == (thumbnails.size() - 1))
output("; bigtree thumbnail end\r\n\r\n"); output("; bigtree thumbnail end\r\n\r\n");
} else { }
else if (format == GCodeThumbnailsFormat::ColPic) {
if (i == 0) {
output((boost::format("\n\n;gimage:%s\n\n") % reinterpret_cast<char*>(compressed->data)).str().c_str());
} else {
output((boost::format("\n\n;simage:%s\n\n") % reinterpret_cast<char*>(compressed->data)).str().c_str());
}
}
else {
output("; THUMBNAIL_BLOCK_START\n"); output("; THUMBNAIL_BLOCK_START\n");
std::string encoded; std::string encoded;
encoded.resize(boost::beast::detail::base64::encoded_size(compressed->size)); encoded.resize(boost::beast::detail::base64::encoded_size(compressed->size));

View file

@ -368,7 +368,8 @@ static const t_config_enum_values s_keys_map_GCodeThumbnailsFormat = {
{ "PNG", int(GCodeThumbnailsFormat::PNG) }, { "PNG", int(GCodeThumbnailsFormat::PNG) },
{ "JPG", int(GCodeThumbnailsFormat::JPG) }, { "JPG", int(GCodeThumbnailsFormat::JPG) },
{ "QOI", int(GCodeThumbnailsFormat::QOI) }, { "QOI", int(GCodeThumbnailsFormat::QOI) },
{ "BTT_TFT", int(GCodeThumbnailsFormat::BTT_TFT) } { "BTT_TFT", int(GCodeThumbnailsFormat::BTT_TFT) },
{ "ColPic", int(GCodeThumbnailsFormat::ColPic) }
}; };
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(GCodeThumbnailsFormat) CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(GCodeThumbnailsFormat)
@ -4390,7 +4391,13 @@ def = this->add("filament_loading_speed", coFloats);
def->enum_values.push_back("PNG"); def->enum_values.push_back("PNG");
def->enum_values.push_back("JPG"); def->enum_values.push_back("JPG");
def->enum_values.push_back("QOI"); def->enum_values.push_back("QOI");
def->enum_values.push_back("BTT TFT"); def->enum_values.push_back("BTT_TFT");
def->enum_values.push_back("ColPic");
def->enum_labels.push_back("PNG");
def->enum_labels.push_back("JPG");
def->enum_labels.push_back("QOI");
def->enum_labels.push_back("BTT TT");
def->enum_labels.push_back("ColPic");
def->set_default_value(new ConfigOptionEnum<GCodeThumbnailsFormat>(GCodeThumbnailsFormat::PNG)); def->set_default_value(new ConfigOptionEnum<GCodeThumbnailsFormat>(GCodeThumbnailsFormat::PNG));
def = this->add("use_relative_e_distances", coBool); def = this->add("use_relative_e_distances", coBool);

View file

@ -272,7 +272,7 @@ enum RetractLiftEnforceType {
}; };
enum class GCodeThumbnailsFormat { enum class GCodeThumbnailsFormat {
PNG, JPG, QOI, BTT_TFT PNG, JPG, QOI, BTT_TFT, ColPic
}; };
static std::string bed_type_to_gcode_string(const BedType type) static std::string bed_type_to_gcode_string(const BedType type)

View file

@ -3408,8 +3408,8 @@ void TabPrinter::build_fff()
optgroup->append_single_option_line("disable_m73"); optgroup->append_single_option_line("disable_m73");
option = optgroup->get_option("thumbnails"); option = optgroup->get_option("thumbnails");
option.opt.full_width = true; option.opt.full_width = true;
optgroup->append_single_option_line(option); optgroup->append_single_option_line(option, "thumbnails");
optgroup->append_single_option_line("thumbnails_format"); optgroup->append_single_option_line("thumbnails_format", "thumbnails");
optgroup->append_single_option_line("use_relative_e_distances"); optgroup->append_single_option_line("use_relative_e_distances");
optgroup->append_single_option_line("use_firmware_retraction"); optgroup->append_single_option_line("use_firmware_retraction");
// optgroup->append_single_option_line("spaghetti_detector"); // optgroup->append_single_option_line("spaghetti_detector");