mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' (early part) into medusa
This commit is contained in:
commit
2c35ccfdfe
2
CHANGES
2
CHANGES
|
@ -34,6 +34,8 @@ Misc:
|
||||||
- DS Core: Backport symbol loading changes from GBA core (fixes mgba.io/i/1834)
|
- DS Core: Backport symbol loading changes from GBA core (fixes mgba.io/i/1834)
|
||||||
|
|
||||||
0.10.0: (Future)
|
0.10.0: (Future)
|
||||||
|
Features:
|
||||||
|
- Tool for converting scanned pictures of e-Reader cards to raw dotcode data
|
||||||
Emulation fixes:
|
Emulation fixes:
|
||||||
- Core: Fix first event scheduling after loading savestate
|
- Core: Fix first event scheduling after loading savestate
|
||||||
- GB Serialize: Fix switching speed modes when loading a state (fixes mgba.io/i/2097)
|
- GB Serialize: Fix switching speed modes when loading a state (fixes mgba.io/i/2097)
|
||||||
|
|
|
@ -100,6 +100,19 @@ void GBASIOBattlechipGateCreate(struct GBASIOBattlechipGate*);
|
||||||
|
|
||||||
void GBACartEReaderQueueCard(struct GBA* gba, const void* data, size_t size);
|
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
|
CXX_GUARD_END
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
|
|
||||||
CXX_GUARD_START
|
CXX_GUARD_START
|
||||||
|
|
||||||
|
#include <mgba-util/vector.h>
|
||||||
|
|
||||||
struct GBACartridgeHardware;
|
struct GBACartridgeHardware;
|
||||||
|
|
||||||
#define EREADER_DOTCODE_STRIDE 1420
|
#define EREADER_DOTCODE_STRIDE 1420
|
||||||
|
@ -77,6 +79,31 @@ struct GBACartEReader {
|
||||||
struct EReaderCard cards[EREADER_CARDS_MAX];
|
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 GBACartEReaderInit(struct GBACartEReader* ereader);
|
||||||
void GBACartEReaderDeinit(struct GBACartEReader* ereader);
|
void GBACartEReaderDeinit(struct GBACartEReader* ereader);
|
||||||
void GBACartEReaderWrite(struct GBACartEReader* ereader, uint32_t address, uint16_t value);
|
void GBACartEReaderWrite(struct GBACartEReader* ereader, uint32_t address, uint16_t value);
|
||||||
|
@ -85,6 +112,16 @@ uint16_t GBACartEReaderRead(struct GBACartEReader* ereader, uint32_t address);
|
||||||
uint8_t GBACartEReaderReadFlash(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);
|
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 EReaderScanRecalibrateBlock(struct EReaderScan*, int block);
|
||||||
|
bool EReaderScanScanBlock(struct EReaderScan*, int block, bool strict);
|
||||||
|
|
||||||
CXX_GUARD_END
|
CXX_GUARD_END
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -10,6 +10,16 @@
|
||||||
#include <mgba/internal/gba/gba.h>
|
#include <mgba/internal/gba/gba.h>
|
||||||
#include <mgba-util/memory.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
|
#define EREADER_BLOCK_SIZE 40
|
||||||
|
|
||||||
static void _eReaderReset(struct GBACartEReader* ereader);
|
static void _eReaderReset(struct GBACartEReader* ereader);
|
||||||
|
@ -38,6 +48,13 @@ const int EREADER_NYBBLE_5BIT[16][5] = {
|
||||||
{ 1, 0, 0, 0, 0 }
|
{ 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[] = {
|
const uint8_t EREADER_CALIBRATION_TEMPLATE[] = {
|
||||||
0x43, 0x61, 0x72, 0x64, 0x2d, 0x45, 0x20, 0x52, 0x65, 0x61, 0x64, 0x65, 0x72, 0x20, 0x32, 0x30,
|
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,
|
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 + 6] = (byte >> 1) & 1;
|
||||||
origin[x * 8 + 7] = byte & 1;
|
origin[x * 8 + 7] = byte & 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -736,3 +753,866 @@ void GBACartEReaderQueueCard(struct GBA* gba, const void* data, size_t size) {
|
||||||
return;
|
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;
|
||||||
|
|
||||||
|
unsigned errors;
|
||||||
|
unsigned missing;
|
||||||
|
unsigned extra;
|
||||||
|
|
||||||
|
bool hFlip;
|
||||||
|
bool vFlip;
|
||||||
|
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[0xF8] = {0};
|
||||||
|
unsigned* histogram[] = {
|
||||||
|
block->histogram,
|
||||||
|
&histograms[0x00],
|
||||||
|
&histograms[0x80],
|
||||||
|
&histograms[0xC0],
|
||||||
|
&histograms[0xE0],
|
||||||
|
&histograms[0xF0]
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
int offset;
|
||||||
|
for (offset = 0; offset < 7; ++offset) {
|
||||||
|
if (histogram[5][offset] > histogram[5][offset + 1]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (; offset < 7; ++offset) {
|
||||||
|
if (histogram[5][offset] < histogram[5][offset + 1]) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 6; i--; offset *= 2) {
|
||||||
|
if (histogram[i][offset] > histogram[i][offset + 1]) {
|
||||||
|
++offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
block->threshold = offset / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EReaderScanRecalibrateBlock(struct EReaderScan* scan, int blockId) {
|
||||||
|
if (blockId < 0 || (unsigned) blockId >= EReaderBlockListSize(&scan->blocks)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
struct EReaderBlock* block = EReaderBlockListGetPointer(&scan->blocks, blockId);
|
||||||
|
if (block->missing && block->extra) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int errors = block->errors;
|
||||||
|
if (block->missing) {
|
||||||
|
while (errors > 0) {
|
||||||
|
errors -= block->histogram[block->threshold];
|
||||||
|
while (!block->histogram[block->threshold] && block->threshold < 254) {
|
||||||
|
++block->threshold;
|
||||||
|
}
|
||||||
|
++block->threshold;
|
||||||
|
if (block->threshold >= 255) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EReaderScanScanBlock(struct EReaderScan* scan, int blockId, bool strict) {
|
||||||
|
if (blockId < 0 || (unsigned) blockId >= EReaderBlockListSize(&scan->blocks)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
struct EReaderBlock* block = EReaderBlockListGetPointer(&scan->blocks, blockId);
|
||||||
|
block->errors = 0;
|
||||||
|
block->missing = 0;
|
||||||
|
block->extra = 0;
|
||||||
|
|
||||||
|
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;
|
||||||
|
int hMissing = 0;
|
||||||
|
int hExtra = 0;
|
||||||
|
int vMissing = 0;
|
||||||
|
int vExtra = 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 (dots[i] != alignment[i]) {
|
||||||
|
++horizontal;
|
||||||
|
if (alignment[i]) {
|
||||||
|
++hMissing;
|
||||||
|
} else {
|
||||||
|
++hExtra;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dots[i + 36 * 35] != alignment[i]) {
|
||||||
|
++horizontal;
|
||||||
|
if (alignment[i]) {
|
||||||
|
++hMissing;
|
||||||
|
} else {
|
||||||
|
++hExtra;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dots[i * 36] != alignment[i]) {
|
||||||
|
++vertical;
|
||||||
|
if (alignment[i]) {
|
||||||
|
++vMissing;
|
||||||
|
} else {
|
||||||
|
++vExtra;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (dots[i * 36 + 35] != alignment[i]) {
|
||||||
|
++vertical;
|
||||||
|
if (alignment[i]) {
|
||||||
|
++vMissing;
|
||||||
|
} else {
|
||||||
|
++vExtra;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int errors;
|
||||||
|
if (horizontal < vertical) {
|
||||||
|
errors = horizontal;
|
||||||
|
block->errors += horizontal;
|
||||||
|
block->extra += hExtra;
|
||||||
|
block->missing += hMissing;
|
||||||
|
horizontal = 0;
|
||||||
|
} else {
|
||||||
|
errors = vertical;
|
||||||
|
block->errors += vertical;
|
||||||
|
block->extra += vExtra;
|
||||||
|
block->missing += vMissing;
|
||||||
|
vertical = 0;
|
||||||
|
}
|
||||||
|
if (strict && errors) {
|
||||||
|
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;
|
||||||
|
block->hFlip = false;
|
||||||
|
block->vFlip = false;
|
||||||
|
if ((sector[0] & 0x1FF) == 1) {
|
||||||
|
yFlip = -1;
|
||||||
|
yBias = 35;
|
||||||
|
block->vFlip = true;
|
||||||
|
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;
|
||||||
|
block->hFlip = true;
|
||||||
|
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);
|
||||||
|
int errors = 36 * 36;
|
||||||
|
while (!EReaderScanScanBlock(scan, i, true)) {
|
||||||
|
if (errors < EReaderBlockListGetPointer(&scan->blocks, i)->errors) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
errors = EReaderBlockListGetPointer(&scan->blocks, i)->errors;
|
||||||
|
if (!EReaderScanRecalibrateBlock(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
|
||||||
|
|
|
@ -23,7 +23,7 @@ set(CMAKE_AUTOMOC ON)
|
||||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/input)
|
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/input)
|
||||||
|
|
||||||
find_package(Qt5 COMPONENTS Core Widgets OpenGL Network Multimedia)
|
find_package(Qt5 COMPONENTS Core Widgets Network Multimedia)
|
||||||
|
|
||||||
if(NOT BUILD_GL AND NOT BUILD_GLES2)
|
if(NOT BUILD_GL AND NOT BUILD_GLES2)
|
||||||
message(WARNING "OpenGL is recommended to build the Qt port")
|
message(WARNING "OpenGL is recommended to build the Qt port")
|
||||||
|
@ -168,7 +168,7 @@ set(GB_SRC
|
||||||
PrinterView.cpp)
|
PrinterView.cpp)
|
||||||
|
|
||||||
set(QT_LIBRARIES)
|
set(QT_LIBRARIES)
|
||||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5widgets5,libqt5opengl5")
|
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt5widgets5")
|
||||||
|
|
||||||
set(AUDIO_SRC)
|
set(AUDIO_SRC)
|
||||||
if(BUILD_SDL)
|
if(BUILD_SDL)
|
||||||
|
@ -304,7 +304,7 @@ endif()
|
||||||
|
|
||||||
list(APPEND QT_LIBRARIES Qt5::Widgets Qt5::Network)
|
list(APPEND QT_LIBRARIES Qt5::Widgets Qt5::Network)
|
||||||
if(BUILD_GL OR BUILD_GLES2 OR BUILD_EPOXY)
|
if(BUILD_GL OR BUILD_GLES2 OR BUILD_EPOXY)
|
||||||
list(APPEND QT_LIBRARIES Qt5::OpenGL ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY})
|
list(APPEND QT_LIBRARIES ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY})
|
||||||
endif()
|
endif()
|
||||||
if(QT_STATIC)
|
if(QT_STATIC)
|
||||||
find_library(QTPCRE NAMES qtpcre2 qtpcre)
|
find_library(QTPCRE NAMES qtpcre2 qtpcre)
|
||||||
|
|
|
@ -82,6 +82,8 @@
|
||||||
#include <mgba/feature/commandline.h>
|
#include <mgba/feature/commandline.h>
|
||||||
#include <mgba-util/vfs.h>
|
#include <mgba-util/vfs.h>
|
||||||
|
|
||||||
|
#include <mgba-util/convolve.h>
|
||||||
|
|
||||||
#ifdef M_CORE_GB
|
#ifdef M_CORE_GB
|
||||||
#define SUPPORT_GB (1 << mPLATFORM_GB)
|
#define SUPPORT_GB (1 << mPLATFORM_GB)
|
||||||
#else
|
#else
|
||||||
|
@ -473,6 +475,53 @@ void Window::scanCard() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Window::parseCard() {
|
||||||
|
#ifdef USE_FFMPEG
|
||||||
|
QStringList filenames = GBAApp::app()->getOpenFileNames(this, tr("Select e-Reader card images"), tr("Image file (*.png *.jpg *.jpeg)"));
|
||||||
|
QMessageBox* dialog = new QMessageBox(QMessageBox::Information, tr("Conversion finished"),
|
||||||
|
QString("oh"), QMessageBox::Ok);
|
||||||
|
dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
auto status = std::make_shared<QPair<int, int>>(0, filenames.size());
|
||||||
|
GBAApp::app()->submitWorkerJob([filenames, dialog, status]() {
|
||||||
|
int success = 0;
|
||||||
|
for (QString filename : filenames) {
|
||||||
|
if (filename.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QImage image(filename);
|
||||||
|
if (image.isNull()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
EReaderScan* scan;
|
||||||
|
switch (image.depth()) {
|
||||||
|
case 8:
|
||||||
|
scan = EReaderScanLoadImage8(image.constBits(), image.width(), image.height(), image.bytesPerLine());
|
||||||
|
break;
|
||||||
|
case 24:
|
||||||
|
scan = EReaderScanLoadImage(image.constBits(), image.width(), image.height(), image.bytesPerLine());
|
||||||
|
break;
|
||||||
|
case 32:
|
||||||
|
scan = EReaderScanLoadImageA(image.constBits(), image.width(), image.height(), image.bytesPerLine());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QFileInfo ofile(filename);
|
||||||
|
if (EReaderScanCard(scan)) {
|
||||||
|
QString ofilename = ofile.path() + QDir::separator() + ofile.baseName() + ".raw";
|
||||||
|
EReaderScanSaveRaw(scan, ofilename.toUtf8().constData(), false);
|
||||||
|
++success;
|
||||||
|
}
|
||||||
|
EReaderScanDestroy(scan);
|
||||||
|
}
|
||||||
|
status->first = success;
|
||||||
|
}, [dialog, status]() {
|
||||||
|
dialog->setText(tr("%1 of %2 e-Reader cards converted successfully.").arg(status->first).arg(status->second));
|
||||||
|
dialog->show();
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
void Window::openView(QWidget* widget) {
|
void Window::openView(QWidget* widget) {
|
||||||
connect(this, &Window::shutdown, widget, &QWidget::close);
|
connect(this, &Window::shutdown, widget, &QWidget::close);
|
||||||
widget->setAttribute(Qt::WA_DeleteOnClose);
|
widget->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
@ -1187,6 +1236,10 @@ void Window::setupMenu(QMenuBar* menubar) {
|
||||||
#ifdef M_CORE_GBA
|
#ifdef M_CORE_GBA
|
||||||
Action* scanCard = addGameAction(tr("Scan e-Reader dotcodes..."), "scanCard", this, &Window::scanCard, "file");
|
Action* scanCard = addGameAction(tr("Scan e-Reader dotcodes..."), "scanCard", this, &Window::scanCard, "file");
|
||||||
m_platformActions.insert(mPLATFORM_GBA, scanCard);
|
m_platformActions.insert(mPLATFORM_GBA, scanCard);
|
||||||
|
|
||||||
|
#ifdef USE_FFMPEG
|
||||||
|
m_actions.addAction(tr("Convert e-Reader card image to raw..."), "parseCard", this, &Window::parseCard, "file");
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
addGameAction(tr("ROM &info..."), "romInfo", openControllerTView<ROMInfo>(), "file");
|
addGameAction(tr("ROM &info..."), "romInfo", openControllerTView<ROMInfo>(), "file");
|
||||||
|
|
|
@ -82,6 +82,7 @@ public slots:
|
||||||
void selectState(bool load);
|
void selectState(bool load);
|
||||||
void selectPatch();
|
void selectPatch();
|
||||||
void scanCard();
|
void scanCard();
|
||||||
|
void parseCard();
|
||||||
void enterFullScreen();
|
void enterFullScreen();
|
||||||
void exitFullScreen();
|
void exitFullScreen();
|
||||||
void toggleFullScreen();
|
void toggleFullScreen();
|
||||||
|
|
Loading…
Reference in New Issue