GBA e-Reader: Add visual dotcode parsing

This commit is contained in:
Vicki Pfau 2021-04-10 03:05:24 -07:00
parent adfc3c4ab2
commit a0360ec936
3 changed files with 836 additions and 1 deletions

View File

@ -100,6 +100,19 @@ void GBASIOBattlechipGateCreate(struct GBASIOBattlechipGate*);
void GBACartEReaderQueueCard(struct GBA* gba, const void* data, size_t size);
struct EReaderScan;
#ifdef USE_PNG
MGBA_EXPORT struct EReaderScan* EReaderScanLoadImagePNG(const char* filename);
#endif
MGBA_EXPORT struct EReaderScan* EReaderScanLoadImage(const void* pixels, unsigned width, unsigned height, unsigned stride);
MGBA_EXPORT struct EReaderScan* EReaderScanLoadImageA(const void* pixels, unsigned width, unsigned height, unsigned stride);
MGBA_EXPORT struct EReaderScan* EReaderScanLoadImage8(const void* pixels, unsigned width, unsigned height, unsigned stride);
MGBA_EXPORT void EReaderScanDestroy(struct EReaderScan*);
MGBA_EXPORT bool EReaderScanCard(struct EReaderScan*);
MGBA_EXPORT void EReaderScanOutputBitmap(const struct EReaderScan*, void* output, size_t stride);
MGBA_EXPORT bool EReaderScanSaveRaw(const struct EReaderScan*, const char* filename, bool strict);
CXX_GUARD_END
#endif

View File

@ -10,6 +10,8 @@
CXX_GUARD_START
#include <mgba-util/vector.h>
struct GBACartridgeHardware;
#define EREADER_DOTCODE_STRIDE 1420
@ -77,6 +79,31 @@ struct GBACartEReader {
struct EReaderCard cards[EREADER_CARDS_MAX];
};
struct EReaderAnchor;
struct EReaderBlock;
DECLARE_VECTOR(EReaderAnchorList, struct EReaderAnchor);
DECLARE_VECTOR(EReaderBlockList, struct EReaderBlock);
struct EReaderScan {
uint8_t* buffer;
unsigned width;
unsigned height;
uint8_t* srcBuffer;
size_t srcWidth;
size_t srcHeight;
unsigned scale;
uint8_t min;
uint8_t max;
uint8_t mean;
uint8_t anchorThreshold;
struct EReaderAnchorList anchors;
struct EReaderBlockList blocks;
};
void GBACartEReaderInit(struct GBACartEReader* ereader);
void GBACartEReaderDeinit(struct GBACartEReader* ereader);
void GBACartEReaderWrite(struct GBACartEReader* ereader, uint32_t address, uint16_t value);
@ -85,6 +112,15 @@ uint16_t GBACartEReaderRead(struct GBACartEReader* ereader, uint32_t address);
uint8_t GBACartEReaderReadFlash(struct GBACartEReader* ereader, uint32_t address);
void GBACartEReaderScan(struct GBACartEReader* ereader, const void* data, size_t size);
struct EReaderScan* EReaderScanCreate(unsigned width, unsigned height);
void EReaderScanDetectParams(struct EReaderScan*);
void EReaderScanDetectAnchors(struct EReaderScan*);
void EReaderScanFilterAnchors(struct EReaderScan*);
void EReaderScanConnectAnchors(struct EReaderScan*);
void EReaderScanCreateBlocks(struct EReaderScan*);
void EReaderScanDetectBlockThreshold(struct EReaderScan*, int block);
bool EReaderScanScanBlock(struct EReaderScan*, int block);
CXX_GUARD_END
#endif

View File

@ -10,6 +10,16 @@
#include <mgba/internal/gba/gba.h>
#include <mgba-util/memory.h>
#ifdef USE_FFMPEG
#include <mgba-util/convolve.h>
#ifdef USE_PNG
#include <mgba-util/png-io.h>
#include <mgba-util/vfs.h>
#endif
#include "feature/ffmpeg/ffmpeg-scale.h"
#endif
#define EREADER_BLOCK_SIZE 40
static void _eReaderReset(struct GBACartEReader* ereader);
@ -38,6 +48,13 @@ const int EREADER_NYBBLE_5BIT[16][5] = {
{ 1, 0, 0, 0, 0 }
};
const int EREADER_NYBBLE_LOOKUP[32] = {
0, 1, 2, -1, 4, 5, 6, -1,
8, 9, 10, -1, 12, 13, -1, -1,
15, 14, 3, -1, 11, -1, 7, -1,
-1, -1, -1, -1, -1, -1, -1, -1
};
const uint8_t EREADER_CALIBRATION_TEMPLATE[] = {
0x43, 0x61, 0x72, 0x64, 0x2d, 0x45, 0x20, 0x52, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x32, 0x30,
0x30, 0x31, 0x00, 0x00, 0xcf, 0x72, 0x2f, 0x37, 0x3a, 0x3a, 0x3a, 0x38, 0x33, 0x30, 0x30, 0x37,
@ -418,7 +435,7 @@ void GBACartEReaderScan(struct GBACartEReader* ereader, const void* data, size_t
origin[x * 8 + 6] = (byte >> 1) & 1;
origin[x * 8 + 7] = byte & 1;
}
}
}
return;
}
@ -736,3 +753,772 @@ void GBACartEReaderQueueCard(struct GBA* gba, const void* data, size_t size) {
return;
}
}
#ifdef USE_FFMPEG
struct EReaderAnchor {
float x;
float y;
float top;
float bottom;
float left;
float right;
size_t nNeighbors;
struct EReaderAnchor** neighbors;
};
struct EReaderBlock {
float x[4];
float y[4];
uint8_t rawdots[36 * 36];
unsigned histogram[256];
uint8_t threshold;
uint8_t min;
uint8_t max;
bool dots[36 * 36];
uint16_t id;
uint16_t next;
};
DEFINE_VECTOR(EReaderAnchorList, struct EReaderAnchor);
DEFINE_VECTOR(EReaderBlockList, struct EReaderBlock);
static void _eReaderScanDownsample(struct EReaderScan* scan) {
// TODO: Replace this logic with a value based on total area
scan->scale = 400;
if (scan->srcWidth > scan->srcHeight) {
scan->height = 400;
scan->width = scan->srcWidth * 400 / scan->srcHeight;
} else {
scan->width = 400;
scan->height = scan->srcHeight * 400 / scan->srcWidth;
}
scan->buffer = malloc(scan->width * scan->height);
FFmpegScale(scan->srcBuffer, scan->srcWidth, scan->srcHeight, scan->srcWidth, scan->buffer, scan->width, scan->height, scan->width, mCOLOR_L8, 3);
free(scan->srcBuffer);
scan->srcBuffer = NULL;
}
static int _compareAnchors(const void* va, const void* vb) {
const struct EReaderAnchor* a = va;
const struct EReaderAnchor* b = vb;
float x = a->x - b->x;
float y = a->y - b->y;
float w = a->right - a->left + b->right - b->left;
float h = a->bottom - a->top + b->bottom - b->top;
if (x < -w) {
return -1;
}
if (x > w) {
return 1;
}
if (y < -h) {
return -1;
}
if (y > h) {
return 1;
}
return 0;
}
static int _compareBlocks(const void* va, const void* vb) {
const struct EReaderBlock* a = va;
const struct EReaderBlock* b = vb;
if (a->id < b->id) {
return -1;
}
if (a->id > b->id) {
return 1;
}
return 0;
}
struct EReaderScan* EReaderScanCreate(unsigned width, unsigned height) {
struct EReaderScan* scan = calloc(1, sizeof(*scan));
scan->srcWidth = width;
scan->srcHeight = height;
scan->srcBuffer = calloc(width, height);
scan->min = 255;
scan->max = 0;
scan->mean = 128;
scan->anchorThreshold = 128;
EReaderAnchorListInit(&scan->anchors, 64);
EReaderBlockListInit(&scan->blocks, 32);
return scan;
}
void EReaderScanDestroy(struct EReaderScan* scan) {
free(scan->buffer);
size_t i;
for (i = 0; i < EReaderAnchorListSize(&scan->anchors); ++i) {
struct EReaderAnchor* anchor = EReaderAnchorListGetPointer(&scan->anchors, i);
if (anchor->neighbors) {
free(anchor->neighbors);
}
}
EReaderAnchorListDeinit(&scan->anchors);
EReaderBlockListDeinit(&scan->blocks);
free(scan);
}
#ifdef USE_PNG
struct EReaderScan* EReaderScanLoadImagePNG(const char* filename) {
struct VFile* vf = VFileOpen(filename, O_RDONLY);
if (!vf) {
return NULL;
}
png_structp png = PNGReadOpen(vf, 0);
if (!png) {
vf->close(vf);
return NULL;
}
png_infop info = png_create_info_struct(png);
png_infop end = png_create_info_struct(png);
PNGReadHeader(png, info);
unsigned height = png_get_image_height(png, info);
unsigned width = png_get_image_width(png, info);
int type = png_get_color_type(png, info);
int depth = png_get_bit_depth(png, info);
void* image = NULL;
switch (type) {
case PNG_COLOR_TYPE_RGB:
if (depth != 8) {
break;
}
image = malloc(height * width * 3);
PNGReadPixels(png, info, image, width, height, width);
break;
case PNG_COLOR_TYPE_RGBA:
if (depth != 8) {
break;
}
image = malloc(height * width * 4);
PNGReadPixelsA(png, info, image, width, height, width);
break;
default:
break;
}
PNGReadFooter(png, end);
PNGReadClose(png, info, end);
vf->close(vf);
if (!image) {
return NULL;
}
struct EReaderScan* scan = NULL;
switch (type) {
case PNG_COLOR_TYPE_RGB:
scan = EReaderScanLoadImage(image, width, height, width);
break;
case PNG_COLOR_TYPE_RGBA:
scan = EReaderScanLoadImageA(image, width, height, width);
break;
default:
break;
}
free(image);
return scan;
}
#endif
struct EReaderScan* EReaderScanLoadImage(const void* pixels, unsigned width, unsigned height, unsigned stride) {
struct EReaderScan* scan = EReaderScanCreate(width, height);
unsigned y;
for (y = 0; y < height; ++y) {
const uint8_t* irow = pixels;
uint8_t* orow = &scan->srcBuffer[y * width];
irow = &irow[y * stride];
unsigned x;
for (x = 0; x < width; ++x) {
orow[x] = irow[x * 3 + 2];
}
}
_eReaderScanDownsample(scan);
return scan;
}
struct EReaderScan* EReaderScanLoadImageA(const void* pixels, unsigned width, unsigned height, unsigned stride) {
struct EReaderScan* scan = EReaderScanCreate(width, height);
unsigned y;
for (y = 0; y < height; ++y) {
const uint8_t* irow = pixels;
uint8_t* orow = &scan->srcBuffer[y * width];
irow = &irow[y * stride];
unsigned x;
for (x = 0; x < width; ++x) {
orow[x] = irow[x * 4 + 2];
}
}
_eReaderScanDownsample(scan);
return scan;
}
struct EReaderScan* EReaderScanLoadImage8(const void* pixels, unsigned width, unsigned height, unsigned stride) {
struct EReaderScan* scan = EReaderScanCreate(width, height);
unsigned y;
for (y = 0; y < height; ++y) {
const uint8_t* row = pixels;
row = &row[y * stride];
memcpy(&scan->srcBuffer[y * width], row, width);
}
_eReaderScanDownsample(scan);
return scan;
}
void EReaderScanDetectParams(struct EReaderScan* scan) {
size_t sum = 0;
unsigned y;
for (y = 0; y < scan->height; ++y) {
const uint8_t* row = &scan->buffer[scan->width * y];
unsigned x;
for (x = 0; x < scan->width; ++x) {
uint8_t color = row[x];
sum += color;
if (color < scan->min) {
scan->min = color;
}
if (color > scan->max) {
scan->max = color;
}
}
}
scan->mean = sum / (scan->width * scan->height);
scan->anchorThreshold = 2 * (scan->mean - scan->min) / 5 + scan->min;
}
void EReaderScanDetectAnchors(struct EReaderScan* scan) {
uint8_t* output = malloc(scan->width * scan->height);
unsigned dim = scan->scale;
// TODO: Codify this magic constant
size_t dims[] = {dim / 30, dim / 30};
struct ConvolutionKernel kern;
ConvolutionKernelCreate(&kern, 2, dims);
ConvolutionKernelFillRadial(&kern, true);
Convolve2DClampPacked8(scan->buffer, output, scan->width, scan->height, scan->width, &kern);
ConvolutionKernelDestroy(&kern);
unsigned y;
for (y = 0; y < scan->height; ++y) {
const uint8_t* row = &output[scan->width * y];
unsigned x;
for (x = 0; x < scan->width; ++x) {
uint8_t color = row[x];
if (color < scan->anchorThreshold) {
bool mustInsert = true;
size_t i;
for (i = 0; i < EReaderAnchorListSize(&scan->anchors); ++i) {
struct EReaderAnchor* anchor = EReaderAnchorListGetPointer(&scan->anchors, i);
float diffX = anchor->x - x;
float diffY = anchor->y - y;
float distance = hypotf(diffX, diffY);
float radius = sqrtf((anchor->right - anchor->left) * (anchor->bottom - anchor->top)) / 2.f; // TODO: This should be M_PI, not 2
if (radius + dim / 45.f > distance) { // TODO: Codify this magic constant
if (x < anchor->left) {
anchor->left = x;
}
if (x > anchor->right) {
anchor->right = x;
}
if (y < anchor->top) {
anchor->top = y;
}
if (y > anchor->bottom) {
anchor->bottom = y;
}
anchor->x = (anchor->left + anchor->right) / 2.f;
anchor->y = (anchor->bottom + anchor->top) / 2.f;
mustInsert = false;
break;
}
}
if (mustInsert) {
struct EReaderAnchor* anchor = EReaderAnchorListAppend(&scan->anchors);
anchor->neighbors = NULL;
anchor->left = x - 0.5f;
anchor->right = x + 0.5f;
anchor->x = x;
anchor->top = y - 0.5f;
anchor->bottom = y + 0.5f;
anchor->y = y;
}
}
}
}
free(output);
}
void EReaderScanFilterAnchors(struct EReaderScan* scan) {
unsigned dim = scan->scale;
float areaMean = 0;
size_t i;
for (i = 0; i < EReaderAnchorListSize(&scan->anchors); ++i) {
struct EReaderAnchor* anchor = EReaderAnchorListGetPointer(&scan->anchors, i);
float width = anchor->right - anchor->left;
float height = anchor->bottom - anchor->top;
float area = width * height;
// TODO: Codify these magic constants
float portion = dim / sqrtf(area);
bool keep = portion > 9. && portion < 30.;
if (width > height) {
if (width / height > 1.2f) {
keep = false;
}
} else {
if (height / width > 1.2f) {
keep = false;
}
}
if (!keep) {
EReaderAnchorListShift(&scan->anchors, i, 1);
--i;
} else {
areaMean += portion;
}
}
areaMean /= EReaderAnchorListSize(&scan->anchors);
for (i = 0; i < EReaderAnchorListSize(&scan->anchors); ++i) {
struct EReaderAnchor* anchor = EReaderAnchorListGetPointer(&scan->anchors, i);
float area = (anchor->right - anchor->left) * (anchor->bottom - anchor->top);
// TODO: Codify these magic constants
float portion = dim / sqrtf(area);
if (fabsf(portion - areaMean) / areaMean > 0.5) {
EReaderAnchorListShift(&scan->anchors, i, 1);
--i;
}
}
qsort(EReaderAnchorListGetPointer(&scan->anchors, 0), EReaderAnchorListSize(&scan->anchors), sizeof(struct EReaderAnchor), _compareAnchors);
}
void EReaderScanConnectAnchors(struct EReaderScan* scan) {
size_t i;
for (i = 0; i < EReaderAnchorListSize(&scan->anchors); ++i) {
struct EReaderAnchor* anchor = EReaderAnchorListGetPointer(&scan->anchors, i);
float closest = scan->scale * 2.f;
float threshold;
size_t j;
for (j = 0; j < EReaderAnchorListSize(&scan->anchors); ++j) {
if (i == j) {
continue;
}
struct EReaderAnchor* candidate = EReaderAnchorListGetPointer(&scan->anchors, j);
float dx = anchor->x - candidate->x;
float dy = anchor->y - candidate->y;
float distance = hypotf(dx, dy);
if (distance < closest) {
closest = distance;
threshold = 1.25 * closest;
}
}
if (closest >= scan->scale) {
continue;
}
if (anchor->neighbors) {
free(anchor->neighbors);
}
// This is an intentional over-estimate which we prune down later
anchor->neighbors = calloc(EReaderAnchorListSize(&scan->anchors) - 1, sizeof(struct EReaderAnchor*));
size_t matches = 0;
for (j = 0; j < EReaderAnchorListSize(&scan->anchors); ++j) {
if (i == j) {
continue;
}
struct EReaderAnchor* candidate = EReaderAnchorListGetPointer(&scan->anchors, j);
float dx = anchor->x - candidate->x;
float dy = anchor->y - candidate->y;
float distance = hypotf(dx, dy);
if (distance <= threshold) {
anchor->neighbors[matches] = candidate;
++matches;
}
}
if (matches) {
anchor->neighbors = realloc(anchor->neighbors, matches * sizeof(struct EReaderAnchor*));
anchor->nNeighbors = matches;
} else {
free(anchor->neighbors);
anchor->neighbors = NULL;
}
}
}
void EReaderScanCreateBlocks(struct EReaderScan* scan) {
size_t i;
for (i = 0; i < EReaderAnchorListSize(&scan->anchors); ++i) {
struct EReaderAnchor* anchor = EReaderAnchorListGetPointer(&scan->anchors, i);
if (anchor->nNeighbors < 2) {
continue;
}
struct EReaderAnchor* neighbors[3] = {anchor->neighbors[0], anchor->neighbors[1]};
size_t j;
for (j = 0; j < neighbors[0]->nNeighbors; ++j) {
if (neighbors[0]->neighbors[j] == anchor) {
size_t remaining = neighbors[0]->nNeighbors - j - 1;
--neighbors[0]->nNeighbors;
if (neighbors[0]->nNeighbors) {
memmove(&neighbors[0]->neighbors[j], &neighbors[0]->neighbors[j + 1], remaining * sizeof(struct EReaderAnchor*));
}
}
}
for (j = 0; j < neighbors[1]->nNeighbors; ++j) {
if (neighbors[1]->neighbors[j] == anchor) {
size_t remaining = neighbors[1]->nNeighbors - j - 1;
--neighbors[1]->nNeighbors;
if (neighbors[1]->nNeighbors) {
memmove(&neighbors[1]->neighbors[j], &neighbors[1]->neighbors[j + 1], remaining * sizeof(struct EReaderAnchor*));
}
}
}
// TODO: Codify this constant
if (fabsf(neighbors[0]->x - neighbors[1]->x) < 6) {
struct EReaderAnchor* neighbor = neighbors[0];
neighbors[0] = neighbors[1];
neighbors[1] = neighbor;
}
bool found = false;
for (j = 0; j < neighbors[0]->nNeighbors && !found; ++j) {
size_t k;
for (k = 0; k < neighbors[1]->nNeighbors; ++k) {
if (neighbors[0]->neighbors[j] == neighbors[1]->neighbors[k]) {
neighbors[2] = neighbors[0]->neighbors[j];
found = true;
break;
}
}
}
if (!found) {
continue;
}
struct EReaderBlock* block = EReaderBlockListAppend(&scan->blocks);
memset(block, 0, sizeof(*block));
block->x[0] = anchor->x;
block->x[1] = neighbors[0]->x;
block->x[2] = neighbors[1]->x;
block->x[3] = neighbors[2]->x;
block->y[0] = anchor->y;
block->y[1] = neighbors[0]->y;
block->y[2] = neighbors[1]->y;
block->y[3] = neighbors[2]->y;
block->min = scan->min;
block->max = scan->max;
block->threshold = scan->mean;
unsigned y;
for (y = 0; y < 36; ++y) {
unsigned x;
for (x = 0; x < 36; ++x) {
float topX = (block->x[1] - block->x[0]) * x / 35.f + block->x[0];
float topY = (block->y[1] - block->y[0]) * x / 35.f + block->y[0];
float bottomX = (block->x[3] - block->x[2]) * x / 35.f + block->x[2];
float bottomY = (block->y[3] - block->y[2]) * x / 35.f + block->y[2];
unsigned midX = (bottomX - topX) * y / 35.f + topX;
unsigned midY = (bottomY - topY) * y / 35.f + topY;
uint8_t color = scan->buffer[midY * scan->width + midX];
block->rawdots[y * 36 + x] = color;
if ((x >= 5 && x <= 30) || (y >= 5 && y <= 30)) {
++block->histogram[color];
}
}
}
}
}
void EReaderScanDetectBlockThreshold(struct EReaderScan* scan, int blockId) {
if (blockId < 0 || (unsigned) blockId >= EReaderBlockListSize(&scan->blocks)) {
return;
}
struct EReaderBlock* block = EReaderBlockListGetPointer(&scan->blocks, blockId);
unsigned histograms[0xFC] = {0};
unsigned* histogram[] = {
block->histogram,
&histograms[0x00],
&histograms[0x80],
&histograms[0xC0],
&histograms[0xE0],
&histograms[0xF0],
&histograms[0xF8]
};
size_t i;
for (i = 0; i < 256; ++i) {
unsigned baseline = histogram[0][i];
histogram[1][i >> 1] += baseline;
histogram[2][i >> 2] += baseline;
histogram[3][i >> 3] += baseline;
histogram[4][i >> 4] += baseline;
histogram[5][i >> 5] += baseline;
histogram[6][i >> 6] += baseline;
}
int offset = 1;
for (i = 7; i--; offset *= 2) {
if (histogram[i][offset] > histogram[i][offset + 1]) {
++offset;
}
}
block->threshold = offset / 2;
}
bool EReaderScanScanBlock(struct EReaderScan* scan, int blockId) {
if (blockId < 0 || (unsigned) blockId >= EReaderBlockListSize(&scan->blocks)) {
return false;
}
struct EReaderBlock* block = EReaderBlockListGetPointer(&scan->blocks, blockId);
bool dots[36 * 36] = {0};
size_t y;
for (y = 0; y < 36; ++y) {
size_t x;
for (x = 0; x < 36; ++x) {
if ((x < 5 || x > 30) && (y < 5 || y > 30)) {
continue;
}
uint8_t color = block->rawdots[y * 36 + x];
if (color < block->threshold) {
dots[y * 36 + x] = true;
} else {
dots[y * 36 + x] = false;
}
}
}
int horizontal = 0;
int vertical = 0;
static const bool alignment[36] = { 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0 };
int i;
for (i = 3; i < 33; ++i) {
if (horizontal && dots[i] != alignment[i]) {
++horizontal;
}
if (dots[i + 36 * 35] != alignment[i]) {
++horizontal;
}
if (horizontal && dots[i * 36] != alignment[i]) {
++vertical;
}
if (dots[i * 36 + 35] != alignment[i]) {
++vertical;
}
}
if (!!horizontal == !!vertical) {
return false;
}
int sector[2] = {0};
if (!vertical) {
for (i = 5; i < 30; ++i) {
sector[0] |= dots[i] << (29 - i);
sector[1] |= dots[i + 36 * 35] << (29 - i);
}
} else {
for (i = 5; i < 30; ++i) {
sector[0] |= dots[i * 36] << (29 - i);
sector[1] |= dots[i * 36 + 35] << (29 - i);
}
}
int xFlip = 1;
int yFlip = 1;
int xBias = 0;
int yBias = 0;
if ((sector[0] & 0x1FF) == 1) {
yFlip = -1;
yBias = 35;
int s0 = 0;
int s1 = 0;
for (i = 0; i < 16; ++i) {
s0 |= ((sector[0] >> (i + 9)) & 1) << (15 - i);
s1 |= ((sector[1] >> (i + 9)) & 1) << (15 - i);
}
sector[0] = s0;
sector[1] = s1;
}
sector[0] &= 0xFFFF;
sector[1] &= 0xFFFF;
if (sector[1] < sector[0]) {
xFlip = -1;
xBias = 35;
int sector0 = sector[0];
sector[0] = sector[1];
sector[1] = sector0;
}
memset(block->dots, 0, sizeof(block->dots));
if (!horizontal) {
block->id = sector[0];
block->next = sector[1];
int y;
for (y = 0; y < 36; ++y) {
int ty = y * yFlip + yBias;
int x;
for (x = 0; x < 36; ++x) {
int tx = x * xFlip + xBias;
block->dots[ty * 36 + tx] = dots[y * 36 + x];
}
}
} else {
block->id = sector[0];
block->next = sector[1];
int y;
for (y = 0; y < 36; ++y) {
int tx = y * xFlip + xBias;
int x;
for (x = 0; x < 36; ++x) {
int ty = x * yFlip + yBias;
block->dots[ty * 36 + tx] = dots[y * 36 + x];
}
}
}
return true;
}
bool EReaderScanCard(struct EReaderScan* scan) {
EReaderScanDetectParams(scan);
EReaderScanDetectAnchors(scan);
EReaderScanFilterAnchors(scan);
if (EReaderAnchorListSize(&scan->anchors) & 1 || EReaderAnchorListSize(&scan->anchors) < 36) {
return false;
}
EReaderScanConnectAnchors(scan);
EReaderScanCreateBlocks(scan);
size_t blocks = EReaderBlockListSize(&scan->blocks);
size_t i;
for (i = 0; i < blocks; ++i) {
EReaderScanDetectBlockThreshold(scan, i);
if (!EReaderScanScanBlock(scan, i)) {
return false;
}
}
qsort(EReaderBlockListGetPointer(&scan->blocks, 0), EReaderBlockListSize(&scan->blocks), sizeof(struct EReaderBlock), _compareBlocks);
return true;
}
static void _eReaderBitAnchor(uint8_t* output, size_t stride, int offset) {
static const uint8_t anchor[5][5] = {
{ 0, 1, 1, 1, 0 },
{ 1, 1, 1, 1, 1 },
{ 1, 1, 1, 1, 1 },
{ 1, 1, 1, 1, 1 },
{ 0, 1, 1, 1, 0 },
};
size_t y;
for (y = 0; y < 5; ++y) {
uint8_t* line = &output[y * stride];
size_t x;
for (x = 0; x < 5; ++x) {
size_t xo = x + offset;
line[xo >> 3] |= 1 << (~xo & 7);
line[xo >> 3] &= ~(anchor[y][x] << (~xo & 7));
}
}
}
void EReaderScanOutputBitmap(const struct EReaderScan* scan, void* output, size_t stride) {
size_t blocks = EReaderBlockListSize(&scan->blocks);
uint8_t* rows = output;
memset(rows, 0xFF, stride * 44);
size_t i;
size_t y;
for (y = 0; y < 36; ++y) {
uint8_t* line = &rows[(y + 4) * stride];
size_t offset = 4;
for (i = 0; i < blocks; ++i) {
const struct EReaderBlock* block = EReaderBlockListGetConstPointer(&scan->blocks, i);
size_t x;
for (x = 0; x < 35; ++x, ++offset) {
bool dot = block->dots[y * 36 + x];
line[offset >> 3] &= ~(dot << (~offset & 7));
}
if (i + 1 == blocks) {
bool dot = block->dots[y * 36 + 35];
line[offset >> 3] &= ~(dot << (~offset & 7));
}
}
}
for (i = 0; i < blocks + 1; ++i) {
_eReaderBitAnchor(&rows[stride * 2], stride, i * 35 + 2);
_eReaderBitAnchor(&rows[stride * 37], stride, i * 35 + 2);
}
}
bool EReaderScanSaveRaw(const struct EReaderScan* scan, const char* filename, bool strict) {
size_t blocks = EReaderBlockListSize(&scan->blocks);
if (!blocks) {
return false;
}
uint8_t* data = malloc(104 * blocks);
size_t i;
for (i = 0; i < blocks; ++i) {
const struct EReaderBlock* block = EReaderBlockListGetConstPointer(&scan->blocks, i);
uint8_t* datablock = &data[104 * i];
bool bits[1040] = {0};
size_t offset = 0;
size_t y;
for (y = 2; y < 34; ++y) {
size_t x;
for (x = 1; x < 35; ++x) {
if ((x < 5 || x > 30) && (y < 5 || y > 30)) {
continue;
}
bits[offset] = block->dots[y * 36 + x];
++offset;
}
}
for (y = 0; y < 104; ++y) {
int hi = 0;
hi |= bits[y * 10 + 0] << 4;
hi |= bits[y * 10 + 1] << 3;
hi |= bits[y * 10 + 2] << 2;
hi |= bits[y * 10 + 3] << 1;
hi |= bits[y * 10 + 4];
hi = EREADER_NYBBLE_LOOKUP[hi];
int lo = 0;
lo |= bits[y * 10 + 5] << 4;
lo |= bits[y * 10 + 6] << 3;
lo |= bits[y * 10 + 7] << 2;
lo |= bits[y * 10 + 8] << 1;
lo |= bits[y * 10 + 9];
lo = EREADER_NYBBLE_LOOKUP[lo];
if (hi < 0) {
if (strict) {
free(data);
return false;
}
hi = 0xF;
}
if (lo < 0) {
if (strict) {
free(data);
return false;
}
lo = 0xF;
}
datablock[y] = (hi << 4) | lo;
}
}
struct VFile* vf = VFileOpen(filename, O_CREAT | O_WRONLY | O_TRUNC);
if (!vf) {
free(data);
return false;
}
vf->write(vf, data, 104 * blocks);
vf->close(vf);
free(data);
return true;
}
#endif