优化打印算法
This commit is contained in:
@@ -64,7 +64,7 @@ static const char *s_web_index =
|
||||
" </div>\n"
|
||||
"\n"
|
||||
" <label>Z-Image Prompt</label>\n"
|
||||
" <textarea id='imagePrompt'>一只坐在窗边的橘猫,午后阳光,胶片质感,写实风格。</textarea>\n"
|
||||
" <textarea id='imagePrompt'>一只可爱橘猫正脸特写,黑白线稿风格,白色背景,主体居中,边缘清晰,细节简洁。</textarea>\n"
|
||||
" <div class='row'>\n"
|
||||
" <select id='imgSize' style='max-width:180px'>\n"
|
||||
" <option value='1024*1024'>1024*1024</option>\n"
|
||||
@@ -121,6 +121,7 @@ static const char *s_web_index =
|
||||
" const imgScaleToWidthEl = document.getElementById('imgScaleToWidth');\n"
|
||||
" const imgInvertEl = document.getElementById('imgInvert');\n"
|
||||
" const imgPromptExtendEl = document.getElementById('imgPromptExtend');\n"
|
||||
" const thermalPromptSuffix = 'black and white line art, pure white background, high contrast, clean edges, minimal texture, no dark background, no heavy shadows, centered subject, thermal printer friendly';\n"
|
||||
"\n"
|
||||
" function log(obj) {\n"
|
||||
" if (typeof obj === 'string') {\n"
|
||||
@@ -152,8 +153,13 @@ static const char *s_web_index =
|
||||
" throw new Error('please input image prompt');\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" const mergedPrompt = prompt + '\\n\\n' + thermalPromptSuffix;\n"
|
||||
" if (mergedPrompt.length > 800) {\n"
|
||||
" throw new Error('prompt too long after thermal optimization, please shorten it');\n"
|
||||
" }\n"
|
||||
"\n"
|
||||
" return {\n"
|
||||
" prompt,\n"
|
||||
" prompt: mergedPrompt,\n"
|
||||
" size: imgSizeEl.value || '1120*1440',\n"
|
||||
" prompt_extend: imgPromptExtendEl.checked,\n"
|
||||
" threshold: toInt(imgThresholdEl.value, 160, 0, 255),\n"
|
||||
|
||||
@@ -40,6 +40,75 @@ static void *calloc_prefer_psram(size_t n, size_t size) {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static uint8_t sample_gray8_scaled(const uint8_t *gray,
|
||||
uint16_t src_width,
|
||||
uint16_t src_height,
|
||||
bool scale_to_width,
|
||||
uint16_t dst_height,
|
||||
uint16_t x,
|
||||
uint16_t y) {
|
||||
uint16_t src_y = scale_to_width
|
||||
? (uint16_t)(((uint32_t)y * src_height) / dst_height)
|
||||
: y;
|
||||
if (src_y >= src_height) {
|
||||
src_y = (uint16_t)(src_height - 1);
|
||||
}
|
||||
|
||||
uint16_t src_x = scale_to_width
|
||||
? (uint16_t)(((uint32_t)x * src_width) / RASTER_WIDTH)
|
||||
: x;
|
||||
if (src_x >= src_width) {
|
||||
src_x = (uint16_t)(src_width - 1);
|
||||
}
|
||||
|
||||
return gray[(size_t)src_y * src_width + src_x];
|
||||
}
|
||||
|
||||
static uint8_t gray8_to_work_luma(uint8_t gray, bool invert) {
|
||||
return invert ? (uint8_t)(255u - gray) : gray;
|
||||
}
|
||||
|
||||
static uint8_t compute_otsu_threshold(const uint32_t hist[256], uint32_t total_count) {
|
||||
if (hist == NULL || total_count == 0) {
|
||||
return 160;
|
||||
}
|
||||
|
||||
uint64_t all_sum = 0;
|
||||
for (uint32_t i = 0; i < 256; ++i) {
|
||||
all_sum += (uint64_t)i * hist[i];
|
||||
}
|
||||
|
||||
uint64_t bg_sum = 0;
|
||||
uint32_t bg_count = 0;
|
||||
double max_between_var = -1.0;
|
||||
uint8_t best_thr = 160;
|
||||
|
||||
for (uint32_t t = 0; t < 256; ++t) {
|
||||
bg_count += hist[t];
|
||||
if (bg_count == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t fg_count = total_count - bg_count;
|
||||
if (fg_count == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
bg_sum += (uint64_t)t * hist[t];
|
||||
double bg_mean = (double)bg_sum / (double)bg_count;
|
||||
double fg_mean = (double)(all_sum - bg_sum) / (double)fg_count;
|
||||
double mean_diff = bg_mean - fg_mean;
|
||||
double between_var = (double)bg_count * (double)fg_count * mean_diff * mean_diff;
|
||||
|
||||
if (between_var > max_between_var) {
|
||||
max_between_var = between_var;
|
||||
best_thr = (uint8_t)t;
|
||||
}
|
||||
}
|
||||
|
||||
return best_thr;
|
||||
}
|
||||
|
||||
esp_err_t raster_tools_convert_gray8_to_raster_384(const uint8_t *gray,
|
||||
uint16_t src_width,
|
||||
uint16_t src_height,
|
||||
@@ -105,30 +174,81 @@ esp_err_t raster_tools_convert_gray8_to_raster_384(const uint8_t *gray,
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
uint32_t hist[256] = {0};
|
||||
uint64_t sum_luma = 0;
|
||||
uint32_t total_px = (uint32_t)RASTER_WIDTH * (uint32_t)dst_height;
|
||||
for (uint16_t y = 0; y < dst_height; ++y) {
|
||||
uint16_t src_y = scale_to_width
|
||||
? (uint16_t)(((uint32_t)y * src_height) / dst_height)
|
||||
: y;
|
||||
if (src_y >= src_height) {
|
||||
src_y = (uint16_t)(src_height - 1);
|
||||
}
|
||||
|
||||
for (uint16_t x = 0; x < RASTER_WIDTH; ++x) {
|
||||
uint16_t src_x = scale_to_width
|
||||
? (uint16_t)(((uint32_t)x * src_width) / RASTER_WIDTH)
|
||||
: x;
|
||||
if (src_x >= src_width) {
|
||||
src_x = (uint16_t)(src_width - 1);
|
||||
uint8_t src_v = sample_gray8_scaled(gray, src_width, src_height, scale_to_width, dst_height, x, y);
|
||||
uint8_t work_v = gray8_to_work_luma(src_v, invert);
|
||||
hist[work_v]++;
|
||||
sum_luma += work_v;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t otsu_thr = compute_otsu_threshold(hist, total_px);
|
||||
uint8_t mean_luma = total_px > 0 ? (uint8_t)(sum_luma / total_px) : (uint8_t)160;
|
||||
|
||||
/* Keep backward compatibility: threshold still works as bias around 160. */
|
||||
int final_thr = (int)otsu_thr + (((int)threshold - 160) / 2);
|
||||
/* White-bias to reduce large black blocks on thermal paper. */
|
||||
if (mean_luma < 112) {
|
||||
final_thr -= 10;
|
||||
} else if (mean_luma > 182) {
|
||||
final_thr += 6;
|
||||
}
|
||||
if (final_thr < 40) {
|
||||
final_thr = 40;
|
||||
} else if (final_thr > 220) {
|
||||
final_thr = 220;
|
||||
}
|
||||
|
||||
int16_t *err_curr = (int16_t *)calloc_prefer_psram(RASTER_WIDTH + 2u, sizeof(int16_t));
|
||||
int16_t *err_next = (int16_t *)calloc_prefer_psram(RASTER_WIDTH + 2u, sizeof(int16_t));
|
||||
bool use_dither = (err_curr != NULL && err_next != NULL);
|
||||
|
||||
for (uint16_t y = 0; y < dst_height; ++y) {
|
||||
for (uint16_t x = 0; x < RASTER_WIDTH; ++x) {
|
||||
uint8_t src_v = sample_gray8_scaled(gray, src_width, src_height, scale_to_width, dst_height, x, y);
|
||||
int luma = (int)gray8_to_work_luma(src_v, invert);
|
||||
if (use_dither) {
|
||||
luma += err_curr[x + 1u];
|
||||
if (luma < 0) {
|
||||
luma = 0;
|
||||
} else if (luma > 255) {
|
||||
luma = 255;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t v = gray[(size_t)src_y * src_width + src_x];
|
||||
bool black = invert ? (v > threshold) : (v < threshold);
|
||||
bool black = luma < final_thr;
|
||||
if (black) {
|
||||
set_black(raster, RASTER_WIDTH, x, y);
|
||||
}
|
||||
|
||||
if (use_dither) {
|
||||
int quantized = black ? 0 : 255;
|
||||
int e = luma - quantized;
|
||||
|
||||
if ((x + 1u) < RASTER_WIDTH) {
|
||||
err_curr[x + 2u] += (int16_t)((e * 7) / 16);
|
||||
}
|
||||
err_next[x] += (int16_t)((e * 3) / 16);
|
||||
err_next[x + 1u] += (int16_t)((e * 5) / 16);
|
||||
err_next[x + 2u] += (int16_t)(e / 16);
|
||||
}
|
||||
}
|
||||
|
||||
if (use_dither) {
|
||||
int16_t *tmp = err_curr;
|
||||
err_curr = err_next;
|
||||
err_next = tmp;
|
||||
memset(err_next, 0, (RASTER_WIDTH + 2u) * sizeof(int16_t));
|
||||
}
|
||||
}
|
||||
|
||||
free(err_curr);
|
||||
free(err_next);
|
||||
|
||||
*out_width = dst_width;
|
||||
*out_height = dst_height;
|
||||
*out_raster = raster;
|
||||
|
||||
Reference in New Issue
Block a user