-
Notifications
You must be signed in to change notification settings - Fork 1
/
Generator.cs
157 lines (133 loc) · 6.37 KB
/
Generator.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace CircleTag
{
public static class Generator
{
public class Settings
{
public double Angle = 0.0;
public int Width = 512;
public int Height = 512;
public uint BackgroundColor = 0x00ffffff;
public uint ForegroundColor = 0xffffffff;
public double StartingRadius = 0.3;
public double EndingRadius = 0.95;
public int BytesPerLayer = 3;
}
private static int _segments;
private static double _segmentScale;
private static double _radiusScale;
private static double _imageLengthNormalizerCached;
private static int _layerCount;
private static Settings _settings;
public static unsafe byte[] From(byte[] bytes, Settings settings = null, byte[] output = null)
{
// Precalculate things
_settings = settings ?? new Settings();
_segments = _settings.BytesPerLayer * 8 + 1;
_segmentScale = 1.0 / (360.0 / _segments);
_radiusScale = 1.0 / (_settings.EndingRadius - _settings.StartingRadius);
uint size = (uint)_settings.Width * (uint)_settings.Height;
int halfWidth = _settings.Width / 2;
int halfHeight = _settings.Height / 2;
_imageLengthNormalizerCached = 1.0 / Math.Min(halfWidth, halfHeight);
_layerCount = bytes.Length / _settings.BytesPerLayer + 1;
// Add size and hash to the data and pad with zeroes
byte[] newBytes = new byte[bytes.Length + 2];
Array.Copy(bytes, 0, newBytes, 1, bytes.Length);
byte hash = Reader.CalculateHash(bytes);
newBytes[0] = (byte) bytes.Length;
newBytes[bytes.Length + 1] = hash;
bytes = newBytes;
bytes = PadBytes(bytes);
// Check if the user wanted to use their own pixel buffer
if (output == null)
{
output = new byte[size * 4];
}
// Write pixels to the buffer
fixed (byte* pixelBytes = output)
{
uint* pixels = (uint*)pixelBytes;
Parallel.For(0, _settings.Height, y =>
{
for (int x = 0; x < _settings.Width; x++)
{
int pixelIndex = y * _settings.Width + x;
pixels[pixelIndex] = PixelColor(x, y, halfWidth, halfHeight, bytes, _settings.ForegroundColor,
_settings.BackgroundColor);
}
});
}
return output;
}
private static byte[] PadBytes(byte[] bytes)
{
int neededPadding = bytes.Length % _settings.BytesPerLayer;
if (neededPadding <= 0) return bytes;
byte[] paddedBytes = new byte[bytes.Length + (_settings.BytesPerLayer - neededPadding)];
Array.Copy(bytes, paddedBytes, bytes.Length);
return paddedBytes;
}
private static uint PixelColor(int x, int y, int centerX, int centerY, IReadOnlyList<byte> data, uint hasPixelColor, uint emptyPixelColor)
{
unchecked
{
// Calculate pixel offset from center
double offX = x - centerX;
double offY = y - centerY;
// Calculate normalized distance from center
double length = Math.Sqrt(offX * offX + offY * offY);
double distance = length * _imageLengthNormalizerCached;
// Normalize distance so that StartingRadius = 0.0 and EndingRadius = 1.0
double pointDistance = (distance - _settings.StartingRadius) * _radiusScale;
if (pointDistance < 0.0 || pointDistance > 1.0)
{
// Point is outside of starting and ending radius
return emptyPixelColor;
}
// Calculate the layer
int layer = (int)(pointDistance * _layerCount);
// Calculate the segment number on layer
double invLength = 1.0 / length;
double normX = offX * invLength;
double normY = offY * invLength;
int segment = GetSegment(normX, normY);
// Draw inner ring so that there is an empty mark for the segment 0, except when there is no data
if (layer <= 0)
{
return data.Count < 3 || segment > 0 ? hasPixelColor : emptyPixelColor;
}
// Offset layer to accommodate for the inner ring
layer--;
// The first segment should be solid to allow finding the orientation, except when there is no data
if (segment == 0)
{
return data.Count > 3 ? hasPixelColor : emptyPixelColor;
}
// Reduce one segment off so the number of segments aligns to number of bytes
segment--;
// Calculate byte array index for the layer and segment
int byteOffset = segment >> 3; // segment / amountOfBitsInByte
int dataIndex = layer * _settings.BytesPerLayer + byteOffset;
// Calculate mask byte for reading the byte array
byte mask = (byte)(0b00000001 << (segment % 8));
// Return empty pixel for where the data has ended
if (data.Count <= dataIndex) return emptyPixelColor;
// Return if bit is true or false
return (data[dataIndex] & mask) > 0 ? hasPixelColor : emptyPixelColor;
}
}
private static int GetSegment(double normX, double normY)
{
const double oneOverPiTimes180 = 1.0 / Math.PI * 180.0;
double angle = Math.Atan2(normY, -normX) * oneOverPiTimes180 + 180.0 + _settings.Angle;
if (angle > 360.0) angle -= 360.0;
double segment = (int)(angle * _segmentScale);
return (int)segment;
}
}
}