Merge branch 'master' (early part) into medusa

This commit is contained in:
Vicki Pfau 2021-06-29 20:34:29 -07:00
commit 2c35ccfdfe
7 changed files with 990 additions and 4 deletions

View File

@ -34,6 +34,8 @@ Misc:
- DS Core: Backport symbol loading changes from GBA core (fixes mgba.io/i/1834)
0.10.0: (Future)
Features:
- Tool for converting scanned pictures of e-Reader cards to raw dotcode data
Emulation fixes:
- Core: Fix first event scheduling after loading savestate
- GB Serialize: Fix switching speed modes when loading a state (fixes mgba.io/i/2097)

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,16 @@ 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 EReaderScanRecalibrateBlock(struct EReaderScan*, int block);
bool EReaderScanScanBlock(struct EReaderScan*, int block, bool strict);
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,866 @@ 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;
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

View File

@ -23,7 +23,7 @@ set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
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)
message(WARNING "OpenGL is recommended to build the Qt port")
@ -168,7 +168,7 @@ set(GB_SRC
PrinterView.cpp)
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)
if(BUILD_SDL)
@ -304,7 +304,7 @@ endif()
list(APPEND QT_LIBRARIES Qt5::Widgets Qt5::Network)
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()
if(QT_STATIC)
find_library(QTPCRE NAMES qtpcre2 qtpcre)

View File

@ -82,6 +82,8 @@
#include <mgba/feature/commandline.h>
#include <mgba-util/vfs.h>
#include <mgba-util/convolve.h>
#ifdef M_CORE_GB
#define SUPPORT_GB (1 << mPLATFORM_GB)
#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) {
connect(this, &Window::shutdown, widget, &QWidget::close);
widget->setAttribute(Qt::WA_DeleteOnClose);
@ -1187,6 +1236,10 @@ void Window::setupMenu(QMenuBar* menubar) {
#ifdef M_CORE_GBA
Action* scanCard = addGameAction(tr("Scan e-Reader dotcodes..."), "scanCard", this, &Window::scanCard, "file");
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
addGameAction(tr("ROM &info..."), "romInfo", openControllerTView<ROMInfo>(), "file");

View File

@ -82,6 +82,7 @@ public slots:
void selectState(bool load);
void selectPatch();
void scanCard();
void parseCard();
void enterFullScreen();
void exitFullScreen();
void toggleFullScreen();