Viewing is open to everyone. Downloading and starring need a GitHub sign-in. Sign in with GitHub

CKeySymbol.cs

v0.1.0-build.85 Latest Commit ede38f5

Size
18 KB
Lines
353
Language
csharp
SHA-256
e49d33163b4c…
View on GitHub

Sign in with GitHub to download. Each download is recorded with your GitHub username.

1using System;
2using System.Collections.Generic;
3using System.Text;
4using OpenCKey.Interop;
5
6namespace OpenCKey
7{
8 /// <summary>
9 /// An encoded Profile 0 (scalable) symbol. This is a managed copy of the C
10 /// <c>ckey_scalable_symbol</c> (8,216 bytes, no native resources), so it
11 /// needs no disposal and is safe to share between threads.
12 /// </summary>
13 public sealed unsafe class CKeySymbol
14 {
15 private NativeScalableSymbol _symbol;
16
17 private CKeySymbol()
18 {
19 }
20
21 /// <summary>Highest Profile 0 version.</summary>
22 public const int MaxVersion = NativeConstants.ScalableVersionCount;
23
24 /// <summary>Encodes UTF-8 text.</summary>
25 /// <param name="text">The payload text; encoded as UTF-8 without a BOM.</param>
26 /// <param name="version">1..31, or 0 for the smallest version that fits.</param>
27 /// <param name="level">Error-correction level.</param>
28 /// <param name="layout">Standard (screen) or print layout.</param>
29 public static CKeySymbol Encode(string text, int version = 0, EccLevel level = EccLevel.M, Layout layout = Layout.Standard)
30 {
31 if (text == null) throw new ArgumentNullException(nameof(text));
32 return Encode(new UTF8Encoding(false).GetBytes(text), version, level, layout);
33 }
34
35 /// <summary>Encodes bytes.</summary>
36 /// <param name="payload">The payload bytes.</param>
37 /// <param name="version">1..31, or 0 for the smallest version that fits.</param>
38 /// <param name="level">Error-correction level.</param>
39 /// <param name="layout">Standard (screen) or print layout.</param>
40 public static CKeySymbol Encode(ReadOnlySpan<byte> payload, int version = 0, EccLevel level = EccLevel.M, Layout layout = Layout.Standard)
41 {
42 if (version < 0 || version > MaxVersion) throw new ArgumentOutOfRangeException(nameof(version), "version must be 0 (auto) or 1..31.");
43 if (level < EccLevel.L || level > EccLevel.H) throw new ArgumentOutOfRangeException(nameof(level));
44 if (layout != Layout.Standard && layout != Layout.Print) throw new ArgumentOutOfRangeException(nameof(layout));
45 Native.EnsureAbi();
46 var symbol = new CKeySymbol();
47 NativeDiagnostics diagnostics = default;
48 byte empty = 0;
49 fixed (byte* p = payload)
50 fixed (NativeScalableSymbol* s = &symbol._symbol)
51 {
52 byte* data = p != null ? p : &empty;
53 int status = NativeMethods.ckey_encode_scalable(data, (nuint)payload.Length, (uint)version, (int)level, (int)layout, s, &diagnostics);
54 Native.Check(status, "ckey_encode_scalable", ref diagnostics);
55 }
56 return symbol;
57 }
58
59 /// <summary>Maximum payload bytes for a version, level and layout; 0 if invalid.</summary>
60 public static int Capacity(int version, EccLevel level = EccLevel.M, Layout layout = Layout.Standard)
61 {
62 if (version < 1 || version > MaxVersion) return 0;
63 Native.EnsureAbi();
64 return (int)NativeMethods.ckey_scalable_capacity((uint)version, (int)level, (int)layout);
65 }
66
67 /// <summary>Modules across the field for a version (29..232); 0 if invalid.</summary>
68 public static int ModulesAcrossFor(int version)
69 {
70 if (version < 1 || version > MaxVersion) return 0;
71 Native.EnsureAbi();
72 return (int)NativeMethods.ckey_scalable_modules_across((uint)version);
73 }
74
75 /// <summary>Largest logo radius (canonical units) for a version and layout.</summary>
76 public static double LogoMaxRadius(int version, Layout layout = Layout.Standard)
77 {
78 Native.EnsureAbi();
79 return NativeMethods.ckey_scalable_logo_max_radius((uint)version, (int)layout);
80 }
81
82 /// <summary>Version 1..31.</summary>
83 public int Version => (int)_symbol.Version;
84 /// <summary>Error-correction level.</summary>
85 public EccLevel EccLevel => (EccLevel)_symbol.EccLevel;
86 /// <summary>Layout family.</summary>
87 public Layout Layout => (Layout)_symbol.Layout;
88 /// <summary>Payload length in bytes.</summary>
89 public int PayloadLength => (int)_symbol.PayloadLength;
90 /// <summary>Codeword bytes in use.</summary>
91 public int CodewordLength => (int)_symbol.CodewordLength;
92 /// <summary>Modules across the field (29..232).</summary>
93 public int ModulesAcross => ModulesAcrossFor(Version);
94
95 /// <summary>A copy of the codeword bytes in use.</summary>
96 public byte[] GetCodeword()
97 {
98 int n = Math.Min(CodewordLength, NativeConstants.ScalableMaxCodewordBytes);
99 var bytes = new byte[n];
100 fixed (NativeScalableSymbol* s = &_symbol)
101 {
102 for (int i = 0; i < n; i++) bytes[i] = s->Codeword[i];
103 }
104 return bytes;
105 }
106
107 /// <summary>Share of the worst block's correction capacity hidden under a logo of this radius.</summary>
108 public double LogoLoad(double logoRadius)
109 {
110 fixed (NativeScalableSymbol* s = &_symbol) return NativeMethods.ckey_scalable_logo_load(s, logoRadius);
111 }
112
113 // ---- Style check and certification ---------------------------------
114
115 /// <summary>
116 /// Checks a style against this symbol (contrast, effect limits, logo
117 /// error budget). Returns true when renderers would accept it; otherwise
118 /// <paramref name="message"/> explains the refusal.
119 /// </summary>
120 public bool CheckStyle(CKeyStyle style, out string message)
121 {
122 if (style == null) throw new ArgumentNullException(nameof(style));
123 NativeDiagnostics diagnostics = default;
124 byte[]? href = style.LogoHrefUtf8Z();
125 int status;
126 fixed (byte* h = href)
127 fixed (NativeScalableSymbol* s = &_symbol)
128 {
129 NativeStyle ns = style.ToNative((IntPtr)h);
130 status = NativeMethods.ckey_check_scalable_style(s, &ns, &diagnostics);
131 }
132 message = Native.Message(ref diagnostics);
133 return status == 0;
134 }
135
136 /// <summary>
137 /// Certifies a style by rendering it, compositing it over the backdrops,
138 /// degrading it with the scan model and decoding it with the real decoder
139 /// (<c>ckey_certify_scalable_style</c>). A transparent symbol needs a
140 /// backdrop (colours and/or the host's pixels behind it); without one it
141 /// is never certified.
142 /// </summary>
143 /// <param name="style">The style, or null for the default; its <see cref="CKeyStyle.Policy"/> decides strict/adaptive/override.</param>
144 /// <param name="transparent">Certify transparent output.</param>
145 /// <param name="backdropColours">Up to 16 solid colours the symbol may sit on.</param>
146 /// <param name="backdropImage">Host pixels behind the symbol, stretched over its full canvas.</param>
147 /// <param name="model">The acquisition model, or null for the default phone-camera model.</param>
148 public CKeyCertification Certify(CKeyStyle? style = null, bool transparent = false,
149 IReadOnlyList<CKeyColor>? backdropColours = null, CKeyImage? backdropImage = null,
150 CKeyScanModel? model = null)
151 {
152 if (backdropColours != null && backdropColours.Count > 16)
153 throw new ArgumentOutOfRangeException(nameof(backdropColours), "At most 16 backdrop colours.");
154 Native.EnsureAbi();
155 NativeRgb[]? colours = null;
156 if (backdropColours != null && backdropColours.Count > 0)
157 {
158 colours = new NativeRgb[backdropColours.Count];
159 for (int i = 0; i < colours.Length; i++) colours[i] = backdropColours[i].ToNative();
160 }
161 NativeScanModel nm;
162 NativeMethods.ckey_scan_model_default(&nm);
163 if (model != null) model.Apply(ref nm);
164
165 byte[]? href = style?.LogoHrefUtf8Z();
166 NativeCertification cert = default;
167 NativeStyle adjusted = default;
168 int status;
169 CKeyStyle? adjustedStyle = null;
170 fixed (byte* h = href)
171 fixed (NativeRgb* c = colours)
172 fixed (byte* bg = backdropImage?.Rgba)
173 fixed (NativeScalableSymbol* s = &_symbol)
174 {
175 NativeStyle ns = style != null ? style.ToNative((IntPtr)h) : default;
176 NativeBackdrop backdrop = new NativeBackdrop
177 {
178 Colours = (IntPtr)c,
179 ColourCount = colours != null ? (uint)colours.Length : 0u,
180 Rgba = (IntPtr)bg,
181 Width = backdropImage != null ? (uint)backdropImage.Width : 0u,
182 Height = backdropImage != null ? (uint)backdropImage.Height : 0u,
183 Stride = 0,
184 };
185 bool hasBackdrop = colours != null || backdropImage != null;
186 status = NativeMethods.ckey_certify_scalable_style(s, style != null ? &ns : null, transparent ? 1u : 0u,
187 hasBackdrop ? &backdrop : null, &nm, &adjusted, &cert);
188 // Read the adjusted style while the logo href it may point to is still pinned.
189 if (status == 0 && style != null && style.Policy == StylePolicy.Adaptive)
190 {
191 adjustedStyle = new CKeyStyle();
192 adjustedStyle.Load(in adjusted);
193 }
194 }
195 string message = Native.FromUtf8Z(cert.Message, NativeConstants.CertificationMessageBytes) ?? string.Empty;
196 return new CKeyCertification((CKeyStatus)status, cert.Certified != 0, (StylePolicy)cert.Policy,
197 (StyleAdjustments)cert.Adjustments, (int)cert.Trials, (int)cert.TrialsPassed, cert.MinContrast,
198 cert.FeatureSeparation, cert.LogoLoad, message, adjustedStyle);
199 }
200
201 // ---- Rendering --------------------------------------------------------
202
203 /// <summary>
204 /// Renders native RGBA8 pixels (size x size, covering the full 31-unit
205 /// canvas including the quiet margin). No SVG, file system or network.
206 /// </summary>
207 /// <param name="size">16..16384 pixels.</param>
208 /// <param name="policy">Raster policy.</param>
209 /// <param name="transparent">Paint no background: unpainted pixels have alpha 0.</param>
210 /// <param name="style">Style, or null for the default.</param>
211 /// <param name="logo">Optional RGBA logo drawn into the style's logo circle.</param>
212 /// <param name="materials">false draws flat ink (no sheen, depth or grain).</param>
213 public CKeyImage RenderRgba(int size = 512, RasterPolicy policy = RasterPolicy.Coverage, bool transparent = false,
214 CKeyStyle? style = null, CKeyImage? logo = null, bool materials = true)
215 {
216 byte[] px = Raster(size, policy, NativeConstants.PixelsRgba8, transparent, style, logo, materials);
217 return new CKeyImage(size, size, px);
218 }
219
220 /// <summary>Renders opaque 8-bit gray pixels (Rec. 709 luma), size x size.</summary>
221 public CKeyGrayImage RenderGray(int size = 512, RasterPolicy policy = RasterPolicy.Coverage, CKeyStyle? style = null,
222 bool materials = true)
223 {
224 byte[] px = Raster(size, policy, NativeConstants.PixelsGray8, false, style, null, materials);
225 return new CKeyGrayImage(size, size, px);
226 }
227
228 /// <summary>
229 /// Print rendering: black ink on paper on the printer's dot grid,
230 /// <paramref name="widthInches"/> wide at <paramref name="dpi"/>. Each
231 /// pixel is 0 (ink) or 255 (paper). Colours, materials and logos are not printed.
232 /// </summary>
233 public CKeyGrayImage RenderPrint(double widthInches, double dpi, CKeyStyle? style = null)
234 {
235 Native.EnsureAbi();
236 byte[]? href = style?.LogoHrefUtf8Z();
237 fixed (byte* h = href)
238 fixed (NativeScalableSymbol* s = &_symbol)
239 {
240 NativeStyle ns = style != null ? style.ToNative((IntPtr)h) : default;
241 NativeStyle* sp = style != null ? &ns : null;
242 nuint side = 0;
243 int status = NativeMethods.ckey_render_scalable_print(s, sp, widthInches, dpi, null, 0, &side);
244 if (status != (int)CKeyStatus.InsufficientBuffer) Native.Check(status, "ckey_render_scalable_print");
245 int n = Native.ToInt32Length(side, "Print raster");
246 if (n <= 0 || (long)n * n > int.MaxValue) throw new CKeyException(CKeyStatus.InvalidArgument, "Print raster size out of range.");
247 var gray = new byte[n * n];
248 fixed (byte* o = gray)
249 {
250 Native.Check(NativeMethods.ckey_render_scalable_print(s, sp, widthInches, dpi, o, (nuint)gray.Length, &side),
251 "ckey_render_scalable_print");
252 }
253 return new CKeyGrayImage(n, n, gray);
254 }
255 }
256
257 /// <summary>Renders RGBA pixels and encodes them as PNG.</summary>
258 public byte[] ToPng(int size = 512, RasterPolicy policy = RasterPolicy.Coverage, bool transparent = false,
259 CKeyStyle? style = null, double dpi = 0) =>
260 RenderRgba(size, policy, transparent, style).ToPng(dpi);
261
262 /// <summary>Styled SVG export (vector), <paramref name="scale"/> pixels per canonical unit.</summary>
263 public string ToSvg(int scale = 8, CKeyStyle? style = null)
264 {
265 if (scale < 1 || scale > 256) throw new ArgumentOutOfRangeException(nameof(scale), "scale must be 1..256.");
266 Native.EnsureAbi();
267 style ??= new CKeyStyle();
268 byte[]? href = style.LogoHrefUtf8Z();
269 fixed (byte* h = href)
270 fixed (NativeScalableSymbol* s = &_symbol)
271 {
272 NativeStyle ns = style.ToNative((IntPtr)h);
273 nuint required = 0;
274 int status = NativeMethods.ckey_render_scalable_styled_svg(s, &ns, (uint)scale, null, 0, &required);
275 if (status != (int)CKeyStatus.InsufficientBuffer) Native.Check(status, "ckey_render_scalable_styled_svg");
276 var utf8 = new byte[Native.ToInt32Length(required, "SVG")];
277 fixed (byte* o = utf8)
278 {
279 Native.Check(NativeMethods.ckey_render_scalable_styled_svg(s, &ns, (uint)scale, o, (nuint)utf8.Length, &required),
280 "ckey_render_scalable_styled_svg");
281 return Native.FromUtf8Z(o, utf8.Length) ?? string.Empty; // required includes the NUL
282 }
283 }
284 }
285
286 /// <summary>One-page PDF 1.4 vector export, <paramref name="widthPt"/> points wide (72 per inch).</summary>
287 public byte[] ToPdf(double widthPt = 288, bool transparent = false, CKeyStyle? style = null)
288 {
289 if (!(widthPt > 0)) throw new ArgumentOutOfRangeException(nameof(widthPt));
290 Native.EnsureAbi();
291 byte[]? href = style?.LogoHrefUtf8Z();
292 fixed (byte* h = href)
293 fixed (NativeScalableSymbol* s = &_symbol)
294 {
295 NativeStyle ns = style != null ? style.ToNative((IntPtr)h) : default;
296 NativeStyle* sp = style != null ? &ns : null;
297 nuint required = 0;
298 uint t = transparent ? 1u : 0u;
299 int status = NativeMethods.ckey_render_scalable_pdf(s, sp, widthPt, t, null, 0, &required);
300 if (status != (int)CKeyStatus.InsufficientBuffer) Native.Check(status, "ckey_render_scalable_pdf");
301 var pdf = new byte[Native.ToInt32Length(required, "PDF")];
302 fixed (byte* o = pdf)
303 {
304 Native.Check(NativeMethods.ckey_render_scalable_pdf(s, sp, widthPt, t, o, (nuint)pdf.Length, &required),
305 "ckey_render_scalable_pdf");
306 }
307 if ((int)required < pdf.Length) Array.Resize(ref pdf, (int)required);
308 return pdf;
309 }
310 }
311
312 private byte[] Raster(int size, RasterPolicy policy, int format, bool transparent, CKeyStyle? style, CKeyImage? logo, bool materials)
313 {
314 if (size < 16 || size > 16384) throw new ArgumentOutOfRangeException(nameof(size), "size must be 16..16384 pixels.");
315 if (policy < RasterPolicy.Coverage || policy > RasterPolicy.Hybrid) throw new ArgumentOutOfRangeException(nameof(policy));
316 Native.EnsureAbi();
317 NativeRasterOptions options;
318 NativeMethods.ckey_raster_options_default(&options);
319 options.SizePx = (uint)size;
320 options.Policy = (uint)policy;
321 options.Format = (uint)format;
322 options.Transparent = transparent ? 1u : 0u;
323 options.Materials = materials ? 1u : 0u;
324 byte[]? href = style?.LogoHrefUtf8Z();
325 fixed (byte* h = href)
326 fixed (byte* lp = logo?.Rgba)
327 fixed (NativeScalableSymbol* s = &_symbol)
328 {
329 NativeLogoImage li = default;
330 if (logo != null)
331 {
332 li.Rgba = (IntPtr)lp;
333 li.Width = (uint)logo.Width;
334 li.Height = (uint)logo.Height;
335 li.Stride = (nuint)(logo.Width * 4);
336 options.Logo = (IntPtr)(&li);
337 }
338 NativeStyle ns = style != null ? style.ToNative((IntPtr)h) : default;
339 NativeStyle* sp = style != null ? &ns : null;
340 nuint required = 0;
341 int status = NativeMethods.ckey_render_scalable_raster(s, sp, &options, null, 0, &required);
342 if (status != (int)CKeyStatus.InsufficientBuffer) Native.Check(status, "ckey_render_scalable_raster");
343 var px = new byte[Native.ToInt32Length(required, "Raster")];
344 fixed (byte* o = px)
345 {
346 Native.Check(NativeMethods.ckey_render_scalable_raster(s, sp, &options, o, (nuint)px.Length, &required),
347 "ckey_render_scalable_raster");
348 }
349 return px;
350 }
351 }
352 }
353}