initital push

This commit is contained in:
Savya Bikram Shah
2026-05-07 14:18:36 +05:45
commit 2223b2d3ae
146 changed files with 8618 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
using System;
namespace Darkmatter.Fonepay
{
public static partial class FonepayQRGenerator
{
// [version-1][eccLevel] = (totalCodewords, ecCodewordsPerBlock, blocks)
static readonly (int total, int ecPerBlock, int blocks)[,] _caps =
{
{ (19, 7, 1), (16, 10, 1), (13, 13, 1), (9, 17, 1) },
{ (34, 10, 1), (28, 16, 1), (22, 22, 1), (16, 28, 1) },
{ (55, 15, 1), (44, 26, 1), (34, 18, 2), (26, 22, 2) },
{ (80, 20, 2), (64, 18, 2), (48, 26, 4), (36, 16, 4) },
{ (108, 26, 2), (86, 24, 2), (62, 18, 2), (46, 22, 2) },
{ (136, 18, 4), (108, 16, 4), (76, 24, 4), (60, 28, 4) },
{ (156, 20, 4), (124, 18, 4), (88, 18, 6), (66, 26, 4) },
{ (194, 24, 4), (154, 22, 4), (110, 22, 6), (86, 26, 4) },
{ (232, 30, 4), (182, 22, 5), (132, 20, 8), (100, 24, 4) },
{ (274, 18, 6), (216, 26, 6), (154, 24, 8), (122, 28, 6) },
};
static int DataCodewords(int ver, EccLevel ecc)
{
var (total, ecPer, blks) = _caps[ver - 1, (int)ecc];
return total - ecPer * blks;
}
static (int version, (int total, int ecPerBlock, int blocks) ecBlocks)
ChooseVersion(int byteLen, EccLevel ecc)
{
for (int v = 1; v <= 10; v++)
{
int dc = DataCodewords(v, ecc);
if (dc >= byteLen + 2)
return (v, _caps[v - 1, (int)ecc]);
}
throw new Exception($"Data too large for versions 1-10 (byte mode, ecc={ecc})");
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a611e576dae8b4ff6b15c314c1246cf1

View File

@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
namespace Darkmatter.Fonepay
{
public static partial class FonepayQRGenerator
{
static byte[] BuildCodewords(byte[] data, int version, EccLevel ecc,
(int total, int ecPerBlock, int blocks) ecBlocks)
{
var bits = new List<bool>();
bits.AddRange(new[] { false, true, false, false }); // byte mode
int len = data.Length;
for (int i = 7; i >= 0; i--) bits.Add(((len >> i) & 1) == 1);
foreach (byte b in data)
for (int i = 7; i >= 0; i--)
bits.Add(((b >> i) & 1) == 1);
int dcBytes = DataCodewords(version, ecc);
int targetBits = dcBytes * 8;
for (int i = 0; i < 4 && bits.Count < targetBits; i++) bits.Add(false);
while (bits.Count % 8 != 0) bits.Add(false);
bool[] padA = { true, true, true, false, true, true, false, false };
bool[] padB = { false, false, false, true, false, false, false, true };
int pi = 0;
while (bits.Count < targetBits)
{
bits.AddRange(pi % 2 == 0 ? padA : padB);
pi++;
}
byte[] dc = new byte[dcBytes];
for (int i = 0; i < dcBytes; i++)
for (int b = 0; b < 8; b++)
if (bits[i * 8 + b])
dc[i] |= (byte)(1 << (7 - b));
int blocks = ecBlocks.blocks;
int ecPer = ecBlocks.ecPerBlock;
int blockSize = dcBytes / blocks;
byte[][] dcBlocks = new byte[blocks][];
byte[][] ecBlocksArr = new byte[blocks][];
for (int i = 0; i < blocks; i++)
{
dcBlocks[i] = new byte[blockSize];
Array.Copy(dc, i * blockSize, dcBlocks[i], 0, blockSize);
ecBlocksArr[i] = ReedSolomon(dcBlocks[i], ecPer);
}
var result = new List<byte>();
for (int i = 0; i < blockSize; i++)
for (int b = 0; b < blocks; b++)
result.Add(dcBlocks[b][i]);
for (int i = 0; i < ecPer; i++)
for (int b = 0; b < blocks; b++)
result.Add(ecBlocksArr[b][i]);
return result.ToArray();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e67574ccbe68944928d706bd99d785ca

View File

@@ -0,0 +1,234 @@
using System;
namespace Darkmatter.Fonepay
{
public static partial class FonepayQRGenerator
{
static bool[,] BuildMatrix(int version, byte[] codewords)
{
int size = version * 4 + 17;
var matrix = new bool[size, size];
var reserved = new bool[size, size];
PlaceFinder(matrix, reserved, 0, 0);
PlaceFinder(matrix, reserved, 0, size - 7);
PlaceFinder(matrix, reserved, size - 7, 0);
PlaceTiming(matrix, reserved, size);
PlaceDarkModule(matrix, reserved, version);
if (version >= 2) PlaceAlignment(matrix, reserved, version);
ReserveFormat(reserved, size);
PlaceData(matrix, reserved, codewords, size);
int bestPenalty = int.MaxValue;
bool[,] bestMatrix = null;
for (int m = 0; m < 8; m++)
{
var candidate = (bool[,])matrix.Clone();
ApplyMask(candidate, reserved, m, size);
ApplyFormatInfo(candidate, reserved, 1, m, size);
int penalty = CalcPenalty(candidate, size);
if (penalty < bestPenalty)
{
bestPenalty = penalty;
bestMatrix = (bool[,])candidate.Clone();
}
}
return bestMatrix;
}
static void PlaceFinder(bool[,] m, bool[,] r, int row, int col)
{
for (int dr = -1; dr <= 7; dr++)
for (int dc = -1; dc <= 7; dc++)
{
int rr = row + dr, cc = col + dc;
if (rr < 0 || cc < 0 || rr >= m.GetLength(0) || cc >= m.GetLength(1)) continue;
r[rr, cc] = true;
bool inOuter = dr >= 0 && dr <= 6 && dc >= 0 && dc <= 6;
bool inBorder = (dr == 0 || dr == 6 || dc == 0 || dc == 6) && inOuter;
bool inInner = dr >= 2 && dr <= 4 && dc >= 2 && dc <= 4;
m[rr, cc] = !(dr == -1 || dc == -1 || dr == 7 || dc == 7) && (inBorder || inInner);
}
}
static void PlaceTiming(bool[,] m, bool[,] r, int size)
{
for (int i = 8; i < size - 8; i++)
{
bool v = i % 2 == 0;
m[6, i] = v; r[6, i] = true;
m[i, 6] = v; r[i, 6] = true;
}
}
static void PlaceDarkModule(bool[,] m, bool[,] r, int ver)
{
int row = ver * 4 + 9;
m[row, 8] = true;
r[row, 8] = true;
}
static readonly int[][] _alignCenters =
{
new int[] { },
new[] { 6, 18 },
new[] { 6, 22 },
new[] { 6, 26 },
new[] { 6, 30 },
new[] { 6, 34 },
new[] { 6, 22, 38 },
new[] { 6, 24, 42 },
new[] { 6, 26, 46 },
new[] { 6, 28, 50 },
};
static void PlaceAlignment(bool[,] m, bool[,] r, int version)
{
int[] centers = _alignCenters[version - 1];
foreach (int row in centers)
foreach (int col in centers)
{
if (r[row, col]) continue;
for (int dr = -2; dr <= 2; dr++)
for (int dc = -2; dc <= 2; dc++)
{
bool dark = Math.Abs(dr) == 2 || Math.Abs(dc) == 2 || (dr == 0 && dc == 0);
m[row + dr, col + dc] = dark;
r[row + dr, col + dc] = true;
}
}
}
static void ReserveFormat(bool[,] r, int size)
{
for (int i = 0; i <= 8; i++)
{
r[8, i] = true;
r[i, 8] = true;
}
for (int i = size - 8; i < size; i++)
{
r[8, i] = true;
r[i, 8] = true;
}
}
static void PlaceData(bool[,] m, bool[,] r, byte[] cw, int size)
{
int cwIdx = 0, bitIdx = 7;
bool upward = true;
int col = size - 1;
while (col > 0)
{
if (col == 6) col--;
for (int rowStep = 0; rowStep < size; rowStep++)
{
int row = upward ? size - 1 - rowStep : rowStep;
for (int c = 0; c < 2; c++)
{
int cc = col - c;
if (r[row, cc]) continue;
bool bit = cwIdx < cw.Length && ((cw[cwIdx] >> bitIdx) & 1) == 1;
m[row, cc] = bit;
if (--bitIdx < 0)
{
bitIdx = 7;
cwIdx++;
}
}
}
upward = !upward;
col -= 2;
}
}
static void ApplyMask(bool[,] m, bool[,] r, int mask, int size)
{
for (int row = 0; row < size; row++)
for (int col = 0; col < size; col++)
{
if (r[row, col]) continue;
bool flip = mask switch
{
0 => (row + col) % 2 == 0,
1 => row % 2 == 0,
2 => col % 3 == 0,
3 => (row + col) % 3 == 0,
4 => (row / 2 + col / 3) % 2 == 0,
5 => row * col % 2 + row * col % 3 == 0,
6 => (row * col % 2 + row * col % 3) % 2 == 0,
7 => ((row + col) % 2 + row * col % 3) % 2 == 0,
_ => false
};
if (flip) m[row, col] ^= true;
}
}
static readonly ushort[] _formatL = { 0x77C4, 0x72F3, 0x7DAA, 0x789D, 0x662F, 0x6318, 0x6C41, 0x6976 };
static readonly ushort[] _formatM = { 0x5412, 0x5125, 0x5E7C, 0x5B4B, 0x45F9, 0x40CE, 0x4F97, 0x4AA0 };
static readonly ushort[] _formatQ = { 0x355F, 0x3068, 0x3F31, 0x3A06, 0x24B4, 0x2183, 0x2EDA, 0x2BED };
static readonly ushort[] _formatH = { 0x1689, 0x13BE, 0x1CE7, 0x19D0, 0x0762, 0x0255, 0x0D0C, 0x083B };
static void ApplyFormatInfo(bool[,] m, bool[,] r, int eccIndex, int mask, int size)
{
ushort[] table = eccIndex switch { 0 => _formatM, 1 => _formatL, 2 => _formatH, _ => _formatQ };
ushort fmt = table[mask];
int[] seq = { 0, 1, 2, 3, 4, 5, 7, 8 };
for (int i = 0; i < 8; i++)
{
bool bit = ((fmt >> (14 - i)) & 1) == 1;
m[8, seq[i]] = bit;
m[seq[i < 6 ? i : i + 1 < 8 ? i : i], 8] = bit;
}
for (int i = 0; i < 7; i++)
{
bool bit = ((fmt >> i) & 1) == 1;
m[size - 1 - i, 8] = bit;
}
for (int i = 7; i < 15; i++)
{
bool bit = ((fmt >> i) & 1) == 1;
m[8, size - 15 + i] = bit;
}
}
static int CalcPenalty(bool[,] m, int size)
{
int p = 0;
for (int r = 0; r < size; r++)
{
int run = 1;
for (int c = 1; c < size; c++)
{
if (m[r, c] == m[r, c - 1])
{
run++;
if (run == 5) p += 3;
else if (run > 5) p++;
}
else run = 1;
}
run = 1;
for (int c = 1; c < size; c++)
{
if (m[c, r] == m[c - 1, r])
{
run++;
if (run == 5) p += 3;
else if (run > 5) p++;
}
else run = 1;
}
}
for (int r = 0; r < size - 1; r++)
for (int c = 0; c < size - 1; c++)
if (m[r, c] == m[r, c + 1] && m[r, c] == m[r + 1, c] && m[r, c] == m[r + 1, c + 1])
p += 3;
return p;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6510d029572264a5b8aea6d31e302686

View File

@@ -0,0 +1,67 @@
using System;
namespace Darkmatter.Fonepay
{
public static partial class FonepayQRGenerator
{
static readonly byte[] _exp = new byte[512];
static readonly byte[] _log = new byte[256];
static FonepayQRGenerator()
{
int x = 1;
for (int i = 0; i < 255; i++)
{
_exp[i] = (byte)x;
_log[x] = (byte)i;
x <<= 1;
if (x >= 256) x ^= 0x11d;
}
for (int i = 255; i < 512; i++) _exp[i] = _exp[i - 255];
}
static byte GfMul(byte a, byte b)
{
if (a == 0 || b == 0) return 0;
return _exp[(_log[a] + _log[b]) % 255];
}
static byte GfPow(int b, int e) => _exp[(_log[(byte)b] * e) % 255];
static byte[] ReedSolomon(byte[] data, int ecCount)
{
byte[] gen = RsGenerator(ecCount);
byte[] msg = new byte[data.Length + ecCount];
Array.Copy(data, msg, data.Length);
for (int i = 0; i < data.Length; i++)
{
byte coef = msg[i];
if (coef == 0) continue;
for (int j = 1; j < gen.Length; j++)
msg[i + j] ^= GfMul(gen[j], coef);
}
byte[] ec = new byte[ecCount];
Array.Copy(msg, data.Length, ec, 0, ecCount);
return ec;
}
static byte[] RsGenerator(int degree)
{
byte[] g = { 1 };
for (int i = 0; i < degree; i++)
{
byte[] ng = new byte[g.Length + 1];
byte root = GfPow(2, i);
for (int j = 0; j < g.Length; j++)
{
ng[j] ^= GfMul(g[j], root);
ng[j + 1] ^= g[j];
}
g = ng;
}
return g;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 049bdafac777b4e58bafafb2224e88a8

View File

@@ -0,0 +1,64 @@
using UnityEngine;
namespace Darkmatter.Fonepay
{
/// <summary>
/// Pure C# QR Code generator. Byte mode, ECC L/M/Q/H, versions 110.
/// </summary>
public static partial class FonepayQRGenerator
{
public enum EccLevel { L, M, Q, H }
public static Texture2D GenerateTexture(string text, int pixelSize = 10,
EccLevel ecc = EccLevel.M, Color? darkColor = null, Color? lightColor = null)
{
bool[,] matrix = GenerateMatrix(text, ecc);
int size = matrix.GetLength(0);
int texSize = size * pixelSize;
var tex = new Texture2D(texSize, texSize, TextureFormat.RGBA32, false)
{
filterMode = FilterMode.Point,
wrapMode = TextureWrapMode.Clamp
};
Color dark = darkColor ?? Color.black;
Color light = lightColor ?? Color.white;
for (int row = 0; row < size; row++)
for (int col = 0; col < size; col++)
{
Color c = matrix[row, col] ? dark : light;
for (int py = 0; py < pixelSize; py++)
for (int px = 0; px < pixelSize; px++)
tex.SetPixel(col * pixelSize + px,
(size - 1 - row) * pixelSize + py, c);
}
tex.Apply();
return tex;
}
public static Sprite GenerateSprite(string text, int pixelSize = 10,
EccLevel ecc = EccLevel.M, Color? darkColor = null, Color? lightColor = null,
float pixelsPerUnit = 100f)
{
var tex = GenerateTexture(text, pixelSize, ecc, darkColor, lightColor);
var sprite = Sprite.Create(tex,
new Rect(0, 0, tex.width, tex.height),
new Vector2(0.5f, 0.5f),
pixelsPerUnit,
0, SpriteMeshType.FullRect);
sprite.name = "FonepayQR";
return sprite;
}
public static bool[,] GenerateMatrix(string text, EccLevel ecc = EccLevel.M)
{
byte[] data = System.Text.Encoding.UTF8.GetBytes(text);
var (version, ecBlocks) = ChooseVersion(data.Length, ecc);
byte[] codewords = BuildCodewords(data, version, ecc, ecBlocks);
return BuildMatrix(version, codewords);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 926e00b59f70b418bbf937c11a1c8494