Files
OrcaSlicer/src/libslic3r/GCode/Thumbnails.cpp
T
HanifKoh 4895bc03b4 Remove Unused Project Includes and Forward-Declare Where a Type Is Only Referenced (#16099)
* Remove Unused Project Includes and Forward-Declare Where a Type Is Only Referenced

Generated with include-what-you-use and applied conservatively. Only OrcaSlicer's own headers, the ones under src/ and tests/, are removed or forward-declared; standard-library and third-party includes are left alone. An include is removed only when both the Release and the Debug configuration leave it unused, never from inside a conditional block, and never from a file with platform-specific blocks, which only gain includes. Files whose only use of a header sits behind a feature or debug macro (libvgcode's OpenGL ES and marker code, the ARACHNE/TESTS_EXPORT_SVGS debug output) keep their includes.

clonable_ptr.hpp gains #pragma once; it had no include guard and was only safe while Config.hpp was its sole includer.

* Remove Unused Project Includes From Files With Platform-Specific Code

A Linux include-what-you-use run cannot see the code inside _WIN32, __APPLE__ or __linux__ blocks, so its verdict is only taken where nothing the removed header declares, directly or through what it includes, is named inside those blocks. Removals also have to hold in both the Release and Debug configuration and never touch a line inside a conditional block.

* Restore the libslic3r Precompiled Header and Direct Includes Lost in the Platform Pass

The platform-file pass treated pchheader.hpp as an ordinary header and
emptied it, and left GUI_Preview.hpp and 14 other files relying on
headers they no longer reached directly.

* Restore MainFrame.hpp in ParamsDialog.cpp for the Windows-Only Reparent Call

* Include Headers That Files Reached Through Ones the Cleanup Removed

* Drop Includes Duplicated by the Cleanup or by Main's Own Additions

* Leave PreciseSeam.cpp as Main Has It After the Precise Seam Rework
2026-10-05 16:47:17 +08:00

623 lines
21 KiB
C++

#include "Thumbnails.hpp"
#include "libslic3r/GCode/ThumbnailData.hpp"
#include "libslic3r/PrintConfig.hpp"
#include "libslic3r/Point.hpp"
#include "libslic3r/Config.hpp"
#include "libslic3r/enum_bitmask.hpp"
#include "format.hpp"
#include <boost/algorithm/string/case_conv.hpp>
#include <qoi/qoi.h>
#include <cstdlib>
#include <cstring>
#include <cstddef>
#include <cstdint>
#include <ios>
#include <jpeglib.h>
#include <jerror.h>
#include <miniz.h>
#include <string_view>
#include <memory>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include <boost/algorithm/string.hpp>
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; }
};
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)
{
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;
}
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 = 46 >> 3;
g = 51 >> 2;
b = 72 >> 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) {
// 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 (unsigned int ypos = 0; ypos < data.height; ypos++) {
std::stringstream line;
line << ";";
for (unsigned 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);
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;
}
std::pair<GCodeThumbnailDefinitionsList, ThumbnailErrors> make_and_check_thumbnail_list(const std::string& thumbnails_string, const std::string_view def_ext /*= "PNG"sv*/)
{
if (thumbnails_string.empty())
return {};
std::istringstream is(thumbnails_string);
std::string point_str;
ThumbnailErrors errors;
// generate thumbnails data to process it
GCodeThumbnailDefinitionsList thumbnails_list;
while (std::getline(is, point_str, ',')) {
Vec2d point(Vec2d::Zero());
GCodeThumbnailsFormat format;
std::istringstream iss(point_str);
std::string coord_str;
if (std::getline(iss, coord_str, 'x') && !coord_str.empty()) {
std::istringstream(coord_str) >> point(0);
if (std::getline(iss, coord_str, '/') && !coord_str.empty()) {
std::istringstream(coord_str) >> point(1);
if (0 < point(0) && point(0) < 1000 && 0 < point(1) && point(1) < 1000) {
std::string ext_str;
std::getline(iss, ext_str, '/');
if (ext_str.empty())
ext_str = def_ext.empty() ? "PNG"sv : def_ext;
// check validity of extention
boost::to_upper(ext_str);
if (!ConfigOptionEnum<GCodeThumbnailsFormat>::from_string(ext_str, format)) {
format = GCodeThumbnailsFormat::PNG;
errors = enum_bitmask(errors | ThumbnailError::InvalidExt);
}
thumbnails_list.emplace_back(std::make_pair(format, point));
}
else
errors = enum_bitmask(errors | ThumbnailError::OutOfRange);
continue;
}
}
errors = enum_bitmask(errors | ThumbnailError::InvalidVal);
}
return std::make_pair(std::move(thumbnails_list), errors);
}
std::pair<GCodeThumbnailDefinitionsList, ThumbnailErrors> make_and_check_thumbnail_list(const ConfigBase& config)
{
// ??? Unit tests or command line slicing may not define "thumbnails" or "thumbnails_format".
// ??? If "thumbnails_format" is not defined, export to PNG.
// generate thumbnails data to process it
if (const auto thumbnails_value = config.option<ConfigOptionString>("thumbnails"))
return make_and_check_thumbnail_list(thumbnails_value->value);
return {};
}
std::string get_error_string(const ThumbnailErrors& errors)
{
std::string error_str;
if (errors.has(ThumbnailError::InvalidVal))
error_str += "\n - " + Slic3r::format("Invalid input format. Expected vector of dimensions in the following format: \"%1%\"", "XxY/EXT, XxY/EXT, ...");
if (errors.has(ThumbnailError::OutOfRange))
error_str += "\n - Input value is out of range";
if (errors.has(ThumbnailError::InvalidExt))
error_str += "\n - Some extension in the input is invalid";
return error_str;
}
} // namespace Slic3r::GCodeThumbnails