차이
문서의 선택한 두 판 사이의 차이를 보여줍니다.
| 다음 판 | 이전 판 | ||
| linuxpc:camera:index [2026/06/09 14:50] – created admin | linuxpc:camera:index [2026/06/09 23:02] (현재) – admin | ||
|---|---|---|---|
| 줄 30: | 줄 30: | ||
| {{ https:// | {{ https:// | ||
| + | ++++[소스 코드]| | ||
| <code C#> | <code C#> | ||
| - | (원본 코드 그대로) </ | + | #:package GirCore.GLib-2.0@0.6.3 |
| + | #:package GirCore.GdkPixbuf-2.0@0.6.3 | ||
| + | #:package GirCore.Gtk-4.0@0.6.3 | ||
| + | |||
| + | using GdkPixbuf; | ||
| + | using Gio; | ||
| + | using Gtk; | ||
| + | using System.Diagnostics; | ||
| + | using System.Runtime.InteropServices; | ||
| + | using System.Threading.Channels; | ||
| + | using Task = System.Threading.Tasks.Task; | ||
| + | |||
| + | public class Program | ||
| + | { | ||
| + | const int Width = 480; | ||
| + | const int Height = 320; | ||
| + | const int FrameSize = Width * Height * 3; | ||
| + | |||
| + | static IntPtr _scanner; | ||
| + | static Channel< | ||
| + | static Channel< | ||
| + | |||
| + | public static int Main(string[] args) | ||
| + | { | ||
| + | var app = Gtk.Application.New(" | ||
| + | app.OnActivate += (gioApp, _) => CreateGUI((Gtk.Application)gioApp); | ||
| + | |||
| + | var status = app.RunWithSynchronizationContext(args); | ||
| + | return status; | ||
| + | } | ||
| + | |||
| + | static void CreateGUI(Gtk.Application app) | ||
| + | { | ||
| + | var window = ApplicationWindow.New(app); | ||
| + | window.Title = "GTK + ZBar QR Demo"; | ||
| + | window.SetDefaultSize(Width, | ||
| + | window.SetResizable(false); | ||
| + | |||
| + | var vbox = Box.New(Orientation.Vertical, | ||
| + | |||
| + | var picture = Picture.New(); | ||
| + | picture.SetSizeRequest(Width, | ||
| + | picture.SetHalign(Align.Center); | ||
| + | |||
| + | var label = Label.New(" | ||
| + | |||
| + | vbox.Append(picture); | ||
| + | vbox.Append(label); | ||
| + | |||
| + | window.Child = vbox; | ||
| + | |||
| + | window.Present(); | ||
| + | |||
| + | _scanner = zbar_image_scanner_create(); | ||
| + | |||
| + | StreamFramesAndScan(picture, | ||
| + | } | ||
| + | |||
| + | private static void StreamFramesAndScan(Picture picture, Label label) | ||
| + | { | ||
| + | var psi = new ProcessStartInfo | ||
| + | { | ||
| + | FileName = " | ||
| + | Arguments = | ||
| + | " | ||
| + | $" | ||
| + | " | ||
| + | $" | ||
| + | " | ||
| + | " | ||
| + | UseShellExecute = false, | ||
| + | RedirectStandardOutput = true, | ||
| + | RedirectStandardError = false, | ||
| + | CreateNoWindow = true | ||
| + | }; | ||
| + | |||
| + | if (Process.Start(psi) is not Process process) | ||
| + | { | ||
| + | GLib.Functions.IdleAdd(0, | ||
| + | return; | ||
| + | } | ||
| + | |||
| + | _previewChannel = Channel.CreateUnbounded< | ||
| + | _scanChannel = Channel.CreateUnbounded< | ||
| + | |||
| + | var stream = process.StandardOutput.BaseStream; | ||
| + | var cts = new CancellationTokenSource(); | ||
| + | |||
| + | ParseFrames(stream, | ||
| + | GeneratePreview(picture, | ||
| + | ScanForQRCode(label, | ||
| + | } | ||
| + | |||
| + | static void ParseFrames(Stream stream, Process process, Label label, CancellationTokenSource cts) | ||
| + | { | ||
| + | Task.Run(async () => | ||
| + | { | ||
| + | try | ||
| + | { | ||
| + | var previewWriter = _previewChannel.Writer; | ||
| + | var scanWriter = _scanChannel.Writer; | ||
| + | |||
| + | while (true) | ||
| + | { | ||
| + | // Skip to frame boundary " | ||
| + | while (stream.ReadByte() != ' | ||
| + | if (stream.ReadByte() != ' | ||
| + | || stream.ReadByte() != ' | ||
| + | || stream.ReadByte() != ' | ||
| + | { | ||
| + | continue; | ||
| + | } | ||
| + | |||
| + | var frame = new byte[FrameSize]; | ||
| + | stream.ReadExactly(frame); | ||
| + | |||
| + | if (frame.Length == FrameSize) | ||
| + | { | ||
| + | await previewWriter.WriteAsync(frame, | ||
| + | await scanWriter.WriteAsync(frame, | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | finally | ||
| + | { | ||
| + | cts.Cancel(); | ||
| + | _previewChannel.Writer.TryComplete(); | ||
| + | _scanChannel.Writer.TryComplete(); | ||
| + | } | ||
| + | }); | ||
| + | } | ||
| + | |||
| + | static void GeneratePreview(Picture picture, CancellationTokenSource cts) | ||
| + | { | ||
| + | Task.Run(async () => | ||
| + | { | ||
| + | try | ||
| + | { | ||
| + | byte[]? lastFrame = null; | ||
| + | await foreach (var frame in _previewChannel.Reader.ReadAllAsync(cts.Token)) | ||
| + | { | ||
| + | if (ReferenceEquals(frame, | ||
| + | lastFrame = frame; | ||
| + | |||
| + | GLib.Functions.IdleAdd(0, | ||
| + | { | ||
| + | try | ||
| + | { | ||
| + | var pix = Pixbuf.NewFromBytes(GLib.Bytes.New(frame), | ||
| + | picture.SetPixbuf(pix); | ||
| + | } | ||
| + | catch { } | ||
| + | return false; | ||
| + | }); | ||
| + | } | ||
| + | } | ||
| + | catch (OperationCanceledException) { } | ||
| + | }); | ||
| + | } | ||
| + | |||
| + | static void ScanForQRCode(Label label, CancellationTokenSource cts) | ||
| + | { | ||
| + | Task.Run(async () => | ||
| + | { | ||
| + | try | ||
| + | { | ||
| + | byte[]? lastFrame = null; | ||
| + | await foreach (var frame in _scanChannel.Reader.ReadAllAsync(cts.Token)) | ||
| + | { | ||
| + | if (ReferenceEquals(frame, | ||
| + | lastFrame = frame; | ||
| + | |||
| + | var text = ScanQrFromRgb(frame); | ||
| + | if (!string.IsNullOrEmpty(text)) | ||
| + | GLib.Functions.IdleAdd(0, | ||
| + | } | ||
| + | } | ||
| + | catch (OperationCanceledException) { } | ||
| + | }); | ||
| + | } | ||
| + | |||
| + | readonly static uint FourCC = ' | ||
| + | |||
| + | private static string? ScanQrFromRgb(ReadOnlySpan< | ||
| + | { | ||
| + | // Convert RGB to Grayscale | ||
| + | var gray = new byte[Width * Height]; | ||
| + | for (int i = 0; i < rgb.Length; i += 3) | ||
| + | { | ||
| + | int y = (int)((rgb[i] * 0.299) + (rgb[i + 1] * 0.587) + (rgb[i + 2] * 0.114)); | ||
| + | gray[i / 3] = (byte)(y < 0 ? 0 : y > 255 ? 255 : y); | ||
| + | } | ||
| + | |||
| + | IntPtr image = zbar_image_create(); | ||
| + | zbar_image_set_format(image, | ||
| + | zbar_image_set_size(image, | ||
| + | |||
| + | IntPtr dataPtr = Marshal.AllocHGlobal(gray.Length); | ||
| + | try | ||
| + | { | ||
| + | Marshal.Copy(gray, | ||
| + | zbar_image_set_data(image, | ||
| + | |||
| + | if (zbar_scan_image(_scanner, | ||
| + | { | ||
| + | zbar_image_destroy(image); | ||
| + | return null; | ||
| + | } | ||
| + | |||
| + | IntPtr symbol = zbar_image_first_symbol(image); | ||
| + | string? result = symbol != IntPtr.Zero ? Marshal.PtrToStringAnsi(zbar_symbol_get_data(symbol)) : null; | ||
| + | zbar_image_destroy(image); | ||
| + | return result; | ||
| + | } | ||
| + | finally | ||
| + | { | ||
| + | Marshal.FreeHGlobal(dataPtr); | ||
| + | } | ||
| + | } | ||
| + | |||
| + | const string ZBarLib = " | ||
| + | [DllImport(ZBarLib)] | ||
| + | private static extern IntPtr zbar_image_scanner_create(); | ||
| + | |||
| + | [DllImport(ZBarLib)] | ||
| + | private static extern IntPtr zbar_image_create(); | ||
| + | |||
| + | [DllImport(ZBarLib)] | ||
| + | private static extern void zbar_image_destroy(IntPtr image); | ||
| + | |||
| + | [DllImport(ZBarLib)] | ||
| + | private static extern void zbar_image_set_format(IntPtr image, uint fourcc); | ||
| + | |||
| + | [DllImport(ZBarLib)] | ||
| + | private static extern void zbar_image_set_size(IntPtr image, uint width, uint height); | ||
| + | |||
| + | [DllImport(ZBarLib)] | ||
| + | private static extern void zbar_image_set_data(IntPtr image, IntPtr data, UIntPtr length, IntPtr cleanup); | ||
| + | |||
| + | [DllImport(ZBarLib)] | ||
| + | private static extern int zbar_scan_image(IntPtr scanner, IntPtr image); | ||
| + | |||
| + | [DllImport(ZBarLib)] | ||
| + | private static extern IntPtr zbar_image_first_symbol(IntPtr image); | ||
| + | |||
| + | [DllImport(ZBarLib)] | ||
| + | private static extern IntPtr zbar_symbol_get_data(IntPtr symbol); | ||
| + | } | ||
| + | |||
| + | </ | ||
| + | ++++ | ||
| [[..: | [[..: | ||
