mirror of
https://github.com/i12bp8/TagTinker.git
synced 2026-10-23 13:32:59 +00:00
Fix Color 2.6 page default, Set Text heap, and WiFi canvas
Default image page is 2 unless the user picked 2-7. Set Text packs planes instead of 45KB buffers. WiFi and odd-size BMPs use glass 296x152 then transpose. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
b328511d02
commit
3b8ad97c34
10 files changed
+321
-158
No files matched your search
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* SmartTAG Color 2.6 (type 1626) geometry and page policy.
|
||||
* Glass is landscape 296x152. The image header is 152x296 (short x long).
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#define TAGTINKER_TYPE_SMARTAG_COLOR_26 1626
|
||||
#define TAGTINKER_COLOR26_WIRE_W 152U
|
||||
#define TAGTINKER_COLOR26_WIRE_H 296U
|
||||
#define TAGTINKER_COLOR26_GLASS_W 296U
|
||||
#define TAGTINKER_COLOR26_GLASS_H 152U
|
||||
|
||||
static inline bool tagtinker_type_needs_wh_swap(uint16_t type_code) {
|
||||
return type_code == TAGTINKER_TYPE_SMARTAG_COLOR_26;
|
||||
}
|
||||
|
||||
static inline bool tagtinker_type_uses_ui_page(uint16_t type_code) {
|
||||
return tagtinker_type_needs_wh_swap(type_code);
|
||||
}
|
||||
|
||||
/* Store-used Color 2.6 tags keep the barcode on page 1. Page 2 is the image
|
||||
* slot. Unspecified pages (0 or 1) map there. Explicit 2-7 are left alone. */
|
||||
static inline uint8_t tagtinker_color26_resolve_page(uint8_t page) {
|
||||
if(page <= 1U) return 2U;
|
||||
if(page > 7U) return 7U;
|
||||
return page;
|
||||
}
|
||||
|
||||
/* proto (px, py) on the 152x296 wire canvas -> glass (bx, by) 296x152. */
|
||||
static inline void tagtinker_color26_proto_to_glass(
|
||||
uint16_t proto_w,
|
||||
uint16_t px,
|
||||
uint16_t py,
|
||||
uint16_t* bx,
|
||||
uint16_t* by) {
|
||||
*bx = py;
|
||||
*by = (uint16_t)(proto_w - 1U - px);
|
||||
}
|
||||
+15
-51
@@ -53,7 +53,8 @@ static const TagTinkerProfileEntry profile_table[] = {
|
||||
{1370, 296, 128, TagTinkerTagKindDotMatrix, TagTinkerTagColorRed, "SmartTag HD L Red (2021)", 0},
|
||||
{1371, 648, 480, TagTinkerTagKindDotMatrix, TagTinkerTagColorRed, "SmartTag HD150 Red (2021)", 0},
|
||||
{1510, 0, 0, TagTinkerTagKindSegment, TagTinkerTagColorMono, "SmartTag E5 M", 1},
|
||||
{1626, 152, 296, TagTinkerTagKindDotMatrix, TagTinkerTagColorRed, "SmartTAG Color 2.6", 0},
|
||||
{TAGTINKER_TYPE_SMARTAG_COLOR_26, TAGTINKER_COLOR26_WIRE_W, TAGTINKER_COLOR26_WIRE_H,
|
||||
TagTinkerTagKindDotMatrix, TagTinkerTagColorRed, "SmartTAG Color 2.6", 0},
|
||||
{1627, 296, 128, TagTinkerTagKindDotMatrix, TagTinkerTagColorRed, "SmartTag HD L Red", 0},
|
||||
{1628, 296, 128, TagTinkerTagKindDotMatrix, TagTinkerTagColorRed, "SmartTag HD L Red", 0},
|
||||
{1639, 152, 152, TagTinkerTagKindDotMatrix, TagTinkerTagColorRed, "SmartTag HD S Red", 0},
|
||||
@@ -224,43 +225,6 @@ static inline uint8_t plane_pixel_at(const uint8_t* p1, const uint8_t* p2, size_
|
||||
return (idx < count) ? p1[idx] : p2[idx - count];
|
||||
}
|
||||
|
||||
static size_t tagtinker_rle_planes_bit_length(const uint8_t* p1, const uint8_t* p2, size_t count) {
|
||||
if(!p1) return 0;
|
||||
size_t total = p2 ? (count * 2U) : count;
|
||||
if(total == 0) return 0;
|
||||
size_t bit_len = 1U;
|
||||
uint8_t run_pixel = plane_pixel_at(p1, p2, count, 0);
|
||||
uint32_t run_count = 1;
|
||||
for(size_t i = 1; i < total; i++) {
|
||||
uint8_t pix = plane_pixel_at(p1, p2, count, i);
|
||||
if(pix == run_pixel) run_count++;
|
||||
else { bit_len += record_run_bit_length(run_count); run_pixel = pix; run_count = 1; }
|
||||
}
|
||||
if(run_count > 0U) bit_len += record_run_bit_length(run_count);
|
||||
return bit_len;
|
||||
}
|
||||
|
||||
static void tagtinker_pack_planes_raw(const uint8_t* p1, const uint8_t* p2, size_t count, uint8_t* out) {
|
||||
size_t total = p2 ? (count * 2U) : count;
|
||||
TagTinkerBitWriter writer = {.data = out, .bit_pos = 0};
|
||||
for(size_t i = 0; i < total; i++) bit_writer_append(&writer, plane_pixel_at(p1, p2, count, i));
|
||||
}
|
||||
|
||||
static void tagtinker_pack_planes_rle(const uint8_t* p1, const uint8_t* p2, size_t count, uint8_t* out) {
|
||||
size_t total = p2 ? (count * 2U) : count;
|
||||
if(total == 0) return;
|
||||
TagTinkerBitWriter writer = {.data = out, .bit_pos = 0};
|
||||
uint8_t run_pixel = plane_pixel_at(p1, p2, count, 0);
|
||||
uint32_t run_count = 1;
|
||||
bit_writer_append(&writer, run_pixel);
|
||||
for(size_t i = 1; i < total; i++) {
|
||||
uint8_t pix = plane_pixel_at(p1, p2, count, i);
|
||||
if(pix == run_pixel) run_count++;
|
||||
else { bit_writer_append_run(&writer, run_count); run_pixel = pix; run_count = 1; }
|
||||
}
|
||||
if(run_count > 0U) bit_writer_append_run(&writer, run_count);
|
||||
}
|
||||
|
||||
#define DATA_BITS_PER_FRAME (TAGTINKER_IMAGE_DATA_BYTES_PER_FRAME * 8U)
|
||||
|
||||
static size_t tagtinker_rle_fn_bit_length(TagTinkerPixelAtFn pixel_at, void* ctx, size_t total) {
|
||||
@@ -319,23 +283,23 @@ bool tagtinker_encode_fn_payload(
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const uint8_t* p1;
|
||||
const uint8_t* p2;
|
||||
size_t count;
|
||||
} TagTinkerPlaneCtx;
|
||||
|
||||
static uint8_t tagtinker_plane_pixel_cb(size_t idx, void* ctx) {
|
||||
const TagTinkerPlaneCtx* c = ctx;
|
||||
return plane_pixel_at(c->p1, c->p2, c->count, idx);
|
||||
}
|
||||
|
||||
bool tagtinker_encode_planes_payload(
|
||||
const uint8_t* p1, const uint8_t* p2, size_t count, TagTinkerCompressionMode mode, TagTinkerImagePayload* payload) {
|
||||
if(!p1 || !payload) return false;
|
||||
memset(payload, 0, sizeof(*payload));
|
||||
TagTinkerPlaneCtx ctx = {.p1 = p1, .p2 = p2, .count = count};
|
||||
size_t total = p2 ? (count * 2U) : count;
|
||||
size_t comp_len = tagtinker_rle_planes_bit_length(p1, p2, count);
|
||||
bool use_compressed = (mode == TagTinkerCompressionRle) ||
|
||||
(mode == TagTinkerCompressionAuto && comp_len > 0U && comp_len < total);
|
||||
size_t src_len = use_compressed ? comp_len : total;
|
||||
size_t padded_bits = src_len + ((DATA_BITS_PER_FRAME - (src_len % DATA_BITS_PER_FRAME)) % DATA_BITS_PER_FRAME);
|
||||
uint8_t* data = calloc(padded_bits / 8U, 1);
|
||||
if(!data) return false;
|
||||
if(use_compressed) tagtinker_pack_planes_rle(p1, p2, count, data);
|
||||
else tagtinker_pack_planes_raw(p1, p2, count, data);
|
||||
payload->data = data; payload->byte_count = padded_bits / 8U;
|
||||
payload->comp_type = use_compressed ? 2U : 0U;
|
||||
return true;
|
||||
return tagtinker_encode_fn_payload(tagtinker_plane_pixel_cb, &ctx, total, mode, payload);
|
||||
}
|
||||
|
||||
bool tagtinker_encode_image_payload(
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include "tagtinker_color26.h"
|
||||
|
||||
#define TAGTINKER_PROTO_DM 0x85
|
||||
#define TAGTINKER_PROTO_SEG 0x84
|
||||
@@ -46,6 +47,28 @@ typedef struct {
|
||||
bool known;
|
||||
} TagTinkerTagProfile;
|
||||
|
||||
static inline bool tagtinker_profile_needs_wh_swap(const TagTinkerTagProfile* profile) {
|
||||
return profile && tagtinker_type_needs_wh_swap(profile->type_code);
|
||||
}
|
||||
|
||||
static inline bool tagtinker_profile_uses_ui_page(const TagTinkerTagProfile* profile) {
|
||||
return profile && tagtinker_type_uses_ui_page(profile->type_code);
|
||||
}
|
||||
|
||||
static inline void tagtinker_profile_glass_size(
|
||||
const TagTinkerTagProfile* profile,
|
||||
uint16_t* width,
|
||||
uint16_t* height) {
|
||||
if(!profile || !width || !height) return;
|
||||
if(tagtinker_profile_needs_wh_swap(profile)) {
|
||||
*width = TAGTINKER_COLOR26_GLASS_W;
|
||||
*height = TAGTINKER_COLOR26_GLASS_H;
|
||||
return;
|
||||
}
|
||||
*width = profile->width;
|
||||
*height = profile->height;
|
||||
}
|
||||
|
||||
bool tagtinker_is_barcode_valid(const char* barcode);
|
||||
bool tagtinker_barcode_to_plid(const char* barcode, uint8_t plid[4]);
|
||||
bool tagtinker_barcode_to_type(const char* barcode, uint16_t* type_code);
|
||||
|
||||
@@ -76,10 +76,9 @@ static void setting_cb(void* ctx, uint32_t index) {
|
||||
void tagtinker_scene_size_picker_on_enter(void* ctx) {
|
||||
TagTinkerApp* app = ctx;
|
||||
app->signal_mode = TagTinkerSignalPP4;
|
||||
if(app->selected_target >= 0 &&
|
||||
app->selected_target < app->target_count &&
|
||||
app->targets[app->selected_target].profile.type_code == 1626) {
|
||||
if(app->img_page == 0U || app->img_page == 1U) app->img_page = 2U;
|
||||
if(app->selected_target >= 0 && app->selected_target < app->target_count &&
|
||||
tagtinker_profile_uses_ui_page(&app->targets[app->selected_target].profile)) {
|
||||
app->img_page = tagtinker_color26_resolve_page(app->img_page);
|
||||
} else if(app->img_page == 0U) {
|
||||
app->img_page = 1U;
|
||||
}
|
||||
|
||||
@@ -90,11 +90,14 @@ static void dropped_images_load(TagTinkerApp* app) {
|
||||
if(file_info_is_dir(&info)) continue;
|
||||
|
||||
TagTinkerSyncedImage entry;
|
||||
uint16_t tw = target->profile.width;
|
||||
uint16_t th = target->profile.height;
|
||||
tagtinker_profile_glass_size(&target->profile, &tw, &th);
|
||||
if(!tagtinker_parse_dropped_filename(
|
||||
name,
|
||||
target->barcode,
|
||||
target->profile.width,
|
||||
target->profile.height,
|
||||
tw,
|
||||
th,
|
||||
&entry)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,9 @@ static void show_target_details(TagTinkerApp* app, const TagTinkerTarget* target
|
||||
text_box_set_focus(app->text_box, TextBoxFocusStart);
|
||||
|
||||
static char details_buf[256];
|
||||
uint16_t size_w = target->profile.width;
|
||||
uint16_t size_h = target->profile.height;
|
||||
tagtinker_profile_glass_size(&target->profile, &size_w, &size_h);
|
||||
snprintf(
|
||||
details_buf,
|
||||
sizeof(details_buf),
|
||||
@@ -41,8 +44,8 @@ static void show_target_details(TagTinkerApp* app, const TagTinkerTarget* target
|
||||
target->profile.model_name ? target->profile.model_name : "Unknown",
|
||||
target->profile.type_code,
|
||||
tagtinker_profile_kind_label(target->profile.kind),
|
||||
target->profile.width,
|
||||
target->profile.height,
|
||||
size_w,
|
||||
size_h,
|
||||
tagtinker_profile_color_label(target->profile.color),
|
||||
target->barcode);
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <furi_hal.h>
|
||||
#include <storage/storage.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct {
|
||||
TagTinkerApp* app;
|
||||
@@ -29,15 +30,11 @@ typedef struct {
|
||||
* shot instead of streaming in chunks. 49152 covers 208×112×2 = 46592
|
||||
* pixels (the most common color DM images). */
|
||||
#define TX_FULL_JOB_PIXEL_LIMIT 49152U
|
||||
|
||||
/* Color 2.6 glass is landscape 296x152. Firmware image header is 152x296
|
||||
* (short x long). Same pixel count, axes swapped. */
|
||||
#define TX_COLOR26_PROTO_W 152U
|
||||
#define TX_COLOR26_PROTO_H 296U
|
||||
#define TX_COLOR26_GLASS_W 296U
|
||||
#define TX_COLOR26_GLASS_H 152U
|
||||
#define TX_COLOR26_BMP_MAX 24576U
|
||||
#define TX_COLOR26_WAKE_REPEATS 400U
|
||||
#define TX_COLOR26_BMP_MAX 24576U
|
||||
#define TX_COLOR26_TEXT_ROWS 16U
|
||||
#define TX_COLOR26_PACKED_BYTES \
|
||||
(((size_t)TAGTINKER_COLOR26_GLASS_W * (size_t)TAGTINKER_COLOR26_GLASS_H + 7U) / 8U)
|
||||
|
||||
static uint16_t tx_pick_chunk_height(uint16_t width, uint16_t height, bool second_plane);
|
||||
|
||||
@@ -59,11 +56,15 @@ static TagTinkerTagColor tx_target_color(const TagTinkerApp* app) {
|
||||
return app->targets[app->selected_target].profile.color;
|
||||
}
|
||||
|
||||
static bool tx_is_color26(const TagTinkerApp* app) {
|
||||
static const TagTinkerTagProfile* tx_target_profile(const TagTinkerApp* app) {
|
||||
if(app->selected_target < 0 || app->selected_target >= app->target_count) {
|
||||
return false;
|
||||
return NULL;
|
||||
}
|
||||
return app->targets[app->selected_target].profile.type_code == 1626;
|
||||
return &app->targets[app->selected_target].profile;
|
||||
}
|
||||
|
||||
static bool tx_is_color26(const TagTinkerApp* app) {
|
||||
return tagtinker_profile_needs_wh_swap(tx_target_profile(app));
|
||||
}
|
||||
|
||||
static bool tx_send_frame(TagTinkerApp* app, const uint8_t* frame, size_t len, uint16_t repeats) {
|
||||
@@ -101,8 +102,8 @@ static bool tx_send_color26_payload(TagTinkerApp* app, const TagTinkerImagePaylo
|
||||
(uint16_t)payload->byte_count,
|
||||
payload->comp_type,
|
||||
job->page,
|
||||
TX_COLOR26_PROTO_W,
|
||||
TX_COLOR26_PROTO_H,
|
||||
TAGTINKER_COLOR26_WIRE_W,
|
||||
TAGTINKER_COLOR26_WIRE_H,
|
||||
0,
|
||||
0);
|
||||
ok = tx_send_frame(app, frame, len, 1);
|
||||
@@ -337,22 +338,70 @@ typedef struct {
|
||||
const uint8_t* p2;
|
||||
} TxColor26TextCtx;
|
||||
|
||||
static uint8_t tx_packed_bit(const uint8_t* packed, size_t idx) {
|
||||
return (packed[idx / 8U] >> (7U - (idx % 8U))) & 1U;
|
||||
}
|
||||
|
||||
static void tx_packed_set(uint8_t* packed, size_t idx, uint8_t val) {
|
||||
size_t byte = idx / 8U;
|
||||
uint8_t mask = (uint8_t)(1U << (7U - (idx % 8U)));
|
||||
if(val) packed[byte] |= mask;
|
||||
else packed[byte] &= (uint8_t)~mask;
|
||||
}
|
||||
|
||||
static void tx_pack_region(
|
||||
uint8_t* packed,
|
||||
const uint8_t* region,
|
||||
uint16_t w,
|
||||
uint16_t y0,
|
||||
uint16_t h) {
|
||||
for(uint16_t y = 0; y < h; y++) {
|
||||
for(uint16_t x = 0; x < w; x++) {
|
||||
tx_packed_set(packed, (size_t)(y0 + y) * w + x, region[(size_t)y * w + x]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t tx_color26_text_pixel(size_t idx, void* ctx) {
|
||||
const TxColor26TextCtx* c = ctx;
|
||||
size_t count = (size_t)TX_COLOR26_PROTO_W * (size_t)TX_COLOR26_PROTO_H;
|
||||
size_t count = (size_t)TAGTINKER_COLOR26_WIRE_W * (size_t)TAGTINKER_COLOR26_WIRE_H;
|
||||
uint8_t plane = 0;
|
||||
if(idx >= count) {
|
||||
plane = 1;
|
||||
idx -= count;
|
||||
}
|
||||
uint16_t px = (uint16_t)(idx % TX_COLOR26_PROTO_W);
|
||||
uint16_t py = (uint16_t)(idx / TX_COLOR26_PROTO_W);
|
||||
uint16_t bx = py;
|
||||
uint16_t by = (uint16_t)(TX_COLOR26_PROTO_W - 1U - px);
|
||||
size_t si = (size_t)by * TX_COLOR26_GLASS_W + bx;
|
||||
if(plane == 0) return c->p1[si];
|
||||
uint16_t px = (uint16_t)(idx % TAGTINKER_COLOR26_WIRE_W);
|
||||
uint16_t py = (uint16_t)(idx / TAGTINKER_COLOR26_WIRE_W);
|
||||
uint16_t bx = 0;
|
||||
uint16_t by = 0;
|
||||
tagtinker_color26_proto_to_glass(TAGTINKER_COLOR26_WIRE_W, px, py, &bx, &by);
|
||||
size_t si = (size_t)by * TAGTINKER_COLOR26_GLASS_W + bx;
|
||||
if(plane == 0) return tx_packed_bit(c->p1, si);
|
||||
if(!c->p2) return 1U;
|
||||
return c->p2[si];
|
||||
return tx_packed_bit(c->p2, si);
|
||||
}
|
||||
|
||||
static bool tx_render_color26_text_plane(
|
||||
uint8_t* packed,
|
||||
const char* text,
|
||||
uint8_t bg,
|
||||
uint8_t fg,
|
||||
uint8_t padding_pct) {
|
||||
const uint16_t w = TAGTINKER_COLOR26_GLASS_W;
|
||||
const uint16_t h = TAGTINKER_COLOR26_GLASS_H;
|
||||
memset(packed, bg ? 0xFF : 0x00, TX_COLOR26_PACKED_BYTES);
|
||||
|
||||
uint8_t* row = malloc((size_t)w * TX_COLOR26_TEXT_ROWS);
|
||||
if(!row) return false;
|
||||
|
||||
for(uint16_t y = 0; y < h; y = (uint16_t)(y + TX_COLOR26_TEXT_ROWS)) {
|
||||
uint16_t rh = (uint16_t)(h - y);
|
||||
if(rh > TX_COLOR26_TEXT_ROWS) rh = TX_COLOR26_TEXT_ROWS;
|
||||
render_text_region_ex(row, w, h, y, rh, text, bg, fg, padding_pct);
|
||||
tx_pack_region(packed, row, w, y, rh);
|
||||
}
|
||||
free(row);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool tx_send_color26_text(TagTinkerApp* app) {
|
||||
@@ -361,52 +410,42 @@ static bool tx_send_color26_text(TagTinkerApp* app) {
|
||||
bool accent_capable = tagtinker_target_supports_accent(target);
|
||||
bool accent_text = accent_capable && app->color_clear;
|
||||
TagTinkerTagColor accent_color = tx_target_color(app);
|
||||
size_t count = (size_t)TX_COLOR26_GLASS_W * (size_t)TX_COLOR26_GLASS_H;
|
||||
uint8_t* primary = malloc(count);
|
||||
uint8_t* secondary = accent_text ? malloc(count) : NULL;
|
||||
if(!primary || (accent_text && !secondary)) {
|
||||
uint8_t* primary = malloc(TX_COLOR26_PACKED_BYTES);
|
||||
uint8_t* secondary = NULL;
|
||||
if(!primary) return false;
|
||||
if(accent_text) {
|
||||
secondary = malloc(TX_COLOR26_PACKED_BYTES);
|
||||
if(!secondary) accent_text = false;
|
||||
}
|
||||
|
||||
uint8_t bg = app->invert_text ? 0 : 1;
|
||||
uint8_t fg = app->invert_text ? 1 : 0;
|
||||
bool ok;
|
||||
if(accent_text) {
|
||||
uint8_t fg_primary = (accent_color == TagTinkerTagColorYellow) ? 0 : 1;
|
||||
ok = tx_render_color26_text_plane(
|
||||
primary, app->text_input_buf, bg, fg_primary, app->text_padding_pct);
|
||||
if(ok) {
|
||||
ok = tx_render_color26_text_plane(
|
||||
secondary, app->text_input_buf, 1, 0, app->text_padding_pct);
|
||||
}
|
||||
} else {
|
||||
ok = tx_render_color26_text_plane(
|
||||
primary, app->text_input_buf, bg, fg, app->text_padding_pct);
|
||||
}
|
||||
if(!ok) {
|
||||
free(primary);
|
||||
free(secondary);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(accent_text) {
|
||||
uint8_t bg_primary = app->invert_text ? 0 : 1;
|
||||
uint8_t fg_primary = (accent_color == TagTinkerTagColorYellow) ? 0 : 1;
|
||||
render_text_ex(
|
||||
primary,
|
||||
TX_COLOR26_GLASS_W,
|
||||
TX_COLOR26_GLASS_H,
|
||||
app->text_input_buf,
|
||||
bg_primary,
|
||||
fg_primary,
|
||||
app->text_padding_pct);
|
||||
render_text_ex(
|
||||
secondary,
|
||||
TX_COLOR26_GLASS_W,
|
||||
TX_COLOR26_GLASS_H,
|
||||
app->text_input_buf,
|
||||
1,
|
||||
0,
|
||||
app->text_padding_pct);
|
||||
} else {
|
||||
render_text_ex(
|
||||
primary,
|
||||
TX_COLOR26_GLASS_W,
|
||||
TX_COLOR26_GLASS_H,
|
||||
app->text_input_buf,
|
||||
app->invert_text ? 0 : 1,
|
||||
app->invert_text ? 1 : 0,
|
||||
app->text_padding_pct);
|
||||
}
|
||||
|
||||
TxColor26TextCtx ctx = {.p1 = primary, .p2 = secondary};
|
||||
TagTinkerImagePayload payload;
|
||||
bool ok = tagtinker_encode_fn_payload(
|
||||
ok = tagtinker_encode_fn_payload(
|
||||
tx_color26_text_pixel,
|
||||
&ctx,
|
||||
count * 2U,
|
||||
TagTinkerCompressionAuto,
|
||||
(size_t)TAGTINKER_COLOR26_WIRE_W * (size_t)TAGTINKER_COLOR26_WIRE_H * 2U,
|
||||
app->compression_mode,
|
||||
&payload);
|
||||
free(primary);
|
||||
free(secondary);
|
||||
@@ -610,9 +649,33 @@ typedef struct {
|
||||
const uint8_t* file;
|
||||
size_t file_len;
|
||||
const TxBmpInfo* info;
|
||||
bool transpose;
|
||||
} TxColor26BmpCtx;
|
||||
|
||||
static void tx_color26_src_xy(
|
||||
const TxBmpInfo* info,
|
||||
uint16_t px,
|
||||
uint16_t py,
|
||||
uint16_t* sx,
|
||||
uint16_t* sy) {
|
||||
if(info->width == TAGTINKER_COLOR26_WIRE_W &&
|
||||
info->height == TAGTINKER_COLOR26_WIRE_H) {
|
||||
*sx = px;
|
||||
*sy = py;
|
||||
return;
|
||||
}
|
||||
uint16_t gx = 0;
|
||||
uint16_t gy = 0;
|
||||
tagtinker_color26_proto_to_glass(TAGTINKER_COLOR26_WIRE_W, px, py, &gx, &gy);
|
||||
if(info->width == TAGTINKER_COLOR26_GLASS_W &&
|
||||
info->height == TAGTINKER_COLOR26_GLASS_H) {
|
||||
*sx = gx;
|
||||
*sy = gy;
|
||||
return;
|
||||
}
|
||||
*sx = bmp_map_x(gx, TAGTINKER_COLOR26_GLASS_W, info->width);
|
||||
*sy = bmp_map_y(gy, TAGTINKER_COLOR26_GLASS_H, info->height);
|
||||
}
|
||||
|
||||
static uint8_t tx_color26_bmp_bit(
|
||||
const TxColor26BmpCtx* c,
|
||||
uint16_t bx,
|
||||
@@ -632,28 +695,37 @@ static uint8_t tx_color26_bmp_bit(
|
||||
|
||||
static uint8_t tx_color26_bmp_pixel(size_t idx, void* ctx) {
|
||||
const TxColor26BmpCtx* c = ctx;
|
||||
size_t count = (size_t)TX_COLOR26_PROTO_W * (size_t)TX_COLOR26_PROTO_H;
|
||||
size_t count = (size_t)TAGTINKER_COLOR26_WIRE_W * (size_t)TAGTINKER_COLOR26_WIRE_H;
|
||||
uint8_t plane = 0;
|
||||
if(idx >= count) {
|
||||
plane = 1;
|
||||
idx -= count;
|
||||
}
|
||||
uint16_t px = (uint16_t)(idx % TX_COLOR26_PROTO_W);
|
||||
uint16_t py = (uint16_t)(idx / TX_COLOR26_PROTO_W);
|
||||
uint16_t bx = px;
|
||||
uint16_t by = py;
|
||||
if(c->transpose) {
|
||||
bx = py;
|
||||
by = (uint16_t)(TX_COLOR26_PROTO_W - 1U - px);
|
||||
}
|
||||
uint16_t px = (uint16_t)(idx % TAGTINKER_COLOR26_WIRE_W);
|
||||
uint16_t py = (uint16_t)(idx / TAGTINKER_COLOR26_WIRE_W);
|
||||
uint16_t bx = 0;
|
||||
uint16_t by = 0;
|
||||
tx_color26_src_xy(c->info, px, py, &bx, &by);
|
||||
if(plane && c->info->bpp != 2) return 1U;
|
||||
return tx_color26_bmp_bit(c, bx, by, plane);
|
||||
}
|
||||
|
||||
static bool tx_send_color26_bmp(TagTinkerApp* app, File* file, const TxBmpInfo* info) {
|
||||
if(info->bpp != 1 && info->bpp != 2) return false;
|
||||
if(info->bpp != 1 && info->bpp != 2) {
|
||||
FURI_LOG_W(
|
||||
TAGTINKER_TAG,
|
||||
"Color 2.6 BMP bpp=%u not supported (need 1 or 2)",
|
||||
info->bpp);
|
||||
return false;
|
||||
}
|
||||
uint64_t sz = storage_file_size(file);
|
||||
if(sz < 54U || sz > TX_COLOR26_BMP_MAX) return false;
|
||||
if(sz < 54U || sz > TX_COLOR26_BMP_MAX) {
|
||||
FURI_LOG_W(
|
||||
TAGTINKER_TAG,
|
||||
"Color 2.6 BMP size %lu not supported",
|
||||
(unsigned long)sz);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t* mem = malloc((size_t)sz);
|
||||
if(!mem) return false;
|
||||
@@ -668,26 +740,17 @@ static bool tx_send_color26_bmp(TagTinkerApp* app, File* file, const TxBmpInfo*
|
||||
.file = mem,
|
||||
.file_len = (size_t)sz,
|
||||
.info = info,
|
||||
.transpose = (info->width == TX_COLOR26_GLASS_W && info->height == TX_COLOR26_GLASS_H),
|
||||
};
|
||||
TagTinkerImagePayload payload;
|
||||
bool ok = tagtinker_encode_fn_payload(
|
||||
tx_color26_bmp_pixel,
|
||||
&ctx,
|
||||
(size_t)TX_COLOR26_PROTO_W * (size_t)TX_COLOR26_PROTO_H * 2U,
|
||||
TagTinkerCompressionAuto,
|
||||
(size_t)TAGTINKER_COLOR26_WIRE_W * (size_t)TAGTINKER_COLOR26_WIRE_H * 2U,
|
||||
app->compression_mode,
|
||||
&payload);
|
||||
free(mem);
|
||||
if(!ok) return false;
|
||||
|
||||
tx_debug_log(
|
||||
"Color26 BMP %ux%u bpp=%u transpose=%u bytes=%u pg=%u",
|
||||
info->width,
|
||||
info->height,
|
||||
info->bpp,
|
||||
ctx.transpose ? 1U : 0U,
|
||||
(unsigned)payload.byte_count,
|
||||
app->image_tx_job.page);
|
||||
ok = tx_send_color26_payload(app, &payload);
|
||||
tagtinker_free_image_payload(&payload);
|
||||
return ok;
|
||||
|
||||
@@ -289,13 +289,10 @@ static void start_run(TagTinkerApp* app) {
|
||||
* else fallback to a reasonable sane size. */
|
||||
uint16_t tw = app->esl_width ? app->esl_width : 296;
|
||||
uint16_t th = app->esl_height ? app->esl_height : 128;
|
||||
/* Honour the tag's accent capability: red/yellow profiles get the
|
||||
* accent plane, mono profiles stay mono. The BMP writer + the IR TX
|
||||
* pipeline already understand 2-plane BMPs (same convention as the
|
||||
* web image prep tool), so plugins can use the accent freely. */
|
||||
uint8_t accent = TT_ACCENT_NONE;
|
||||
if(app->selected_target >= 0 && app->selected_target < app->target_count) {
|
||||
const TagTinkerTarget* t = &app->targets[app->selected_target];
|
||||
tagtinker_profile_glass_size(&t->profile, &tw, &th);
|
||||
if(tagtinker_target_supports_accent(t)) {
|
||||
accent = (t->profile.color == TagTinkerTagColorYellow)
|
||||
? TT_ACCENT_YELLOW
|
||||
|
||||
+15
-14
@@ -354,15 +354,18 @@ void tagtinker_prepare_text_tx(TagTinkerApp* app, const uint8_t plid[4]) {
|
||||
memset(&app->image_tx_job, 0, sizeof(app->image_tx_job));
|
||||
app->image_tx_job.mode = TagTinkerTxModeTextImage;
|
||||
memcpy(app->image_tx_job.plid, plid, sizeof(app->image_tx_job.plid));
|
||||
/* HD tags map UI page 1-8 to protocol 0-7. Color 2.6 uses the UI
|
||||
* page as the protocol page (store image slot is page 2). */
|
||||
if(app->selected_target >= 0 &&
|
||||
app->selected_target < app->target_count &&
|
||||
app->targets[app->selected_target].profile.type_code == 1626) {
|
||||
app->image_tx_job.page = app->img_page;
|
||||
if(app->image_tx_job.page < 1U) app->image_tx_job.page = 1U;
|
||||
} else {
|
||||
app->image_tx_job.page = (app->img_page > 0U) ? (uint8_t)(app->img_page - 1U) : 0U;
|
||||
/* HD tags map UI page 1-8 to protocol 0-7. Color 2.6 uses the UI page
|
||||
* number on the wire. Unspecified 0/1 become page 2 (image slot). */
|
||||
{
|
||||
const TagTinkerTagProfile* profile = NULL;
|
||||
if(app->selected_target >= 0 && app->selected_target < app->target_count) {
|
||||
profile = &app->targets[app->selected_target].profile;
|
||||
}
|
||||
if(tagtinker_profile_uses_ui_page(profile)) {
|
||||
app->image_tx_job.page = tagtinker_color26_resolve_page(app->img_page);
|
||||
} else {
|
||||
app->image_tx_job.page = (app->img_page > 0U) ? (uint8_t)(app->img_page - 1U) : 0U;
|
||||
}
|
||||
}
|
||||
if(app->image_tx_job.page > 7U) app->image_tx_job.page = 7U;
|
||||
app->image_tx_job.width = app->esl_width;
|
||||
@@ -391,11 +394,9 @@ void tagtinker_prepare_bmp_tx(
|
||||
app->image_tx_job.mode = TagTinkerTxModeBmpImage;
|
||||
memcpy(app->image_tx_job.plid, plid, sizeof(app->image_tx_job.plid));
|
||||
app->image_tx_job.page = page;
|
||||
if(app->selected_target >= 0 &&
|
||||
app->selected_target < app->target_count &&
|
||||
app->targets[app->selected_target].profile.type_code == 1626 &&
|
||||
app->image_tx_job.page == 0U) {
|
||||
app->image_tx_job.page = 2U;
|
||||
if(app->selected_target >= 0 && app->selected_target < app->target_count &&
|
||||
tagtinker_profile_uses_ui_page(&app->targets[app->selected_target].profile)) {
|
||||
app->image_tx_job.page = tagtinker_color26_resolve_page(page);
|
||||
}
|
||||
app->image_tx_job.width = width;
|
||||
app->image_tx_job.height = height;
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
#include "../protocol/tagtinker_color26.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static int failures = 0;
|
||||
|
||||
static void expect_u8(const char* name, uint8_t got, uint8_t want) {
|
||||
if(got == want) return;
|
||||
fprintf(stderr, "FAIL %s: got %u want %u\n", name, got, want);
|
||||
failures++;
|
||||
}
|
||||
|
||||
static void expect_xy(
|
||||
const char* name,
|
||||
uint16_t gx,
|
||||
uint16_t gy,
|
||||
uint16_t want_x,
|
||||
uint16_t want_y) {
|
||||
if(gx == want_x && gy == want_y) return;
|
||||
fprintf(stderr, "FAIL %s: got %u,%u want %u,%u\n", name, gx, gy, want_x, want_y);
|
||||
failures++;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
expect_u8("page 0", tagtinker_color26_resolve_page(0), 2);
|
||||
expect_u8("page 1", tagtinker_color26_resolve_page(1), 2);
|
||||
expect_u8("page 2", tagtinker_color26_resolve_page(2), 2);
|
||||
expect_u8("page 3", tagtinker_color26_resolve_page(3), 3);
|
||||
expect_u8("page 7", tagtinker_color26_resolve_page(7), 7);
|
||||
expect_u8("page 8", tagtinker_color26_resolve_page(8), 7);
|
||||
expect_u8("page 9", tagtinker_color26_resolve_page(9), 7);
|
||||
|
||||
/* 2x3 wire, 3x2 glass. bx = py, by = proto_w - 1 - px. */
|
||||
const uint16_t proto_w = 2;
|
||||
struct {
|
||||
uint16_t px, py, bx, by;
|
||||
} cases[] = {
|
||||
{0, 0, 0, 1},
|
||||
{1, 0, 0, 0},
|
||||
{0, 1, 1, 1},
|
||||
{1, 1, 1, 0},
|
||||
{0, 2, 2, 1},
|
||||
{1, 2, 2, 0},
|
||||
};
|
||||
for(size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
|
||||
uint16_t bx = 99, by = 99;
|
||||
tagtinker_color26_proto_to_glass(proto_w, cases[i].px, cases[i].py, &bx, &by);
|
||||
char name[32];
|
||||
snprintf(name, sizeof(name), "map %u,%u", cases[i].px, cases[i].py);
|
||||
expect_xy(name, bx, by, cases[i].bx, cases[i].by);
|
||||
}
|
||||
|
||||
if(!tagtinker_type_needs_wh_swap(TAGTINKER_TYPE_SMARTAG_COLOR_26)) {
|
||||
fprintf(stderr, "FAIL type 1626 should swap W/H\n");
|
||||
failures++;
|
||||
}
|
||||
if(tagtinker_type_needs_wh_swap(1627)) {
|
||||
fprintf(stderr, "FAIL type 1627 should not swap W/H\n");
|
||||
failures++;
|
||||
}
|
||||
|
||||
if(failures) {
|
||||
fprintf(stderr, "%d failure(s)\n", failures);
|
||||
return 1;
|
||||
}
|
||||
printf("ok\n");
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user