feat(vqt6/vipermath/vipersimd/w32/requests): 自绘 Canvas/Graph3D/ChartView 与 Qt6 Charts、新增 gzip 与 torch-lite 模块, 修包装类悬垂指针与中文路径/exp/大文件下载
- vqt6: 包装类统一补 __new__ 堆分配, 修"创建函数栈帧 alloca"被存进字段/跨帧返回后 _ptr 悬垂(传给 QLayout::addWidget 等即崩溃); 菜单类按依赖顺序前移, menuBar()/statusBar() 改返回裸指针; 接入 Qt6 Charts 并新增 _bridge_canvas(Canvas 手绘板 / Graph3D 软件 3D 网络图 / ChartView 折线图); 新增自绘 QRadioButton(圆环+内点, 过渡动画)、剪贴板、应用级字体族 API - 新增模块: gzip.vp(RFC1952/1951 解码器)、torch/(torch-lite 稠密张量 + conv/pool 前反向) - vipersimd: 新增 scalar_fma/add/mul/sqrt_array 未向量化对照内核(与对应 SIMD 版逐位一致) - w32: 新增 UTF-8/UTF-16 路径转换, CreateFile/CreateDirectory 走 W 系列, 修中文路径被按 GBK 误读; read_all 参数 max_count -> buf_size 并文档化"容量含终止符"契约 - requests: https 响应缓冲按需翻倍扩容(上限 256MB), 修 MB 级正文(如 MNIST .gz)被 4KB 静默截断 - vipermath: 重写 exp 为范围归约 + 短泰勒, 修负值发散(x=-30 得 3.7e10)与 NaN 触发 2^31 次缩放导致的卡死 - builtins: 新增 sizet/uzt 别名; stdarg: va_arg 参数 CType -> CPtr - chore: 删除 vendored jpeg change.log
This commit is contained in:
1 parent
7d092f25b1
commit
728d090815
22 files changed
+3915
-755
No files matched your search
@@ -28,6 +28,8 @@ def max(a: double, b: double) -> double:
|
||||
# rowptr 已内置于编译器(TPC/TPV 双端),作为语法保留字,不可重定义:
|
||||
# rowptr[T] 固定表示"裸指针,指向原始类型 T"(等价 t.CPtr[T])。
|
||||
size_t: t.CTypedef = t.CSizeT
|
||||
uzt: t.CTypedef = t.CSizeT
|
||||
sizet: t.CTypedef = t.CSizeT
|
||||
int8: t.CTypedef = t.CInt8T
|
||||
int16: t.CTypedef = t.CInt16T
|
||||
int32: t.CTypedef = t.CInt32T
|
||||
|
||||
@@ -1,527 +0,0 @@
|
||||
CHANGE LOG for Independent JPEG Group's JPEG software
|
||||
|
||||
|
||||
Version 9f 14-Jan-2024
|
||||
-----------------------
|
||||
|
||||
Add build system for C++Builder/RAD Studio.
|
||||
|
||||
Add build system for Xcode (beside configure).
|
||||
|
||||
Add ARM64EC (Emulation Compatible) platform support in the
|
||||
Visual Studio build.
|
||||
|
||||
|
||||
Version 9e 16-Jan-2022
|
||||
-----------------------
|
||||
|
||||
Include alternate user interface files for cjpeg/djpeg.
|
||||
|
||||
jcparam.c: change default chrominance DC quantization factor
|
||||
for lossless support. Note: Requires rebuild of test images.
|
||||
|
||||
rdgif.c, cderror.h: add sanity check for GIF image dimensions.
|
||||
Thank to Casper Sun for cjpeg potential vulnerability report.
|
||||
|
||||
Add ARM and ARM64 platform support in the Visual Studio build.
|
||||
|
||||
|
||||
Version 9d 12-Jan-2020
|
||||
-----------------------
|
||||
|
||||
Optimize the optimal Huffman code table generation to produce
|
||||
slightly smaller files. Thank to John Korejwa for suggestion.
|
||||
Note: Requires rebuild of testimgp.jpg.
|
||||
|
||||
Decoding Huffman: Use default tables if tables are not defined.
|
||||
Thank to Simone Azzalin for report (Motion JPEG),
|
||||
and to Martin Strunz for hint.
|
||||
|
||||
Add sanity check in optimal Huffman code table generation.
|
||||
Thank to Adam Farley for suggestion.
|
||||
|
||||
rdtarga.c: use read_byte(), with EOF check, instead of getc()
|
||||
in read_*_pixel().
|
||||
Thank to Chijin Zhou for cjpeg potential vulnerability report.
|
||||
|
||||
jmemnobs.c: respect the max_memory_to_use setting in
|
||||
jpeg_mem_available() computation. Thank to Sheng Shu and
|
||||
Dongdong She for djpeg potential vulnerability report.
|
||||
|
||||
jdarith.c, jdhuff.c: avoid left shift of negative value
|
||||
compiler warning in decode_mcu_AC_refine().
|
||||
Thank to Indu Bhagat for suggestion.
|
||||
|
||||
Add x64 (64-bit) platform support, avoid compiler warnings.
|
||||
Thank to Jonathan Potter, Feiyun Wang, and Sheng Shu for suggestion.
|
||||
|
||||
Adjust libjpeg version specification for pkg-config file.
|
||||
Thank to Chen Chen for suggestion.
|
||||
|
||||
Restore GIF read and write support from libjpeg version 6a.
|
||||
Thank to Wolfgang Werner (W.W.) Heinz for suggestion.
|
||||
|
||||
Improve consistency in raw (downsampled) image data processing mode.
|
||||
Thank to Zhongyuan Zhou for hint.
|
||||
|
||||
Avoid out of bounds array read (AC derived table pointers)
|
||||
in start pass in jdhuff.c. Thank to Peng Li for report.
|
||||
|
||||
Improve code sanity (jdhuff.c).
|
||||
Thank to Reza Mirzazade farkhani for reports.
|
||||
|
||||
Add jpegtran -drop option; add options to the crop extension and wipe
|
||||
to fill the extra area with content from the source image region,
|
||||
instead of gray out.
|
||||
|
||||
|
||||
Version 9c 14-Jan-2018
|
||||
-----------------------
|
||||
|
||||
jpegtran: add an option to the -wipe switch to fill the region
|
||||
with the average of adjacent blocks, instead of gray out.
|
||||
Thank to Caitlyn Feddock and Maddie Ziegler for inspiration.
|
||||
|
||||
Make range extension bits adjustable (in jpegint.h).
|
||||
Thank to Robin Watts for suggestion.
|
||||
|
||||
Provide macros for fflush() and ferror() in jinclude.h in order
|
||||
to facilitate adaption by applications using an own FILE class.
|
||||
Thank to Gerhard Huber for suggestion.
|
||||
|
||||
Add libjpeg pkg-config file. Thank to Mark Lavi, Vincent Torri,
|
||||
Patrick McMunn, and Huw Davies for suggestion.
|
||||
|
||||
Add sanity checks in cjpeg image reader modules.
|
||||
Thank to Bingchang, Liu for reports.
|
||||
|
||||
|
||||
Version 9b 17-Jan-2016
|
||||
-----------------------
|
||||
|
||||
Improvements and optimizations in DCT and color calculations.
|
||||
Normalize range limit array composition and access pattern.
|
||||
Thank to Sia Furler and Maddie Ziegler for inspiration.
|
||||
|
||||
Use merged upsample with scaled DCT sizes larger than 8.
|
||||
Thank to Taylor Hatala for inspiration.
|
||||
|
||||
Check for excessive comment lengths in argument parsing in wrjpgcom.c.
|
||||
Thank to Julian Cohen for hint.
|
||||
|
||||
Add makefile.b32 for use with Borland C++ 32-bit (bcc32).
|
||||
Thank to Joe Slater for contribution.
|
||||
|
||||
Document 'f' specifier for jpegtran -crop specification.
|
||||
Thank to Michele Martone for suggestion.
|
||||
|
||||
Use defined value from header instead of hardwired number in rdswitch.c.
|
||||
Thank to Robert Sprowson for hint.
|
||||
|
||||
|
||||
Version 9a 19-Jan-2014
|
||||
-----------------------
|
||||
|
||||
Add support for wide gamut color spaces (JFIF version 2).
|
||||
Improve clarity and accuracy in color conversion modules.
|
||||
Note: Requires rebuild of test images.
|
||||
|
||||
Extend the bit depth support to all values from 8 to 12
|
||||
(BITS_IN_JSAMPLE configuration option in jmorecfg.h).
|
||||
jpegtran now supports N bits sample data precision with all N from 8 to 12
|
||||
in a single instance. Thank to Roland Fassauer for inspiration.
|
||||
|
||||
Try to resolve issues with new boolean type definition.
|
||||
Thank also to v4hn for suggestion.
|
||||
|
||||
Enable option to use default Huffman tables for lossless compression
|
||||
(for hardware solution), and in this case improve lossless RGB compression
|
||||
with reversible color transform. Thank to Benny Alexandar for hint.
|
||||
|
||||
Extend the entropy decoding structure, so that extraneous bytes between
|
||||
compressed scan data and following marker can be reported correctly.
|
||||
Thank to Nigel Tao for hint.
|
||||
|
||||
Add jpegtran -wipe option and extension for -crop.
|
||||
Thank to Andrew Senior, David Clunie, and Josef Schmid for suggestion.
|
||||
|
||||
|
||||
Version 9 13-Jan-2013
|
||||
----------------------
|
||||
|
||||
Add cjpeg -rgb1 option to create an RGB JPEG file, and insert
|
||||
a simple reversible color transform into the processing which
|
||||
significantly improves the compression.
|
||||
The recommended command for lossless coding of RGB images is now
|
||||
cjpeg -rgb1 -block 1 -arithmetic.
|
||||
As said, this option improves the compression significantly, but
|
||||
the files are not compatible with JPEG decoders prior to IJG v9
|
||||
due to the included color transform.
|
||||
The used color transform and marker signaling is compatible with
|
||||
other JPEG standards (e.g., JPEG-LS part 2).
|
||||
|
||||
Remove the automatic de-ANSI-fication support (Automake 1.12).
|
||||
Thank also to Nitin A Kamble for suggestion.
|
||||
|
||||
Add remark for jpeg_mem_dest() in jdatadst.c.
|
||||
Thank to Elie-Gregoire Khoury for the hint.
|
||||
|
||||
Support files with invalid component identifiers (created
|
||||
by Adobe PDF). Thank to Robin Watts for the suggestion.
|
||||
|
||||
Adapt full buffer case in jcmainct.c for use with scaled DCT.
|
||||
Thank to Sergii Biloshytskyi for the suggestion.
|
||||
|
||||
Add type identifier for declaration of noreturn functions.
|
||||
Thank to Brett L. Moore for the suggestion.
|
||||
|
||||
Correct argument type in format string, avoid compiler warnings.
|
||||
Thank to Vincent Torri for hint.
|
||||
|
||||
Add missing #include directives in configuration checks, avoid
|
||||
configuration errors. Thank to John Spencer for the hint.
|
||||
|
||||
|
||||
Version 8d 15-Jan-2012
|
||||
-----------------------
|
||||
|
||||
Add cjpeg -rgb option to create RGB JPEG files.
|
||||
Using this switch suppresses the conversion from RGB
|
||||
colorspace input to the default YCbCr JPEG colorspace.
|
||||
This feature allows true lossless JPEG coding of RGB color images.
|
||||
The recommended command for this purpose is currently
|
||||
cjpeg -rgb -block 1 -arithmetic.
|
||||
SmartScale capable decoder (introduced with IJG JPEG 8) required.
|
||||
Thank to Michael Koch for the initial suggestion.
|
||||
|
||||
Add option to disable the region adjustment in the transupp crop code.
|
||||
Thank to Jeffrey Friedl for the suggestion.
|
||||
|
||||
Thank to Richard Jones and Edd Dawson for various minor corrections.
|
||||
|
||||
Thank to Akim Demaille for configure.ac cleanup.
|
||||
|
||||
|
||||
Version 8c 16-Jan-2011
|
||||
-----------------------
|
||||
|
||||
Add option to compression library and cjpeg (-block N) to use
|
||||
different DCT block size.
|
||||
All N from 1 to 16 are possible. Default is 8 (baseline format).
|
||||
Larger values produce higher compression,
|
||||
smaller values produce higher quality.
|
||||
SmartScale capable decoder (introduced with IJG JPEG 8) required.
|
||||
|
||||
|
||||
Version 8b 16-May-2010
|
||||
-----------------------
|
||||
|
||||
Repair problem in new memory source manager with corrupt JPEG data.
|
||||
Thank to Ted Campbell and Samuel Chun for the report.
|
||||
|
||||
Repair problem in Makefile.am test target.
|
||||
Thank to anonymous user for the report.
|
||||
|
||||
Support MinGW installation with automatic configure.
|
||||
Thank to Volker Grabsch for the suggestion.
|
||||
|
||||
|
||||
Version 8a 28-Feb-2010
|
||||
-----------------------
|
||||
|
||||
Writing tables-only datastreams via jpeg_write_tables works again.
|
||||
|
||||
Support 32-bit BMPs (RGB image with Alpha channel) for read in cjpeg.
|
||||
Thank to Brett Blackham for the suggestion.
|
||||
|
||||
Improve accuracy in floating point IDCT calculation.
|
||||
Thank to Robert Hooke for the hint.
|
||||
|
||||
|
||||
Version 8 10-Jan-2010
|
||||
----------------------
|
||||
|
||||
jpegtran now supports the same -scale option as djpeg for "lossless" resize.
|
||||
An implementation of the JPEG SmartScale extension is required for this
|
||||
feature. A (draft) specification of the JPEG SmartScale extension is
|
||||
available as a contributed document at ITU and ISO. Revision 2 or later
|
||||
of the document is required (latest document version is Revision 3).
|
||||
The SmartScale extension will enable more features beside lossless resize
|
||||
in future implementations, as described in the document (new compression
|
||||
options).
|
||||
|
||||
Add sanity check in BMP reader module to avoid cjpeg crash for empty input
|
||||
image (thank to Isaev Ildar of ISP RAS, Moscow, RU for reporting this error).
|
||||
|
||||
Add data source and destination managers for read from and write to
|
||||
memory buffers. New API functions jpeg_mem_src and jpeg_mem_dest.
|
||||
Thank to Roberto Boni from Italy for the suggestion.
|
||||
|
||||
|
||||
Version 7 27-Jun-2009
|
||||
----------------------
|
||||
|
||||
New scaled DCTs implemented.
|
||||
djpeg now supports scalings N/8 with all N from 1 to 16.
|
||||
cjpeg now supports scalings 8/N with all N from 1 to 16.
|
||||
Scaled DCTs with size larger than 8 are now also used for resolving the
|
||||
common 2x2 chroma subsampling case without additional spatial resampling.
|
||||
Separate spatial resampling for those kind of files is now only necessary
|
||||
for N>8 scaling cases.
|
||||
Furthermore, separate scaled DCT functions are provided for direct resolving
|
||||
of the common asymmetric subsampling cases (2x1 and 1x2) without additional
|
||||
spatial resampling.
|
||||
|
||||
cjpeg -quality option has been extended for support of separate quality
|
||||
settings for luminance and chrominance (or in general, for every provided
|
||||
quantization table slot).
|
||||
New API function jpeg_default_qtables() and q_scale_factor array in library.
|
||||
|
||||
Added -nosmooth option to cjpeg, complementary to djpeg.
|
||||
New variable "do_fancy_downsampling" in library, complement to fancy
|
||||
upsampling. Fancy upsampling now uses direct DCT scaling with sizes
|
||||
larger than 8. The old method is not reversible and has been removed.
|
||||
|
||||
Support arithmetic entropy encoding and decoding.
|
||||
Added files jaricom.c, jcarith.c, jdarith.c.
|
||||
|
||||
Straighten the file structure:
|
||||
Removed files jidctred.c, jcphuff.c, jchuff.h, jdphuff.c, jdhuff.h.
|
||||
|
||||
jpegtran has a new "lossless" cropping feature.
|
||||
|
||||
Implement -perfect option in jpegtran, new API function
|
||||
jtransform_perfect_transform() in transupp. (DP 204_perfect.dpatch)
|
||||
|
||||
Better error messages for jpegtran fopen failure.
|
||||
(DP 203_jpegtran_errmsg.dpatch)
|
||||
|
||||
Fix byte order issue with 16bit PPM/PGM files in rdppm.c/wrppm.c:
|
||||
according to Netpbm, the de facto standard implementation of the PNM formats,
|
||||
the most significant byte is first. (DP 203_rdppm.dpatch)
|
||||
|
||||
Add -raw option to rdjpgcom not to mangle the output.
|
||||
(DP 205_rdjpgcom_raw.dpatch)
|
||||
|
||||
Make rdjpgcom locale aware. (DP 201_rdjpgcom_locale.dpatch)
|
||||
|
||||
Add extern "C" to jpeglib.h.
|
||||
This avoids the need to put extern "C" { ... } around #include "jpeglib.h"
|
||||
in your C++ application. Defining the symbol DONT_USE_EXTERN_C in the
|
||||
configuration prevents this. (DP 202_jpeglib.h_c++.dpatch)
|
||||
|
||||
|
||||
Version 6b 27-Mar-1998
|
||||
-----------------------
|
||||
|
||||
jpegtran has new features for lossless image transformations (rotation
|
||||
and flipping) as well as "lossless" reduction to grayscale.
|
||||
|
||||
jpegtran now copies comments by default; it has a -copy switch to enable
|
||||
copying all APPn blocks as well, or to suppress comments. (Formerly it
|
||||
always suppressed comments and APPn blocks.) jpegtran now also preserves
|
||||
JFIF version and resolution information.
|
||||
|
||||
New decompressor library feature: COM and APPn markers found in the input
|
||||
file can be saved in memory for later use by the application. (Before,
|
||||
you had to code this up yourself with a custom marker processor.)
|
||||
|
||||
There is an unused field "void * client_data" now in compress and decompress
|
||||
parameter structs; this may be useful in some applications.
|
||||
|
||||
JFIF version number information is now saved by the decoder and accepted by
|
||||
the encoder. jpegtran uses this to copy the source file's version number,
|
||||
to ensure "jpegtran -copy all" won't create bogus files that contain JFXX
|
||||
extensions but claim to be version 1.01. Applications that generate their
|
||||
own JFXX extension markers also (finally) have a supported way to cause the
|
||||
encoder to emit JFIF version number 1.02.
|
||||
|
||||
djpeg's trace mode reports JFIF 1.02 thumbnail images as such, rather
|
||||
than as unknown APP0 markers.
|
||||
|
||||
In -verbose mode, djpeg and rdjpgcom will try to print the contents of
|
||||
APP12 markers as text. Some digital cameras store useful text information
|
||||
in APP12 markers.
|
||||
|
||||
Handling of truncated data streams is more robust: blocks beyond the one in
|
||||
which the error occurs will be output as uniform gray, or left unchanged
|
||||
if decoding a progressive JPEG. The appearance no longer depends on the
|
||||
Huffman tables being used.
|
||||
|
||||
Huffman tables are checked for validity much more carefully than before.
|
||||
|
||||
To avoid the Unisys LZW patent, djpeg's GIF output capability has been
|
||||
changed to produce "uncompressed GIFs", and cjpeg's GIF input capability
|
||||
has been removed altogether. We're not happy about it either, but there
|
||||
seems to be no good alternative.
|
||||
|
||||
The configure script now supports building libjpeg as a shared library
|
||||
on many flavors of Unix (all the ones that GNU libtool knows how to
|
||||
build shared libraries for). Use "./configure --enable-shared" to
|
||||
try this out.
|
||||
|
||||
New jconfig file and makefiles for Microsoft Visual C++ and Developer Studio.
|
||||
Also, a jconfig file and a build script for Metrowerks CodeWarrior
|
||||
on Apple Macintosh. makefile.dj has been updated for DJGPP v2, and there
|
||||
are miscellaneous other minor improvements in the makefiles.
|
||||
|
||||
jmemmac.c now knows how to create temporary files following Mac System 7
|
||||
conventions.
|
||||
|
||||
djpeg's -map switch is now able to read raw-format PPM files reliably.
|
||||
|
||||
cjpeg -progressive -restart no longer generates any unnecessary DRI markers.
|
||||
|
||||
Multiple calls to jpeg_simple_progression for a single JPEG object
|
||||
no longer leak memory.
|
||||
|
||||
|
||||
Version 6a 7-Feb-96
|
||||
--------------------
|
||||
|
||||
Library initialization sequence modified to detect version mismatches
|
||||
and struct field packing mismatches between library and calling application.
|
||||
This change requires applications to be recompiled, but does not require
|
||||
any application source code change.
|
||||
|
||||
All routine declarations changed to the style "GLOBAL(type) name ...",
|
||||
that is, GLOBAL, LOCAL, METHODDEF, EXTERN are now macros taking the
|
||||
routine's return type as an argument. This makes it possible to add
|
||||
Microsoft-style linkage keywords to all the routines by changing just
|
||||
these macros. Note that any application code that was using these macros
|
||||
will have to be changed.
|
||||
|
||||
DCT coefficient quantization tables are now stored in normal array order
|
||||
rather than zigzag order. Application code that calls jpeg_add_quant_table,
|
||||
or otherwise manipulates quantization tables directly, will need to be
|
||||
changed. If you need to make such code work with either older or newer
|
||||
versions of the library, a test like "#if JPEG_LIB_VERSION >= 61" is
|
||||
recommended.
|
||||
|
||||
djpeg's trace capability now dumps DQT tables in natural order, not zigzag
|
||||
order. This allows the trace output to be made into a "-qtables" file
|
||||
more easily.
|
||||
|
||||
New system-dependent memory manager module for use on Apple Macintosh.
|
||||
|
||||
Fix bug in cjpeg's -smooth option: last one or two scanlines would be
|
||||
duplicates of the prior line unless the image height mod 16 was 1 or 2.
|
||||
|
||||
Repair minor problems in VMS, BCC, MC6 makefiles.
|
||||
|
||||
New configure script based on latest GNU Autoconf.
|
||||
|
||||
Correct the list of include files needed by MetroWerks C for ccommand().
|
||||
|
||||
Numerous small documentation updates.
|
||||
|
||||
|
||||
Version 6 2-Aug-95
|
||||
-------------------
|
||||
|
||||
Progressive JPEG support: library can read and write full progressive JPEG
|
||||
files. A "buffered image" mode supports incremental decoding for on-the-fly
|
||||
display of progressive images. Simply recompiling an existing IJG-v5-based
|
||||
decoder with v6 should allow it to read progressive files, though of course
|
||||
without any special progressive display.
|
||||
|
||||
New "jpegtran" application performs lossless transcoding between different
|
||||
JPEG formats; primarily, it can be used to convert baseline to progressive
|
||||
JPEG and vice versa. In support of jpegtran, the library now allows lossless
|
||||
reading and writing of JPEG files as DCT coefficient arrays. This ability
|
||||
may be of use in other applications.
|
||||
|
||||
Notes for programmers:
|
||||
* We changed jpeg_start_decompress() to be able to suspend; this makes all
|
||||
decoding modes available to suspending-input applications. However,
|
||||
existing applications that use suspending input will need to be changed
|
||||
to check the return value from jpeg_start_decompress(). You don't need to
|
||||
do anything if you don't use a suspending data source.
|
||||
* We changed the interface to the virtual array routines: access_virt_array
|
||||
routines now take a count of the number of rows to access this time. The
|
||||
last parameter to request_virt_array routines is now interpreted as the
|
||||
maximum number of rows that may be accessed at once, but not necessarily
|
||||
the height of every access.
|
||||
|
||||
|
||||
Version 5b 15-Mar-95
|
||||
---------------------
|
||||
|
||||
Correct bugs with grayscale images having v_samp_factor > 1.
|
||||
|
||||
jpeg_write_raw_data() now supports output suspension.
|
||||
|
||||
Correct bugs in "configure" script for case of compiling in
|
||||
a directory other than the one containing the source files.
|
||||
|
||||
Repair bug in jquant1.c: sometimes didn't use as many colors as it could.
|
||||
|
||||
Borland C makefile and jconfig file work under either MS-DOS or OS/2.
|
||||
|
||||
Miscellaneous improvements to documentation.
|
||||
|
||||
|
||||
Version 5a 7-Dec-94
|
||||
--------------------
|
||||
|
||||
Changed color conversion roundoff behavior so that grayscale values are
|
||||
represented exactly. (This causes test image files to change.)
|
||||
|
||||
Make ordered dither use 16x16 instead of 4x4 pattern for a small quality
|
||||
improvement.
|
||||
|
||||
New configure script based on latest GNU Autoconf.
|
||||
Fix configure script to handle CFLAGS correctly.
|
||||
Rename *.auto files to *.cfg, so that configure script still works if
|
||||
file names have been truncated for DOS.
|
||||
|
||||
Fix bug in rdbmp.c: didn't allow for extra data between header and image.
|
||||
|
||||
Modify rdppm.c/wrppm.c to handle 2-byte raw PPM/PGM formats for 12-bit data.
|
||||
|
||||
Fix several bugs in rdrle.c.
|
||||
|
||||
NEED_SHORT_EXTERNAL_NAMES option was broken.
|
||||
|
||||
Revise jerror.h/jerror.c for more flexibility in message table.
|
||||
|
||||
Repair oversight in jmemname.c NO_MKTEMP case: file could be there
|
||||
but unreadable.
|
||||
|
||||
|
||||
Version 5 24-Sep-94
|
||||
--------------------
|
||||
|
||||
Version 5 represents a nearly complete redesign and rewrite of the IJG
|
||||
software. Major user-visible changes include:
|
||||
* Automatic configuration simplifies installation for most Unix systems.
|
||||
* A range of speed vs. image quality tradeoffs are supported.
|
||||
This includes resizing of an image during decompression: scaling down
|
||||
by a factor of 1/2, 1/4, or 1/8 is handled very efficiently.
|
||||
* New programs rdjpgcom and wrjpgcom allow insertion and extraction
|
||||
of text comments in a JPEG file.
|
||||
|
||||
The application programmer's interface to the library has changed completely.
|
||||
Notable improvements include:
|
||||
* We have eliminated the use of callback routines for handling the
|
||||
uncompressed image data. The application now sees the library as a
|
||||
set of routines that it calls to read or write image data on a
|
||||
scanline-by-scanline basis.
|
||||
* The application image data is represented in a conventional interleaved-
|
||||
pixel format, rather than as a separate array for each color channel.
|
||||
This can save a copying step in many programs.
|
||||
* The handling of compressed data has been cleaned up: the application can
|
||||
supply routines to source or sink the compressed data. It is possible to
|
||||
suspend processing on source/sink buffer overrun, although this is not
|
||||
supported in all operating modes.
|
||||
* All static state has been eliminated from the library, so that multiple
|
||||
instances of compression or decompression can be active concurrently.
|
||||
* JPEG abbreviated datastream formats are supported, ie, quantization and
|
||||
Huffman tables can be stored separately from the image data.
|
||||
* And not only that, but the documentation of the library has improved
|
||||
considerably!
|
||||
|
||||
|
||||
The last widely used release before the version 5 rewrite was version 4A of
|
||||
18-Feb-93. Change logs before that point have been discarded, since they
|
||||
are not of much interest after the rewrite.
|
||||
@@ -0,0 +1,663 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ============================================================
|
||||
# gzip - 单文件 gzip 解码器(RFC 1952 + RFC 1951 deflate)
|
||||
#
|
||||
# OOP 范式对齐 zipfile: 数据由 GzipFile 对象持有并负责释放;
|
||||
# 内部位流/Huffman 助手为模块级私有函数。
|
||||
#
|
||||
# 用法:
|
||||
# gz: gzip.GzipFile | t.CPtr = gzip.GzipFile("train-images-idx3-ubyte.gz")
|
||||
# if gz is None or gz.ok == 0: ...
|
||||
# buf: bytes = gz.read_all() # 内部 malloc, read_all 返回即解压完成
|
||||
# n: t.CSizeT = gz.dst_len # 原始长度(由对象持有, close 时释放)
|
||||
# gz.close()
|
||||
#
|
||||
# 覆盖 stored/fixed/dynamic 三种 deflate 块; 不解 CRC。
|
||||
# ============================================================
|
||||
import t, c
|
||||
import stdlib
|
||||
import string
|
||||
import w32.fileio as fileio
|
||||
from stdint import *
|
||||
|
||||
|
||||
# ============================================================
|
||||
# GzipFile
|
||||
# ============================================================
|
||||
class GzipFile:
|
||||
ok: t.CInt # 1 = 可正常使用
|
||||
err: t.CInt # 错误码(0 无, <0 出错)
|
||||
src: BYTEPTR # 整个 .gz 文件的内存副本(构造时读入, close 释放)
|
||||
src_len: t.CSizeT
|
||||
dst: BYTEPTR # 解压输出缓冲(read_all 生成, close 释放)
|
||||
dst_len: t.CSizeT # 解压结果长度
|
||||
_need_close_src: t.CInt # 是否持有 src(读失败时为 0)
|
||||
|
||||
def __init__(self, path: str):
|
||||
self.ok = 0
|
||||
self.err = 0
|
||||
self.src = None
|
||||
self.src_len = 0
|
||||
self.dst = None
|
||||
self.dst_len = 0
|
||||
self._need_close_src = 0
|
||||
if path is None:
|
||||
self.err = -1
|
||||
return
|
||||
f: fileio.File | t.CPtr = fileio.File(path, fileio.MODE.R)
|
||||
if f.closed:
|
||||
self.err = -2 # 打不开文件
|
||||
return
|
||||
sz: LONGLONG = f.size()
|
||||
if sz < 18:
|
||||
f.close()
|
||||
self.err = -3 # 文件过小
|
||||
return
|
||||
self.src_len = t.CSizeT(sz)
|
||||
# read_all 契约: 最多读 max_count-1 字节并补 NUL; 故按 src_len+1 申请与请求
|
||||
buf: BYTEPTR = stdlib.malloc(self.src_len + 1)
|
||||
if buf is None:
|
||||
f.close()
|
||||
self.err = -4
|
||||
return
|
||||
got: LONG = f.read_all(buf, ULONG(self.src_len + 1))
|
||||
f.close()
|
||||
if got <= 0 or t.CSizeT(got) != self.src_len:
|
||||
stdlib.free(buf)
|
||||
self.err = -5 # 读取失败或被截断
|
||||
return
|
||||
# gzip 魔数校验
|
||||
ub: t.CUnsignedChar | t.CPtr = buf
|
||||
if ub[0] != 0x1F or ub[1] != 0x8B or ub[2] != 8:
|
||||
stdlib.free(buf)
|
||||
self.err = -6
|
||||
return
|
||||
self.src = buf
|
||||
self._need_close_src = 1
|
||||
self.ok = 1
|
||||
|
||||
def read_all(self) -> BYTEPTR:
|
||||
"""解压整个成员。返回内部持有的输出缓冲(或 None); 长度见 self.dst_len。"""
|
||||
if self.ok == 0 or self.src is None:
|
||||
return None
|
||||
if self.dst is not None:
|
||||
return self.dst # 已解压过
|
||||
want: t.CUInt32T = gzip_isize(self.src, self.src_len)
|
||||
cap: t.CSizeT = t.CSizeT(want)
|
||||
if cap == 0:
|
||||
cap = self.src_len * 64 # 无 ISIZE 时保守初值
|
||||
if cap < 1024:
|
||||
cap = 1024
|
||||
# 保险起见: ISIZE 与实际不符时报"目标缓冲不足", 就翻倍重试(正常情况下一次成功)
|
||||
out: BYTEPTR = None
|
||||
n: LONG = 0
|
||||
while True:
|
||||
out = stdlib.malloc(cap)
|
||||
if out is None:
|
||||
self.err = -7
|
||||
return None
|
||||
n = _gzip_decode(self.src, self.src_len, out, cap)
|
||||
if n == -9 or n == -18 or n == -23:
|
||||
stdlib.free(out)
|
||||
out = None
|
||||
if cap >= 1073741824:
|
||||
self.err = t.CInt(n)
|
||||
return None
|
||||
cap = cap * 2
|
||||
continue
|
||||
break
|
||||
if n <= 0:
|
||||
stdlib.free(out)
|
||||
self.err = t.CInt(n) # n 为负错误码, 原样保存
|
||||
return None
|
||||
self.dst = out
|
||||
self.dst_len = t.CSizeT(n)
|
||||
return out
|
||||
|
||||
def close(self):
|
||||
if self._need_close_src != 0 and self.src is not None:
|
||||
stdlib.free(self.src)
|
||||
if self.dst is not None:
|
||||
stdlib.free(self.dst)
|
||||
self.src = None
|
||||
self.dst = None
|
||||
self.src_len = 0
|
||||
self.dst_len = 0
|
||||
self._need_close_src = 0
|
||||
self.ok = 0
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 公共访问器(跨模块读字段用函数, 避免 stub 字段访问陷阱)
|
||||
# ============================================================
|
||||
def gzip_open(path: str) -> GzipFile | t.CPtr:
|
||||
return GzipFile(path)
|
||||
|
||||
|
||||
def gzip_ok(gz: GzipFile | t.CPtr) -> t.CInt:
|
||||
if gz is None:
|
||||
return 0
|
||||
return gz.ok
|
||||
|
||||
|
||||
def gzip_err(gz: GzipFile | t.CPtr) -> t.CInt:
|
||||
if gz is None:
|
||||
return -1
|
||||
return gz.err
|
||||
|
||||
|
||||
def gzip_dst_len(gz: GzipFile | t.CPtr) -> t.CSizeT:
|
||||
if gz is None:
|
||||
return 0
|
||||
return gz.dst_len
|
||||
|
||||
|
||||
def gzip_read(gz: GzipFile | t.CPtr) -> BYTEPTR:
|
||||
if gz is None:
|
||||
return None
|
||||
return gz.read_all()
|
||||
|
||||
|
||||
def gzip_close(gz: GzipFile | t.CPtr):
|
||||
if gz is not None:
|
||||
gz.close()
|
||||
|
||||
|
||||
def gzip_decode_bytes(src: t.CPtr, src_len: t.CSizeT,
|
||||
dst: t.CPtr, dst_cap: t.CSizeT) -> LONG:
|
||||
"""原始缓冲到缓冲的解压(不读文件), 供下载流程直接使用"""
|
||||
return _gzip_decode(src, src_len, dst, dst_cap)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 位流状态(模块级, 单线程; 每次 _gzip_decode 重置)
|
||||
# ============================================================
|
||||
_g_bits: t.CUInt64T = 0
|
||||
_g_bitcnt: t.CInt = 0
|
||||
_g_pos: t.CSizeT = 0
|
||||
_g_src: t.CPtr = None
|
||||
_g_src_len: t.CSizeT = 0
|
||||
|
||||
|
||||
def gzip_isize(src: t.CPtr, src_len: t.CSizeT) -> t.CUInt32T:
|
||||
"""gzip 尾部 ISIZE(小端 u32), 即原始长度提示"""
|
||||
if src is None or src_len < 4:
|
||||
return 0
|
||||
ub: t.CUnsignedChar | t.CPtr = src
|
||||
p: t.CSizeT = src_len - 4
|
||||
# 小端 u32 拼装(64 位中间量后收窄)
|
||||
v64: t.CUInt64T = t.CUInt64T(ub[p]) \
|
||||
+ t.CUInt64T(ub[p + 1]) * 256 \
|
||||
+ t.CUInt64T(ub[p + 2]) * 65536 \
|
||||
+ t.CUInt64T(ub[p + 3]) * 16777216
|
||||
return t.CUInt32T(v64)
|
||||
|
||||
|
||||
def _reset(src: t.CPtr, src_len: t.CSizeT):
|
||||
global _g_bits, _g_bitcnt, _g_pos, _g_src, _g_src_len
|
||||
_g_bits = 0
|
||||
_g_bitcnt = 0
|
||||
_g_pos = 0
|
||||
_g_src = src
|
||||
_g_src_len = src_len
|
||||
|
||||
|
||||
def _fill(n: t.CInt) -> t.CInt:
|
||||
global _g_bits, _g_bitcnt, _g_pos, _g_src, _g_src_len
|
||||
sb: t.CUnsignedChar | t.CPtr = _g_src
|
||||
while _g_bitcnt < n:
|
||||
if _g_pos >= _g_src_len:
|
||||
return -1
|
||||
_g_bits = _g_bits | (t.CUInt64T(sb[_g_pos]) << _g_bitcnt)
|
||||
_g_pos += 1
|
||||
_g_bitcnt += 8
|
||||
return 0
|
||||
|
||||
|
||||
def _bits(n: t.CInt) -> t.CInt:
|
||||
global _g_bits, _g_bitcnt
|
||||
if _fill(n) != 0:
|
||||
return -1
|
||||
v: t.CUInt64T = _g_bits & ((t.CUInt64T(1) << n) - 1)
|
||||
_g_bits = _g_bits >> n
|
||||
_g_bitcnt -= n
|
||||
return t.CInt(v)
|
||||
|
||||
|
||||
def _byte() -> t.CInt:
|
||||
return _bits(8)
|
||||
|
||||
|
||||
def _align():
|
||||
global _g_bits, _g_bitcnt
|
||||
_g_bits = 0
|
||||
_g_bitcnt = 0
|
||||
|
||||
|
||||
def _decode(count: t.CInt | t.CPtr, first: t.CInt | t.CPtr,
|
||||
off: t.CInt | t.CPtr, sorted: t.CInt | t.CPtr) -> t.CInt:
|
||||
code: t.CInt = 0
|
||||
ln: t.CInt = 1
|
||||
while ln <= 15:
|
||||
b: t.CInt = _bits(1)
|
||||
if b < 0:
|
||||
return -1
|
||||
code = (code << 1) | b
|
||||
if count[ln] != 0:
|
||||
d: t.CInt = code - first[ln]
|
||||
if d >= 0 and d < count[ln]:
|
||||
return sorted[off[ln] + d]
|
||||
ln += 1
|
||||
return -1
|
||||
|
||||
|
||||
def _build(first: t.CInt | t.CPtr, off: t.CInt | t.CPtr,
|
||||
count: t.CInt | t.CPtr) -> t.CInt:
|
||||
anylen: t.CInt = 0
|
||||
l1: t.CInt = 1
|
||||
while l1 <= 15:
|
||||
if count[l1] != 0:
|
||||
anylen = 1
|
||||
l1 += 1
|
||||
if anylen == 0:
|
||||
return -1
|
||||
left: t.CInt = 1
|
||||
l2: t.CInt = 1
|
||||
while l2 <= 15:
|
||||
left = left << 1
|
||||
if count[l2] > left:
|
||||
return -1
|
||||
left = left - count[l2]
|
||||
l2 += 1
|
||||
code: t.CInt = 0
|
||||
l3: t.CInt = 1
|
||||
while l3 <= 15:
|
||||
code = (code + count[l3 - 1]) << 1
|
||||
first[l3] = code
|
||||
l3 += 1
|
||||
offsum: t.CInt = 0
|
||||
l4: t.CInt = 1
|
||||
while l4 <= 15:
|
||||
off[l4] = offsum
|
||||
offsum += count[l4]
|
||||
l4 += 1
|
||||
return 0
|
||||
|
||||
|
||||
def _collect(len_arr: t.CInt | t.CPtr, cnt: t.CInt | t.CPtr,
|
||||
sorted_arr: t.CInt | t.CPtr, nsym: t.CInt,
|
||||
pos: t.CInt | t.CPtr):
|
||||
string.memset(cnt, 0, 17 * 4)
|
||||
s1: t.CInt = 0
|
||||
while s1 < nsym:
|
||||
if len_arr[s1] != 0:
|
||||
cnt[len_arr[s1]] += 1
|
||||
s1 += 1
|
||||
run: t.CInt = 0
|
||||
l5: t.CInt = 1
|
||||
while l5 <= 15:
|
||||
pos[l5] = run
|
||||
run += cnt[l5]
|
||||
l5 += 1
|
||||
s2: t.CInt = 0
|
||||
while s2 < nsym:
|
||||
if len_arr[s2] != 0:
|
||||
sorted_arr[pos[len_arr[s2]]] = s2
|
||||
pos[len_arr[s2]] += 1
|
||||
s2 += 1
|
||||
|
||||
|
||||
def _len_extra(code: t.CInt) -> t.CInt:
|
||||
if code == 285:
|
||||
return 0
|
||||
if code >= 281:
|
||||
return 5
|
||||
if code >= 277:
|
||||
return 4
|
||||
if code >= 273:
|
||||
return 3
|
||||
if code >= 269:
|
||||
return 2
|
||||
if code >= 265:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def _len_base(code: t.CInt) -> t.CInt:
|
||||
if code == 285:
|
||||
return 258
|
||||
if code >= 281:
|
||||
return 131 + ((code - 281) << 5)
|
||||
if code >= 277:
|
||||
return 67 + ((code - 277) << 4)
|
||||
if code >= 273:
|
||||
return 35 + ((code - 273) << 3)
|
||||
if code >= 269:
|
||||
return 19 + ((code - 269) << 2)
|
||||
if code >= 265:
|
||||
return 11 + ((code - 265) << 1)
|
||||
return 3 + (code - 257)
|
||||
|
||||
|
||||
def _dist_extra(code: t.CInt) -> t.CInt:
|
||||
if code < 4:
|
||||
return 0
|
||||
return (code - 4) // 2 + 1
|
||||
|
||||
|
||||
def _dist_base(code: t.CInt) -> t.CInt:
|
||||
if code < 4:
|
||||
return code + 1
|
||||
e: t.CInt = (code - 4) // 2 + 1
|
||||
f: t.CInt = (1 << (e + 1)) + 1
|
||||
if code % 2 == 0:
|
||||
return f
|
||||
return f + (1 << e)
|
||||
|
||||
|
||||
def _cl_order_set(arr: t.CInt | t.CPtr, idx: t.CInt, val: t.CInt):
|
||||
arr[idx] = val
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 核心解压(模块级私有)
|
||||
# ============================================================
|
||||
def _gzip_decode(src: t.CPtr, src_len: t.CSizeT,
|
||||
dst: t.CPtr, dst_cap: t.CSizeT) -> LONG:
|
||||
if src is None or dst is None or src_len < 18 or dst_cap == 0:
|
||||
return -1
|
||||
ub: t.CUnsignedChar | t.CPtr = src
|
||||
if ub[0] != 0x1F or ub[1] != 0x8B or ub[2] != 8:
|
||||
return -2
|
||||
_reset(src, src_len)
|
||||
flg: t.CInt = t.CInt(ub[3])
|
||||
hdr_len: t.CSizeT = 10
|
||||
if flg & 0x04 != 0:
|
||||
xlen: t.CSizeT = t.CSizeT(ub[hdr_len]) | (t.CSizeT(ub[hdr_len + 1]) << 8)
|
||||
hdr_len += 2 + xlen
|
||||
if flg & 0x08 != 0:
|
||||
while hdr_len < src_len and ub[hdr_len] != 0:
|
||||
hdr_len += 1
|
||||
hdr_len += 1
|
||||
if flg & 0x10 != 0:
|
||||
while hdr_len < src_len and ub[hdr_len] != 0:
|
||||
hdr_len += 1
|
||||
hdr_len += 1
|
||||
if flg & 0x02 != 0:
|
||||
hdr_len += 2
|
||||
if hdr_len >= src_len:
|
||||
return -3
|
||||
global _g_pos
|
||||
_g_pos = hdr_len
|
||||
|
||||
dout: t.CUnsignedChar | t.CPtr = dst
|
||||
wpos: t.CSizeT = 0
|
||||
|
||||
lit_len: t.CInt | t.CPtr = stdlib.malloc(288 * 4)
|
||||
lit_cnt: t.CInt | t.CPtr = stdlib.malloc(17 * 4)
|
||||
lit_first: t.CInt | t.CPtr = stdlib.malloc(17 * 4)
|
||||
lit_off: t.CInt | t.CPtr = stdlib.malloc(17 * 4)
|
||||
lit_sorted: t.CInt | t.CPtr = stdlib.malloc(288 * 4)
|
||||
dst_len: t.CInt | t.CPtr = stdlib.malloc(32 * 4)
|
||||
dst_cnt: t.CInt | t.CPtr = stdlib.malloc(17 * 4)
|
||||
dst_first: t.CInt | t.CPtr = stdlib.malloc(17 * 4)
|
||||
dst_off: t.CInt | t.CPtr = stdlib.malloc(17 * 4)
|
||||
dst_sorted: t.CInt | t.CPtr = stdlib.malloc(32 * 4)
|
||||
cl_len: t.CInt | t.CPtr = stdlib.malloc(19 * 4)
|
||||
cl_order: t.CInt | t.CPtr = stdlib.malloc(19 * 4)
|
||||
cl_cnt: t.CInt | t.CPtr = stdlib.malloc(17 * 4)
|
||||
cl_first: t.CInt | t.CPtr = stdlib.malloc(17 * 4)
|
||||
cl_off: t.CInt | t.CPtr = stdlib.malloc(17 * 4)
|
||||
cl_sorted: t.CInt | t.CPtr = stdlib.malloc(19 * 4)
|
||||
pos_tmp: t.CInt | t.CPtr = stdlib.malloc(17 * 4)
|
||||
if lit_len is None or lit_cnt is None or lit_first is None \
|
||||
or lit_off is None or lit_sorted is None or dst_len is None \
|
||||
or dst_cnt is None or dst_first is None or dst_off is None \
|
||||
or dst_sorted is None or cl_len is None or cl_order is None \
|
||||
or cl_cnt is None or cl_first is None or cl_off is None \
|
||||
or cl_sorted is None or pos_tmp is None:
|
||||
return -4
|
||||
|
||||
# 全部表缓冲清零(初始 malloc 未清零; 防个别路径读到垃圾表导致提前/错乱收尾)
|
||||
string.memset(lit_len, 0, 288 * 4)
|
||||
string.memset(lit_cnt, 0, 17 * 4)
|
||||
string.memset(lit_first, 0, 17 * 4)
|
||||
string.memset(lit_off, 0, 17 * 4)
|
||||
string.memset(lit_sorted, 0, 288 * 4)
|
||||
string.memset(dst_len, 0, 32 * 4)
|
||||
string.memset(dst_cnt, 0, 17 * 4)
|
||||
string.memset(dst_first, 0, 17 * 4)
|
||||
string.memset(dst_off, 0, 17 * 4)
|
||||
string.memset(dst_sorted, 0, 32 * 4)
|
||||
string.memset(cl_len, 0, 19 * 4)
|
||||
string.memset(cl_order, 0, 19 * 4)
|
||||
string.memset(cl_cnt, 0, 17 * 4)
|
||||
string.memset(cl_first, 0, 17 * 4)
|
||||
string.memset(cl_off, 0, 17 * 4)
|
||||
string.memset(cl_sorted, 0, 19 * 4)
|
||||
string.memset(pos_tmp, 0, 17 * 4)
|
||||
|
||||
_cl_order_set(cl_order, 0, 16)
|
||||
_cl_order_set(cl_order, 1, 17)
|
||||
_cl_order_set(cl_order, 2, 18)
|
||||
_cl_order_set(cl_order, 3, 0)
|
||||
_cl_order_set(cl_order, 4, 8)
|
||||
_cl_order_set(cl_order, 5, 7)
|
||||
_cl_order_set(cl_order, 6, 9)
|
||||
_cl_order_set(cl_order, 7, 6)
|
||||
_cl_order_set(cl_order, 8, 10)
|
||||
_cl_order_set(cl_order, 9, 5)
|
||||
_cl_order_set(cl_order, 10, 11)
|
||||
_cl_order_set(cl_order, 11, 4)
|
||||
_cl_order_set(cl_order, 12, 12)
|
||||
_cl_order_set(cl_order, 13, 3)
|
||||
_cl_order_set(cl_order, 14, 13)
|
||||
_cl_order_set(cl_order, 15, 2)
|
||||
_cl_order_set(cl_order, 16, 14)
|
||||
_cl_order_set(cl_order, 17, 1)
|
||||
_cl_order_set(cl_order, 18, 15)
|
||||
|
||||
err: t.CInt = 0
|
||||
final: t.CInt = 0
|
||||
|
||||
while final == 0 and err == 0:
|
||||
bfinal: t.CInt = _bits(1)
|
||||
btype: t.CInt = _bits(2)
|
||||
if bfinal < 0 or btype < 0:
|
||||
err = -5
|
||||
break
|
||||
final = bfinal
|
||||
if btype == 0:
|
||||
_align()
|
||||
slen: t.CInt = _bits(16)
|
||||
snlen: t.CInt = _bits(16)
|
||||
if slen < 0 or snlen < 0:
|
||||
err = -6
|
||||
break
|
||||
if (slen ^ snlen) != 0xFFFF:
|
||||
err = -7
|
||||
break
|
||||
i: t.CSizeT = 0
|
||||
while i < t.CSizeT(slen):
|
||||
ch: t.CInt = _byte()
|
||||
if ch < 0:
|
||||
err = -8
|
||||
break
|
||||
if wpos >= dst_cap:
|
||||
err = -9
|
||||
break
|
||||
dout[wpos] = t.CUnsignedChar(ch)
|
||||
wpos += 1
|
||||
i += 1
|
||||
if err != 0:
|
||||
break
|
||||
elif btype == 1:
|
||||
string.memset(lit_cnt, 0, 17 * 4)
|
||||
j: t.CInt = 0
|
||||
while j < 144:
|
||||
lit_len[j] = 8
|
||||
j += 1
|
||||
while j < 256:
|
||||
lit_len[j] = 9
|
||||
j += 1
|
||||
while j < 280:
|
||||
lit_len[j] = 7
|
||||
j += 1
|
||||
while j < 288:
|
||||
lit_len[j] = 8
|
||||
j += 1
|
||||
_collect(lit_len, lit_cnt, lit_sorted, 288, pos_tmp)
|
||||
if _build(lit_first, lit_off, lit_cnt) != 0:
|
||||
err = -10
|
||||
break
|
||||
else:
|
||||
hlit: t.CInt = _bits(5) + 257
|
||||
hdist: t.CInt = _bits(5) + 1
|
||||
hclen: t.CInt = _bits(4) + 4
|
||||
if hlit < 0 or hdist < 0 or hclen < 0:
|
||||
err = -11
|
||||
break
|
||||
string.memset(cl_len, 0, 19 * 4)
|
||||
k: t.CInt = 0
|
||||
while k < hclen:
|
||||
vv: t.CInt = _bits(3)
|
||||
if vv < 0:
|
||||
err = -12
|
||||
break
|
||||
cl_len[cl_order[k]] = vv
|
||||
k += 1
|
||||
if err != 0:
|
||||
break
|
||||
_collect(cl_len, cl_cnt, cl_sorted, 19, pos_tmp)
|
||||
if _build(cl_first, cl_off, cl_cnt) != 0:
|
||||
err = -13
|
||||
break
|
||||
string.memset(lit_len, 0, 288 * 4)
|
||||
string.memset(dst_len, 0, 32 * 4)
|
||||
sym_i: t.CInt = 0
|
||||
while sym_i < hlit + hdist:
|
||||
sym: t.CInt = _decode(cl_cnt, cl_first, cl_off, cl_sorted)
|
||||
if sym < 0:
|
||||
err = -14
|
||||
break
|
||||
if sym < 16:
|
||||
if sym_i < hlit:
|
||||
lit_len[sym_i] = sym
|
||||
else:
|
||||
dst_len[sym_i - hlit] = sym
|
||||
sym_i += 1
|
||||
elif sym == 16:
|
||||
prev: t.CInt = 0
|
||||
if sym_i > 0:
|
||||
if sym_i - 1 < hlit:
|
||||
prev = lit_len[sym_i - 1]
|
||||
else:
|
||||
prev = dst_len[sym_i - hlit - 1]
|
||||
rp: t.CInt = _bits(2) + 3
|
||||
r2: t.CInt = 0
|
||||
while r2 < rp and sym_i < hlit + hdist:
|
||||
if sym_i < hlit:
|
||||
lit_len[sym_i] = prev
|
||||
else:
|
||||
dst_len[sym_i - hlit] = prev
|
||||
sym_i += 1
|
||||
r2 += 1
|
||||
elif sym == 17:
|
||||
rp2: t.CInt = _bits(3) + 3
|
||||
r3: t.CInt = 0
|
||||
while r3 < rp2 and sym_i < hlit + hdist:
|
||||
if sym_i < hlit:
|
||||
lit_len[sym_i] = 0
|
||||
else:
|
||||
dst_len[sym_i - hlit] = 0
|
||||
sym_i += 1
|
||||
r3 += 1
|
||||
else:
|
||||
rp3: t.CInt = _bits(7) + 11
|
||||
r4: t.CInt = 0
|
||||
while r4 < rp3 and sym_i < hlit + hdist:
|
||||
if sym_i < hlit:
|
||||
lit_len[sym_i] = 0
|
||||
else:
|
||||
dst_len[sym_i - hlit] = 0
|
||||
sym_i += 1
|
||||
r4 += 1
|
||||
if err != 0:
|
||||
break
|
||||
_collect(lit_len, lit_cnt, lit_sorted, hlit, pos_tmp)
|
||||
if _build(lit_first, lit_off, lit_cnt) != 0:
|
||||
err = -15
|
||||
break
|
||||
_collect(dst_len, dst_cnt, dst_sorted, hdist, pos_tmp)
|
||||
if _build(dst_first, dst_off, dst_cnt) != 0:
|
||||
err = -16
|
||||
break
|
||||
if err != 0:
|
||||
break
|
||||
|
||||
run: t.CInt = 1
|
||||
while run == 1 and err == 0:
|
||||
sym: t.CInt = _decode(lit_cnt, lit_first, lit_off, lit_sorted)
|
||||
if sym < 0:
|
||||
err = -17
|
||||
break
|
||||
if sym < 256:
|
||||
if wpos >= dst_cap:
|
||||
err = -18
|
||||
break
|
||||
dout[wpos] = t.CUnsignedChar(sym)
|
||||
wpos += 1
|
||||
elif sym == 256:
|
||||
run = 0
|
||||
else:
|
||||
le: t.CInt = _len_extra(sym)
|
||||
ev: t.CInt = _bits(le)
|
||||
if ev < 0:
|
||||
err = -19
|
||||
break
|
||||
length: t.CSizeT = t.CSizeT(_len_base(sym) + ev)
|
||||
ds: t.CInt = _decode(dst_cnt, dst_first, dst_off, dst_sorted)
|
||||
if ds < 0:
|
||||
err = -20
|
||||
break
|
||||
de: t.CInt = _dist_extra(ds)
|
||||
dv: t.CInt = _bits(de)
|
||||
if dv < 0:
|
||||
err = -21
|
||||
break
|
||||
dist: t.CSizeT = t.CSizeT(_dist_base(ds) + dv)
|
||||
if dist == 0 or dist > wpos:
|
||||
err = -22
|
||||
break
|
||||
ci: t.CSizeT = 0
|
||||
while ci < length:
|
||||
if wpos >= dst_cap:
|
||||
err = -23
|
||||
break
|
||||
dout[wpos] = dout[wpos - dist]
|
||||
wpos += 1
|
||||
ci += 1
|
||||
if err != 0:
|
||||
break
|
||||
|
||||
stdlib.free(lit_len)
|
||||
stdlib.free(lit_cnt)
|
||||
stdlib.free(lit_first)
|
||||
stdlib.free(lit_off)
|
||||
stdlib.free(lit_sorted)
|
||||
stdlib.free(dst_len)
|
||||
stdlib.free(dst_cnt)
|
||||
stdlib.free(dst_first)
|
||||
stdlib.free(dst_off)
|
||||
stdlib.free(dst_sorted)
|
||||
stdlib.free(cl_len)
|
||||
stdlib.free(cl_order)
|
||||
stdlib.free(cl_cnt)
|
||||
stdlib.free(cl_first)
|
||||
stdlib.free(cl_off)
|
||||
stdlib.free(cl_sorted)
|
||||
stdlib.free(pos_tmp)
|
||||
|
||||
if err != 0:
|
||||
return LONG(err) # err 本身为负错误码, 不能再取负(否则被上层当成"长度>0 成功")
|
||||
return LONG(wpos)
|
||||
+14
-1
@@ -104,11 +104,24 @@ def chdir(path: str) -> int:
|
||||
# =============================================================================
|
||||
|
||||
def mkdir(path: str) -> int:
|
||||
"""创建目录。成功返回 0,失败返回 -1。"""
|
||||
"""创建目录。成功返回 0,失败返回 -1。
|
||||
路径按 UTF-8 解释时转 UTF-16 走 CreateDirectoryW, 否则退回 A 系列"""
|
||||
if w32.win32base.utf8_is_valid(path) == 0:
|
||||
result: BOOL = w32.win32file.CreateDirectoryA(path, t.CPtr(0))
|
||||
if result == 0:
|
||||
return -1
|
||||
return 0
|
||||
wp: w32.win32base.WCHARPTR = w32.win32base.utf8_to_utf16_dup(path)
|
||||
if wp is None:
|
||||
result_a: BOOL = w32.win32file.CreateDirectoryA(path, t.CPtr(0))
|
||||
if result_a == 0:
|
||||
return -1
|
||||
return 0
|
||||
result_w: BOOL = w32.win32file.CreateDirectoryW(t.CPtr(wp), t.CPtr(0))
|
||||
stdlib.free(wp)
|
||||
if result_w == 0:
|
||||
return -1
|
||||
return 0
|
||||
|
||||
|
||||
def remove(path: str) -> int:
|
||||
|
||||
+25
-5
@@ -431,8 +431,9 @@ def _HttpsRequest(method: str, url: str, verify: bool, ca_file: str, data: str,
|
||||
|
||||
requests._mbuddy.free(req_buf)
|
||||
|
||||
# ---- 接收响应 ----
|
||||
resp_buf: str = requests._mbuddy.alloc(requests.HTTP_BUFSIZ)
|
||||
# ---- 接收响应(缓冲自动翻倍增长, 支持 MB 级正文, 如 MNIST .gz 下载) ----
|
||||
resp_cap: INT = requests.HTTP_BUFSIZ
|
||||
resp_buf: str = requests._mbuddy.alloc(t.CSizeT(resp_cap))
|
||||
if resp_buf is None:
|
||||
_RecordErrorText("alloc response buffer failed")
|
||||
openssl.SSL_free(ssl)
|
||||
@@ -443,9 +444,28 @@ def _HttpsRequest(method: str, url: str, verify: bool, ca_file: str, data: str,
|
||||
return None
|
||||
|
||||
total: INT = 0
|
||||
while total < requests.HTTP_BUFSIZ - 1:
|
||||
n: INT = openssl.SSL_read(ssl, resp_buf + total, requests.HTTP_BUFSIZ - 1 - total)
|
||||
if n <= 0: break
|
||||
while True:
|
||||
room: INT = resp_cap - 1 - total
|
||||
if room <= 0:
|
||||
# 缓冲已满仍可能有数据: 翻倍扩容(硬上限 256MB, 防失控流)
|
||||
if resp_cap >= 268435456:
|
||||
break
|
||||
resp_cap = resp_cap * 2
|
||||
nb: t.CVoid | t.CPtr = requests._mbuddy.realloc(resp_buf, t.CSizeT(resp_cap))
|
||||
if nb is None:
|
||||
_RecordErrorText("realloc response buffer failed")
|
||||
requests._mbuddy.free(resp_buf)
|
||||
openssl.SSL_free(ssl)
|
||||
openssl.SSL_CTX_free(ctx)
|
||||
sock.close()
|
||||
requests._mbuddy.free(errmsg)
|
||||
requests.release(resp)
|
||||
return None
|
||||
resp_buf = t.CChar(t.CUInt64T(nb), t.CPtr)
|
||||
room = resp_cap - 1 - total
|
||||
n: INT = openssl.SSL_read(ssl, resp_buf + total, room)
|
||||
if n <= 0:
|
||||
break
|
||||
total += n
|
||||
|
||||
resp_buf[total] = 0
|
||||
|
||||
@@ -6,4 +6,4 @@ extern def va_end(args: t.CPtr)
|
||||
|
||||
va_list: t.CTypedef = t.CUnsignedChar | t.CPtr
|
||||
|
||||
extern def arg(type: t.CType)
|
||||
extern def arg(type: t.CPtr)
|
||||
@@ -0,0 +1,925 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ============================================================
|
||||
# torch-lite: 仿 PyTorch 的极简张量/神经网络库(Viper)
|
||||
#
|
||||
# 设计原则:
|
||||
# * "足够用就行": 只覆盖 MNIST 两层 MLP 训练/推理所需的原语。
|
||||
# * API 命名贴近 PyTorch: Tensor / linear_fwd(bias 融合) / relu /
|
||||
# relu_backward / softmax_crossentropy(前向+梯度一体) / sgd_step。
|
||||
# * 张量为 2D 稠密 double (rows x cols, 行主序), 权重形状同 PyTorch:
|
||||
# Linear(in, out) -> weight (out, in) / bias (out,), forward = x @ W^T + b。
|
||||
# * 计算热点结合 vipersimd:
|
||||
# - linear_fwd 内积按 k%4 用 simd_dot4d 累加(纯 AVX2 点积);
|
||||
# - relu_fwd 用 simd_max4d(x, 0) 四路取大;
|
||||
# - 向量乘标量用 simd_mul_scalar4d(归一化 /255 等)。
|
||||
# backward / softmax / sgd 等按标量写(数据量小, 可读性优先)。
|
||||
# * 内存: 所有 Tensor 及 data/grad 都从调用方传入的 memhub.MemBuddy
|
||||
# 分配(与 includes 既有约定一致, pool.alloc 免手动 free)。
|
||||
# ============================================================
|
||||
from stdint import *
|
||||
import t, c
|
||||
import stdlib
|
||||
import string
|
||||
import stdio
|
||||
import vipermath
|
||||
import vipersimd
|
||||
import memhub
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Tensor
|
||||
# ------------------------------------------------------------
|
||||
@t.NoVTable
|
||||
class Tensor:
|
||||
Data: t.CPtr # rows*cols*8 字节连续 double
|
||||
Grad: t.CPtr # 同尺寸梯度(可为 None)
|
||||
Rows: sizet
|
||||
Cols: sizet
|
||||
|
||||
# 字段: Data/Grad/Rows/Cols = 32 字节(无 vtable)
|
||||
TENSOR_SIZE: t.CDefine = 32
|
||||
|
||||
|
||||
def _zeros_block(pool: rowptr[memhub.MemBuddy], bytes_n: sizet) -> t.CPtr:
|
||||
"""分配并清零一块内存"""
|
||||
p: t.CPtr = pool.alloc(bytes_n)
|
||||
if p is not None:
|
||||
string.memset(p, 0, bytes_n)
|
||||
return p
|
||||
|
||||
|
||||
def zeros(pool: rowptr[memhub.MemBuddy], rows: sizet,
|
||||
cols: sizet, with_grad: int = 1) -> Tensor | t.CPtr:
|
||||
"""torch.zeros: 创建 rows x cols 的全零 Tensor, 可选分配 Grad 缓冲"""
|
||||
if pool is None:
|
||||
return None
|
||||
t0: rowptr[Tensor] = pool.alloc(TENSOR_SIZE)
|
||||
if t0 is None:
|
||||
return None
|
||||
string.memset(t0, 0, TENSOR_SIZE)
|
||||
nb: sizet = rows * cols * sizet(8)
|
||||
t0.Data = _zeros_block(pool, nb)
|
||||
if t0.Data is None:
|
||||
return None
|
||||
if with_grad != 0:
|
||||
t0.Grad = _zeros_block(pool, nb)
|
||||
t0.Rows = rows
|
||||
t0.Cols = cols
|
||||
return t0
|
||||
|
||||
|
||||
def from_raw(pool: rowptr[memhub.MemBuddy], data: t.CPtr,
|
||||
rows: sizet, cols: sizet,
|
||||
with_grad: int = 0) -> Tensor | t.CPtr:
|
||||
"""torch.from_raw: 包装一块已有内存为 Tensor(不复制, 不拥有)"""
|
||||
if pool is None:
|
||||
return None
|
||||
t0: rowptr[Tensor] = pool.alloc(TENSOR_SIZE)
|
||||
if t0 is None:
|
||||
return None
|
||||
string.memset(t0, 0, TENSOR_SIZE)
|
||||
t0.Data = data
|
||||
t0.Grad = None
|
||||
t0.Rows = rows
|
||||
t0.Cols = cols
|
||||
return t0
|
||||
|
||||
|
||||
def tensor_free(t0: Tensor | t.CPtr):
|
||||
"""释放 Tensor 外壳(数据由 MemBuddy 池统一回收, 无需手动 free)"""
|
||||
pass
|
||||
|
||||
|
||||
def tensor_data(t0: Tensor | t.CPtr) -> t.CPtr:
|
||||
if t0 is None:
|
||||
return None
|
||||
return t0.Data
|
||||
|
||||
|
||||
def tensor_grad(t0: Tensor | t.CPtr) -> t.CPtr:
|
||||
if t0 is None:
|
||||
return None
|
||||
return t0.Grad
|
||||
|
||||
|
||||
def tensor_rows(t0: Tensor | t.CPtr) -> sizet:
|
||||
if t0 is None:
|
||||
return 0
|
||||
return t0.Rows
|
||||
|
||||
|
||||
def tensor_cols(t0: Tensor | t.CPtr) -> sizet:
|
||||
if t0 is None:
|
||||
return 0
|
||||
return t0.Cols
|
||||
|
||||
|
||||
def fill(t0: Tensor | t.CPtr, val: double):
|
||||
"""torch.fill: 全部元素置为 val"""
|
||||
if t0 is None or t0.Data is None:
|
||||
return
|
||||
d: double | t.CPtr = t0.Data
|
||||
n: sizet = t0.Rows * t0.Cols
|
||||
i: sizet = 0
|
||||
while i < n:
|
||||
d[i] = val
|
||||
i += 1
|
||||
|
||||
|
||||
def zero_grad(t0: Tensor | t.CPtr):
|
||||
"""torch.zero_grad: Grad 缓冲清零"""
|
||||
if t0 is None or t0.Grad is None:
|
||||
return
|
||||
string.memset(t0.Grad, 0, t0.Rows * t0.Cols * sizet(8))
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 确定性伪随机(LCG, minstd)与 Kaiming 均匀初始化
|
||||
# ------------------------------------------------------------
|
||||
_rng_state: double = 12345.0
|
||||
LCG_M: double = 2147483647.0
|
||||
LCG_A: double = 48271.0
|
||||
|
||||
|
||||
def manual_seed(seed: int64):
|
||||
global _rng_state
|
||||
_rng_state = double(seed % int64(2147483647))
|
||||
if _rng_state <= double(0.0):
|
||||
_rng_state = double(12345.0)
|
||||
|
||||
|
||||
def _next_rand() -> double:
|
||||
"""(0,1) 均匀随机"""
|
||||
global _rng_state
|
||||
_rng_state = vipermath.fmod(_rng_state * LCG_A, LCG_M)
|
||||
return _rng_state / LCG_M
|
||||
|
||||
|
||||
def uniform_fill(t0: Tensor | t.CPtr, lo: double, hi: double):
|
||||
"""U(lo, hi) 填充"""
|
||||
if t0 is None or t0.Data is None:
|
||||
return
|
||||
d: double | t.CPtr = t0.Data
|
||||
n: sizet = t0.Rows * t0.Cols
|
||||
span: double = hi - lo
|
||||
i: sizet = 0
|
||||
while i < n:
|
||||
d[i] = lo + _next_rand() * span
|
||||
i += 1
|
||||
|
||||
|
||||
def kaiming_uniform_fill(w: Tensor | t.CPtr, fan_in: sizet,
|
||||
fan_out: sizet):
|
||||
"""PyTorch Linear 默认初始化: U(-sqrt(6/(fan_in+fan_out)), +...)"""
|
||||
bound: double = vipermath.sqrt(
|
||||
double(6.0) / double(fan_in + fan_out))
|
||||
uniform_fill(w, -bound, bound)
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 基础元素级运算(结合 vipersimd 4 宽指令)
|
||||
# ------------------------------------------------------------
|
||||
def vector_mul_scalar4(src: t.CPtr, scalar_ptr: t.CPtr, dst: t.CPtr,
|
||||
n: sizet):
|
||||
"""dst = src * scalar (逐 4 元素 simd_mul_scalar4d + 标量尾数)"""
|
||||
if src is None or scalar_ptr is None or dst is None:
|
||||
return
|
||||
i: sizet = 0
|
||||
while i + 4 <= n:
|
||||
vipersimd.simd_mul_scalar4d(
|
||||
t.CPtr(uint64(src) + uint64(i) * 8),
|
||||
scalar_ptr,
|
||||
t.CPtr(uint64(dst) + uint64(i) * 8))
|
||||
i += 4
|
||||
sd: double | t.CPtr = src
|
||||
dd: double | t.CPtr = dst
|
||||
# 尾部标量
|
||||
sp: double | t.CPtr = scalar_ptr
|
||||
sval: double = sp[0]
|
||||
while i < n:
|
||||
dd[i] = sd[i] * sval
|
||||
i += 1
|
||||
|
||||
|
||||
def vector_add_inplace(a: t.CPtr, b: t.CPtr, n: sizet):
|
||||
"""a += b (逐 4 元素 simd_add_array 尾部标量)"""
|
||||
if a is None or b is None:
|
||||
return
|
||||
tmp: t.CPtr = stdlib.malloc(8)
|
||||
if tmp is None:
|
||||
return
|
||||
i: sizet = 0
|
||||
while i + 4 <= n:
|
||||
vipersimd.simd_add_array(
|
||||
t.CPtr(uint64(a) + uint64(i) * 8),
|
||||
t.CPtr(uint64(b) + uint64(i) * 8),
|
||||
t.CPtr(uint64(a) + uint64(i) * 8))
|
||||
i += 4
|
||||
da: double | t.CPtr = a
|
||||
db: double | t.CPtr = b
|
||||
while i < n:
|
||||
da[i] = da[i] + db[i]
|
||||
i += 1
|
||||
stdlib.free(tmp)
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 线性层: forward = x @ W^T + b (W: out x in, x: m x in, out: m x out)
|
||||
# 内积按 k%4 用 vipersimd.simd_dot4d —— MNIST 主计算热点
|
||||
# ------------------------------------------------------------
|
||||
def linear_fwd(w: Tensor | t.CPtr, b: Tensor | t.CPtr,
|
||||
x: Tensor | t.CPtr, out: Tensor | t.CPtr):
|
||||
if w is None or b is None or x is None or out is None:
|
||||
return
|
||||
m: sizet = x.Rows
|
||||
k: sizet = x.Cols
|
||||
n: sizet = w.Rows
|
||||
wd: double | t.CPtr = w.Data
|
||||
bd: double | t.CPtr = b.Data
|
||||
xd: double | t.CPtr = x.Data
|
||||
od: double | t.CPtr = out.Data
|
||||
acc_buf: t.CPtr = stdlib.malloc(8)
|
||||
if acc_buf is None:
|
||||
return
|
||||
accp: double | t.CPtr = acc_buf
|
||||
i: sizet = 0
|
||||
while i < m:
|
||||
j: sizet = 0
|
||||
while j < n:
|
||||
acc: double = bd[j]
|
||||
kk: sizet = 0
|
||||
while kk + 4 <= k:
|
||||
vipersimd.simd_dot4d(
|
||||
t.CPtr(uint64(xd) + uint64(i * k + kk) * 8),
|
||||
t.CPtr(uint64(wd) + uint64(j * k + kk) * 8),
|
||||
acc_buf)
|
||||
acc = acc + accp[0]
|
||||
kk += 4
|
||||
while kk < k:
|
||||
acc = acc + xd[i * k + kk] * wd[j * k + kk]
|
||||
kk += 1
|
||||
od[i * n + j] = acc
|
||||
j += 1
|
||||
i += 1
|
||||
stdlib.free(acc_buf)
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 线性层反向: 输入 w(out,in) x(m,in) g(m,out)
|
||||
# dw[j,i] += sum_b g[b,j]*x[b,i] ; db[j] += sum_b g[b,j]
|
||||
# dx[b,i] = sum_j g[b,j]*w[j,i]
|
||||
# 注意 dw/db 按"累加"语义(调用前清零 Grad)
|
||||
# ------------------------------------------------------------
|
||||
def linear_bwd(w: Tensor | t.CPtr, x: Tensor | t.CPtr,
|
||||
g: Tensor | t.CPtr, dw: Tensor | t.CPtr,
|
||||
db: Tensor | t.CPtr, dx: Tensor | t.CPtr,
|
||||
accum: int = 0):
|
||||
if w is None or x is None or g is None:
|
||||
return
|
||||
if dw is None or db is None or dx is None:
|
||||
return
|
||||
m: sizet = x.Rows
|
||||
k: sizet = x.Cols
|
||||
n: sizet = w.Rows
|
||||
wd: double | t.CPtr = w.Data
|
||||
xd: double | t.CPtr = x.Data
|
||||
gd: double | t.CPtr = g.Data
|
||||
dwd: double | t.CPtr = dw.Data
|
||||
dbd: double | t.CPtr = db.Data
|
||||
dxd: double | t.CPtr = dx.Data
|
||||
|
||||
# dw/db/dx 清零(dx 恒清零: 它是本样本的输入梯度, 不跨样本累积)
|
||||
# accum=1 时保留 dw/db 现值 —— 逐样本反向累积整批参数梯度的调用方用
|
||||
if accum == 0:
|
||||
string.memset(dw.Data, 0, n * k * sizet(8))
|
||||
string.memset(db.Data, 0, n * sizet(8))
|
||||
string.memset(dx.Data, 0, m * k * sizet(8))
|
||||
|
||||
b: sizet = 0
|
||||
while b < m:
|
||||
j: sizet = 0
|
||||
while j < n:
|
||||
gj: double = gd[b * n + j]
|
||||
if gj != double(0.0):
|
||||
dbd[j] = dbd[j] + gj
|
||||
i: sizet = 0
|
||||
while i < k:
|
||||
dwd[j * k + i] = dwd[j * k + i] + gj * xd[b * k + i]
|
||||
dxd[b * k + i] = dxd[b * k + i] + gj * wd[j * k + i]
|
||||
i += 1
|
||||
j += 1
|
||||
b += 1
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# ReLU (simd_max4d 版本) 与反向
|
||||
# ------------------------------------------------------------
|
||||
def relu_fwd(x: Tensor | t.CPtr, out: Tensor | t.CPtr):
|
||||
if x is None or out is None:
|
||||
return
|
||||
n: sizet = x.Rows * x.Cols
|
||||
zero4: t.CPtr = stdlib.malloc(32)
|
||||
if zero4 is None:
|
||||
return
|
||||
string.memset(zero4, 0, 32)
|
||||
xd: double | t.CPtr = x.Data
|
||||
od: double | t.CPtr = out.Data
|
||||
i: sizet = 0
|
||||
while i + 4 <= n:
|
||||
vipersimd.simd_max4d(
|
||||
t.CPtr(uint64(xd) + uint64(i) * 8),
|
||||
zero4,
|
||||
t.CPtr(uint64(od) + uint64(i) * 8))
|
||||
i += 4
|
||||
while i < n:
|
||||
if xd[i] > double(0.0):
|
||||
od[i] = xd[i]
|
||||
else:
|
||||
od[i] = double(0.0)
|
||||
i += 1
|
||||
stdlib.free(zero4)
|
||||
|
||||
|
||||
def relu_backward(x: Tensor | t.CPtr, g: Tensor | t.CPtr,
|
||||
gx: Tensor | t.CPtr):
|
||||
"""gx = g * (x > 0)"""
|
||||
if x is None or g is None or gx is None:
|
||||
return
|
||||
n: sizet = x.Rows * x.Cols
|
||||
xd: double | t.CPtr = x.Data
|
||||
gd: double | t.CPtr = g.Data
|
||||
od: double | t.CPtr = gx.Data
|
||||
i: sizet = 0
|
||||
while i < n:
|
||||
if xd[i] > double(0.0):
|
||||
od[i] = gd[i]
|
||||
else:
|
||||
od[i] = double(0.0)
|
||||
i += 1
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# softmax + CrossEntropy(融合, 数值稳定) —— MNIST 输出层
|
||||
# logits: (m, C), labels: m 个 int (0..C-1)
|
||||
# loss 写入 *loss, 梯度(已按 1/m 归一)写入 grad (m, C)
|
||||
# ------------------------------------------------------------
|
||||
def softmax_crossentropy(logits: Tensor | t.CPtr,
|
||||
labels: int | t.CPtr,
|
||||
n_labels: sizet,
|
||||
loss: double | t.CPtr,
|
||||
grad: Tensor | t.CPtr):
|
||||
if logits is None or labels is None or grad is None:
|
||||
return
|
||||
if loss is None:
|
||||
return
|
||||
m: sizet = logits.Rows
|
||||
c: sizet = logits.Cols
|
||||
ld: double | t.CPtr = logits.Data
|
||||
gd: double | t.CPtr = grad.Data
|
||||
lb: int | t.CPtr = labels
|
||||
total: double = double(0.0)
|
||||
inv_m: double = double(1.0) / double(m)
|
||||
b: sizet = 0
|
||||
while b < m:
|
||||
# 数值稳定 softmax
|
||||
mx: double = ld[b * c]
|
||||
j: sizet = 1
|
||||
while j < c:
|
||||
if ld[b * c + j] > mx:
|
||||
mx = ld[b * c + j]
|
||||
j += 1
|
||||
s: double = double(0.0)
|
||||
j = 0
|
||||
while j < c:
|
||||
s = s + vipermath.exp(ld[b * c + j] - mx)
|
||||
j += 1
|
||||
y: int = lb[b]
|
||||
# loss += -(logits[lab] - max - log(s))
|
||||
total = total - (ld[b * c + sizet(y)] - mx - vipermath.log(s))
|
||||
# 梯度: (softmax - onehot) / m
|
||||
j = 0
|
||||
while j < c:
|
||||
p: double = vipermath.exp(ld[b * c + j] - mx) / s
|
||||
o: double = double(0.0)
|
||||
if sizet(y) == j:
|
||||
o = double(1.0)
|
||||
gd[b * c + j] = (p - o) * inv_m
|
||||
j += 1
|
||||
b += 1
|
||||
loss[0] = total
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 每行 argmax (分类预测)
|
||||
# ------------------------------------------------------------
|
||||
def row_argmax(x: Tensor | t.CPtr, pred: int | t.CPtr):
|
||||
if x is None or pred is None:
|
||||
return
|
||||
m: sizet = x.Rows
|
||||
c: sizet = x.Cols
|
||||
d: double | t.CPtr = x.Data
|
||||
i: sizet = 0
|
||||
while i < m:
|
||||
best: sizet = 0
|
||||
bv: double = d[i * c]
|
||||
j: sizet = 1
|
||||
while j < c:
|
||||
if d[i * c + j] > bv:
|
||||
bv = d[i * c + j]
|
||||
best = j
|
||||
j += 1
|
||||
pred[i] = int(best)
|
||||
i += 1
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# SGD: data -= lr * grad(全量梯度) / 重球法: v = m*v+g, data -= lr*v
|
||||
# ------------------------------------------------------------
|
||||
def sgd_step(param: Tensor | t.CPtr, lr: double):
|
||||
if param is None or param.Data is None or param.Grad is None:
|
||||
return
|
||||
d: double | t.CPtr = param.Data
|
||||
g: double | t.CPtr = param.Grad
|
||||
n: sizet = param.Rows * param.Cols
|
||||
i: sizet = 0
|
||||
while i < n:
|
||||
d[i] = d[i] - lr * g[i]
|
||||
i += 1
|
||||
|
||||
|
||||
def sgd_step_momentum(param: Tensor | t.CPtr, vel: t.CPtr,
|
||||
lr: double, momentum: double):
|
||||
"""带动量(重球法)的 SGD: v = m*v + g ; w -= lr*v
|
||||
|
||||
vel 是与 param 同元素个数的 double 缓冲, 由调用方分配并清零(跨 step 持有);
|
||||
vel 传 None 时退化为普通 sgd_step。动量是 CNN/MLP 训练收敛速度的关键 ——
|
||||
纯 SGD 在固定 lr 下前几个 epoch 很慢且容易卡在低准确率。"""
|
||||
if param is None or param.Data is None or param.Grad is None:
|
||||
return
|
||||
if vel is None:
|
||||
sgd_step(param, lr)
|
||||
return
|
||||
d: double | t.CPtr = param.Data
|
||||
g: double | t.CPtr = param.Grad
|
||||
v: double | t.CPtr = vel
|
||||
n: sizet = param.Rows * param.Cols
|
||||
i: sizet = 0
|
||||
while i < n:
|
||||
vv: double = momentum * v[i] + g[i]
|
||||
v[i] = vv
|
||||
d[i] = d[i] - lr * vv
|
||||
i += 1
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# 逐元素乘加 (调试/扩展用, vipersimd fma_array)
|
||||
# a = a*b + c
|
||||
# ------------------------------------------------------------
|
||||
def fma_array_inplace(a: Tensor | t.CPtr, b: t.CPtr, c: t.CPtr):
|
||||
if a is None or a.Data is None:
|
||||
return
|
||||
vipersimd.simd_fma_array(a.Data, b, c, a.Data, a.Rows * a.Cols)
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Conv2d / MaxPool2d —— 轻量 CNN 用的原始缓冲算子
|
||||
#
|
||||
# 这些算子直接吃裸缓冲(不用 Tensor: torch.Tensor 只有 2D 形状),
|
||||
# 布局一律 NCHW 连续 double; 卷积权重 [cout][cin][k][k]。
|
||||
# 约定:
|
||||
# *_fwd 覆盖写输出
|
||||
# conv2d_bwd_dx 先清零再累加(逐样本输入梯度, 不跨样本累积)
|
||||
# maxpool2d_bwd 同上
|
||||
# conv2d_bwd_dw += 累加参数梯度(跨样本累积, 批次开始由调用方清零)
|
||||
#
|
||||
# SIMD: 三个卷积算子的内层都按"输出列"做 4 路 AVX2 double。
|
||||
# 依据是 stride==1 时相邻输出列恰好对应相邻输入列, 故一列 4 个 double
|
||||
# 在内存里连续, 可直接 vmovupd + vfmadd213pd, 权重标量 vbroadcastsd。
|
||||
# 代价是需要一个"安全列区间": 4 条 lane 的输出列都在 [0,wout) 内, 且它们
|
||||
# 触及的输入列 [ox-pad, ox+3+k-1-pad] 全在 [0,win) 内 —— 即
|
||||
# ox ∈ [pad, win+pad-k-3]。区间外的列与 stride!=1 一律走原标量分支。
|
||||
# (maxpool2d 不做 SIMD: k==stride==2 时 4 个窗口起点间隔 2 个 double,
|
||||
# 不是连续地址, 无 gather 原语; 且它占总计算量不到 1%。)
|
||||
# ------------------------------------------------------------
|
||||
def _dptr(base: t.CPtr, elem: sizet) -> t.CPtr:
|
||||
"""裸 double 缓冲的元素下标 → 字节地址
|
||||
|
||||
裸 t.CPtr 是 i8*, `p + n` 走的是字节偏移; 而 vipersimd 的 4 路原语按
|
||||
"4 个 double = 32 字节"读写, 所以下标必须显式 ×8。
|
||||
(与 linear_fwd 里的 t.CPtr(uint64(p) + i * 8) 同一写法。)"""
|
||||
return t.CPtr(uint64(base) + uint64(elem) * 8)
|
||||
|
||||
|
||||
def conv2d_fwd(x: t.CPtr, w: t.CPtr, b: t.CPtr, out: t.CPtr,
|
||||
n: sizet, cin: sizet, hin: sizet, win: sizet,
|
||||
cout: sizet, k: sizet, stride: sizet,
|
||||
pad: sizet):
|
||||
"""out[n][cout][hout][wout] = conv2d(x, w) + b
|
||||
hout = (hin + 2*pad - k)/stride + 1(wout 同理, 整除向下取整)"""
|
||||
if x is None or w is None or out is None:
|
||||
return
|
||||
hout: sizet = (hin + pad * 2 - k) / stride + 1
|
||||
wout: sizet = (win + pad * 2 - k) / stride + 1
|
||||
xd: double | t.CPtr = x
|
||||
wd: double | t.CPtr = w
|
||||
od: double | t.CPtr = out
|
||||
kk: sizet = cin * k * k
|
||||
# SIMD 安全区间(见文件头说明)
|
||||
simd_on: int = 0
|
||||
smax: sizet = 0
|
||||
if stride == 1 and win + pad >= k + 3:
|
||||
simd_on = 1
|
||||
smax = win + pad - (k + 3)
|
||||
wb: t.CPtr = None # 权重标量 × 4 路广播副本(kk 个标量)
|
||||
t4: t.CPtr = None # 4 路累加器
|
||||
if simd_on != 0:
|
||||
wb = stdlib.malloc(kk * 32)
|
||||
t4 = stdlib.malloc(32)
|
||||
if wb is None or t4 is None:
|
||||
if wb is not None:
|
||||
stdlib.free(wb)
|
||||
if t4 is not None:
|
||||
stdlib.free(t4)
|
||||
wb = None
|
||||
t4 = None
|
||||
simd_on = 0
|
||||
wbd: double | t.CPtr = wb
|
||||
t4d: double | t.CPtr = t4
|
||||
|
||||
bi: sizet = 0
|
||||
while bi < n:
|
||||
co: sizet = 0
|
||||
while co < cout:
|
||||
bias: double = double(0.0)
|
||||
if b is not None:
|
||||
bd: double | t.CPtr = b
|
||||
bias = bd[co]
|
||||
# 权重标量预广播: 每个 (ci,ky,kx) 一个 4 路副本, 本通道所有输出
|
||||
# 行/列共用。否则 vbroadcastsd(4 次 store) 会跟着 4 路循环重复,
|
||||
# 把 SIMD 的收益吃掉一半。
|
||||
if simd_on != 0:
|
||||
wb4: sizet = 0
|
||||
while wb4 < kk:
|
||||
wq: double = wd[co * kk + wb4]
|
||||
wbd[wb4 * 4] = wq
|
||||
wbd[wb4 * 4 + 1] = wq
|
||||
wbd[wb4 * 4 + 2] = wq
|
||||
wbd[wb4 * 4 + 3] = wq
|
||||
wb4 += 1
|
||||
oy: sizet = 0
|
||||
while oy < hout:
|
||||
oy0: sizet = oy * stride
|
||||
ox: sizet = 0
|
||||
while ox < wout:
|
||||
if simd_on != 0 and ox >= pad and ox <= smax and ox + 4 <= wout:
|
||||
# ---- 4 路: 一次算 4 个相邻输出列 ----
|
||||
t4d[0] = bias
|
||||
t4d[1] = bias
|
||||
t4d[2] = bias
|
||||
t4d[3] = bias
|
||||
ci: sizet = 0
|
||||
while ci < cin:
|
||||
ky: sizet = 0
|
||||
while ky < k:
|
||||
iy: sizet = oy0 + ky
|
||||
if iy >= pad:
|
||||
yy: sizet = iy - pad
|
||||
if yy < hin:
|
||||
xrow: sizet = ((bi * cin + ci) * hin + yy) * win
|
||||
kx: sizet = 0
|
||||
while kx < k:
|
||||
vipersimd.simd_fma4d(
|
||||
_dptr(x, xrow + ox + kx - pad),
|
||||
_dptr(wb, ((ci * k + ky) * k + kx) * 4),
|
||||
t4, t4)
|
||||
kx += 1
|
||||
ky += 1
|
||||
ci += 1
|
||||
ob: sizet = ((bi * cout + co) * hout + oy) * wout + ox
|
||||
od[ob] = t4d[0]
|
||||
od[ob + 1] = t4d[1]
|
||||
od[ob + 2] = t4d[2]
|
||||
od[ob + 3] = t4d[3]
|
||||
ox += 4
|
||||
else:
|
||||
# ---- 标量: 边界列 / stride != 1 ----
|
||||
ox0: sizet = ox * stride
|
||||
acc: double = bias
|
||||
ci2: sizet = 0
|
||||
while ci2 < cin:
|
||||
ky2: sizet = 0
|
||||
while ky2 < k:
|
||||
iy2: sizet = oy0 + ky2
|
||||
if iy2 >= pad:
|
||||
yy2: sizet = iy2 - pad
|
||||
if yy2 < hin:
|
||||
kx2: sizet = 0
|
||||
while kx2 < k:
|
||||
ix2: sizet = ox0 + kx2
|
||||
if ix2 >= pad:
|
||||
xx2: sizet = ix2 - pad
|
||||
if xx2 < win:
|
||||
acc = acc + \
|
||||
wd[co * kk + (ci2 * k + ky2) * k + kx2] * \
|
||||
xd[((bi * cin + ci2) * hin + yy2) * win + xx2]
|
||||
kx2 += 1
|
||||
ky2 += 1
|
||||
ci2 += 1
|
||||
od[((bi * cout + co) * hout + oy) * wout + ox] = acc
|
||||
ox += 1
|
||||
oy += 1
|
||||
co += 1
|
||||
bi += 1
|
||||
if wb is not None:
|
||||
stdlib.free(wb)
|
||||
if t4 is not None:
|
||||
stdlib.free(t4)
|
||||
|
||||
|
||||
def conv2d_bwd_dw(x: t.CPtr, dout: t.CPtr, dw: t.CPtr, db: t.CPtr,
|
||||
n: sizet, cin: sizet, hin: sizet, win: sizet,
|
||||
cout: sizet, k: sizet, stride: sizet,
|
||||
pad: sizet):
|
||||
"""累加: dw[co][ci][ky][kx] += sum dout*x ; db[co] += sum dout
|
||||
(+= 语义, 调用方在批次开始前清零)
|
||||
|
||||
循环顺序为 (ci,ky,kx) 在外、(oy,ox) 在内: 每个权重元素只做一次
|
||||
读-改-写; 内层按输出列 4 路 SIMD 累加, 最后 hsum 成标量。"""
|
||||
if x is None or dout is None or dw is None:
|
||||
return
|
||||
hout: sizet = (hin + pad * 2 - k) / stride + 1
|
||||
wout: sizet = (win + pad * 2 - k) / stride + 1
|
||||
xd: double | t.CPtr = x
|
||||
gd: double | t.CPtr = dout
|
||||
dwd: double | t.CPtr = dw
|
||||
dbd: double | t.CPtr = None
|
||||
if db is not None:
|
||||
dbd = db
|
||||
kk: sizet = cin * k * k
|
||||
simd_on: int = 0
|
||||
smax: sizet = 0
|
||||
if stride == 1 and win + pad >= k + 3:
|
||||
simd_on = 1
|
||||
smax = win + pad - (k + 3)
|
||||
acc: t.CPtr = None
|
||||
if simd_on != 0:
|
||||
acc = stdlib.malloc(32)
|
||||
if acc is None:
|
||||
simd_on = 0
|
||||
accd: double | t.CPtr = acc
|
||||
|
||||
bi: sizet = 0
|
||||
while bi < n:
|
||||
co: sizet = 0
|
||||
while co < cout:
|
||||
gp: sizet = (bi * cout + co) * hout * wout
|
||||
# ---- db[co] += sum(g): g 的整个通道平面是连续的 ----
|
||||
if dbd is not None:
|
||||
tot0: double = double(0.0)
|
||||
if simd_on != 0:
|
||||
accd[0] = double(0.0)
|
||||
accd[1] = double(0.0)
|
||||
accd[2] = double(0.0)
|
||||
accd[3] = double(0.0)
|
||||
gcnt: sizet = hout * wout
|
||||
gof: sizet = 0
|
||||
while gof + 4 <= gcnt:
|
||||
if simd_on != 0:
|
||||
vipersimd.simd_add4d(_dptr(dout, gp + gof), acc, acc)
|
||||
else:
|
||||
tot0 = tot0 + gd[gp + gof]
|
||||
tot0 = tot0 + gd[gp + gof + 1]
|
||||
tot0 = tot0 + gd[gp + gof + 2]
|
||||
tot0 = tot0 + gd[gp + gof + 3]
|
||||
gof += 4
|
||||
while gof < gcnt:
|
||||
tot0 = tot0 + gd[gp + gof]
|
||||
gof += 1
|
||||
if simd_on != 0:
|
||||
vipersimd.simd_hsum4d(acc, acc)
|
||||
tot0 = tot0 + accd[0]
|
||||
dbd[co] = dbd[co] + tot0
|
||||
# ---- dw[co][ci][ky][kx] ----
|
||||
ci: sizet = 0
|
||||
while ci < cin:
|
||||
ky: sizet = 0
|
||||
while ky < k:
|
||||
kx: sizet = 0
|
||||
while kx < k:
|
||||
tot: double = double(0.0)
|
||||
if simd_on != 0:
|
||||
accd[0] = double(0.0)
|
||||
accd[1] = double(0.0)
|
||||
accd[2] = double(0.0)
|
||||
accd[3] = double(0.0)
|
||||
oy: sizet = 0
|
||||
while oy < hout:
|
||||
iy: sizet = oy * stride + ky
|
||||
if iy >= pad:
|
||||
yy: sizet = iy - pad
|
||||
if yy < hin:
|
||||
xrow: sizet = ((bi * cin + ci) * hin + yy) * win
|
||||
goff: sizet = gp + oy * wout
|
||||
ox: sizet = 0
|
||||
while ox < wout:
|
||||
if simd_on != 0 and ox >= pad and ox <= smax and ox + 4 <= wout:
|
||||
vipersimd.simd_fma4d(
|
||||
_dptr(dout, goff + ox),
|
||||
_dptr(x, xrow + ox + kx - pad),
|
||||
acc, acc)
|
||||
ox += 4
|
||||
else:
|
||||
ix: sizet = ox * stride + kx
|
||||
if ix >= pad:
|
||||
xx: sizet = ix - pad
|
||||
if xx < win:
|
||||
tot = tot + gd[goff + ox] * \
|
||||
xd[xrow + xx]
|
||||
ox += 1
|
||||
oy += 1
|
||||
if simd_on != 0:
|
||||
vipersimd.simd_hsum4d(acc, acc)
|
||||
tot = tot + accd[0]
|
||||
wj: sizet = co * kk + (ci * k + ky) * k + kx
|
||||
dwd[wj] = dwd[wj] + tot
|
||||
kx += 1
|
||||
ky += 1
|
||||
ci += 1
|
||||
co += 1
|
||||
bi += 1
|
||||
if acc is not None:
|
||||
stdlib.free(acc)
|
||||
|
||||
|
||||
def conv2d_bwd_dx(w: t.CPtr, dout: t.CPtr, dx: t.CPtr,
|
||||
n: sizet, cin: sizet, hin: sizet, win: sizet,
|
||||
cout: sizet, k: sizet, stride: sizet,
|
||||
pad: sizet):
|
||||
"""dx 先清零再累加(逐样本输入梯度): dx[ci][iy][ix] += sum dout*w
|
||||
|
||||
stride==1 时内层按输出列 4 路 SIMD: 4 条 lane 对应的 dx 单元也是连续的,
|
||||
故可直接对 dx 做 in-place fma(g4 * w_broadcast + dx4)。"""
|
||||
if w is None or dout is None or dx is None:
|
||||
return
|
||||
hout: sizet = (hin + pad * 2 - k) / stride + 1
|
||||
wout: sizet = (win + pad * 2 - k) / stride + 1
|
||||
wd: double | t.CPtr = w
|
||||
gd: double | t.CPtr = dout
|
||||
dxd: double | t.CPtr = dx
|
||||
string.memset(dx, 0, n * cin * hin * win * sizet(8))
|
||||
kk: sizet = cin * k * k
|
||||
simd_on: int = 0
|
||||
smax: sizet = 0
|
||||
if stride == 1 and win + pad >= k + 3:
|
||||
simd_on = 1
|
||||
smax = win + pad - (k + 3)
|
||||
wb: t.CPtr = None # 整个权重张量的 4 路广播副本(cout*kk 个标量)
|
||||
if simd_on != 0:
|
||||
wb = stdlib.malloc(cout * kk * 32)
|
||||
if wb is None:
|
||||
simd_on = 0
|
||||
wbd: double | t.CPtr = wb
|
||||
if simd_on != 0:
|
||||
# 权重在一次调用内不变 → 整体预广播一次(注意别在 4 路循环里重复
|
||||
# vbroadcastsd: 那是 4 次 store, 会把 SIMD 的收益吃掉一半)
|
||||
wjn: sizet = cout * kk
|
||||
wj4: sizet = 0
|
||||
while wj4 < wjn:
|
||||
wq2: double = wd[wj4]
|
||||
wbd[wj4 * 4] = wq2
|
||||
wbd[wj4 * 4 + 1] = wq2
|
||||
wbd[wj4 * 4 + 2] = wq2
|
||||
wbd[wj4 * 4 + 3] = wq2
|
||||
wj4 += 1
|
||||
|
||||
bi: sizet = 0
|
||||
while bi < n:
|
||||
ci: sizet = 0
|
||||
while ci < cin:
|
||||
ky: sizet = 0
|
||||
while ky < k:
|
||||
oy: sizet = 0
|
||||
while oy < hout:
|
||||
iy: sizet = oy * stride + ky
|
||||
if iy >= pad:
|
||||
yy: sizet = iy - pad
|
||||
if yy < hin:
|
||||
dxrow: sizet = ((bi * cin + ci) * hin + yy) * win
|
||||
ox: sizet = 0
|
||||
while ox < wout:
|
||||
if simd_on != 0 and ox >= pad and ox <= smax and ox + 4 <= wout:
|
||||
co: sizet = 0
|
||||
while co < cout:
|
||||
kx: sizet = 0
|
||||
while kx < k:
|
||||
goff: sizet = ((bi * cout + co) * hout + oy) * wout + ox
|
||||
doff: sizet = dxrow + ox + kx - pad
|
||||
vipersimd.simd_fma4d(
|
||||
_dptr(dout, goff),
|
||||
_dptr(wb, (co * kk + (ci * k + ky) * k + kx) * 4),
|
||||
_dptr(dx, doff), _dptr(dx, doff))
|
||||
kx += 1
|
||||
co += 1
|
||||
ox += 4
|
||||
else:
|
||||
co2: sizet = 0
|
||||
while co2 < cout:
|
||||
kx2: sizet = 0
|
||||
while kx2 < k:
|
||||
ix: sizet = ox * stride + kx2
|
||||
if ix >= pad:
|
||||
xx: sizet = ix - pad
|
||||
if xx < win:
|
||||
xi: sizet = dxrow + xx
|
||||
gi: sizet = ((bi * cout + co2) * hout + oy) * wout + ox
|
||||
dxd[xi] = dxd[xi] + \
|
||||
gd[gi] * wd[co2 * kk + (ci * k + ky) * k + kx2]
|
||||
kx2 += 1
|
||||
co2 += 1
|
||||
ox += 1
|
||||
oy += 1
|
||||
ky += 1
|
||||
ci += 1
|
||||
bi += 1
|
||||
if wb is not None:
|
||||
stdlib.free(wb)
|
||||
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# MaxPool2d (k x k, stride 通常 = k)
|
||||
# ------------------------------------------------------------
|
||||
def maxpool2d_fwd(x: t.CPtr, out: t.CPtr, argmax: t.CPtr,
|
||||
n: sizet, c: sizet, hin: sizet, win: sizet,
|
||||
k: sizet, stride: sizet):
|
||||
"""argmax: int 数组 [n*c*hout*wout], 记窗口内最大值在本通道内的下标(yy*win+xx)"""
|
||||
if x is None or out is None or argmax is None:
|
||||
return
|
||||
hout: sizet = (hin - k) / stride + 1
|
||||
wout: sizet = (win - k) / stride + 1
|
||||
xd: double | t.CPtr = x
|
||||
od: double | t.CPtr = out
|
||||
am: int | t.CPtr = argmax
|
||||
bi: sizet = 0
|
||||
while bi < n:
|
||||
ch: sizet = 0
|
||||
while ch < c:
|
||||
plano: sizet = (bi * c + ch) * hin * win
|
||||
oy: sizet = 0
|
||||
while oy < hout:
|
||||
oy0: sizet = oy * stride
|
||||
ox: sizet = 0
|
||||
while ox < wout:
|
||||
ox0: sizet = ox * stride
|
||||
best: double = double(0.0)
|
||||
bidx: int = 0
|
||||
first: int = 1
|
||||
ky: sizet = 0
|
||||
while ky < k:
|
||||
yy: sizet = oy0 + ky
|
||||
kx: sizet = 0
|
||||
while kx < k:
|
||||
xx: sizet = ox0 + kx
|
||||
v: double = xd[plano + yy * win + xx]
|
||||
if first != 0 or v > best:
|
||||
best = v
|
||||
bidx = int(yy * win + xx)
|
||||
first = 0
|
||||
kx += 1
|
||||
ky += 1
|
||||
oi: sizet = (bi * c + ch) * hout * wout + oy * wout + ox
|
||||
od[oi] = best
|
||||
am[oi] = bidx
|
||||
ox += 1
|
||||
oy += 1
|
||||
ch += 1
|
||||
bi += 1
|
||||
|
||||
|
||||
def maxpool2d_bwd(dout: t.CPtr, argmax: t.CPtr, dx: t.CPtr,
|
||||
n: sizet, c: sizet, hin: sizet, win: sizet,
|
||||
k: sizet, stride: sizet):
|
||||
"""dx 先清零再散射累加: dx[argmax] += dout"""
|
||||
if dout is None or argmax is None or dx is None:
|
||||
return
|
||||
hout: sizet = (hin - k) / stride + 1
|
||||
wout: sizet = (win - k) / stride + 1
|
||||
gd: double | t.CPtr = dout
|
||||
am: int | t.CPtr = argmax
|
||||
dxd: double | t.CPtr = dx
|
||||
string.memset(dx, 0, n * c * hin * win * sizet(8))
|
||||
bi: sizet = 0
|
||||
while bi < n:
|
||||
ch: sizet = 0
|
||||
while ch < c:
|
||||
plano: sizet = (bi * c + ch) * hin * win
|
||||
oy: sizet = 0
|
||||
while oy < hout:
|
||||
ox: sizet = 0
|
||||
while ox < wout:
|
||||
oi: sizet = (bi * c + ch) * hout * wout + oy * wout + ox
|
||||
tgt: sizet = plano + sizet(am[oi])
|
||||
dxd[tgt] = dxd[tgt] + gd[oi]
|
||||
ox += 1
|
||||
oy += 1
|
||||
ch += 1
|
||||
bi += 1
|
||||
+36
-36
@@ -168,48 +168,48 @@ def tanh(x: t.CDouble) -> t.CDouble:
|
||||
|
||||
|
||||
def exp(x: t.CDouble) -> t.CDouble:
|
||||
"""e^x
|
||||
|
||||
范围归约 + 短泰勒: 直接对 x 展开 18 项泰勒在 |x| 稍大时就会发散
|
||||
(x=-10 得 54.5, 真值 4.5e-5; x=-30 得 3.7e10), 负数端更是给出巨大正值,
|
||||
会让 softmax 的分母被撑爆 → 所有概率 ≈ 0。改为先做
|
||||
x = k*ln2 + r (|r| <= ln2/2), 再算 exp(r) 后乘 2^k。
|
||||
"""
|
||||
# 先用"有限且落在范围里"的正向判断过一遍: NaN 与任何数比较都为假, 会同时漏过
|
||||
# 下面两个判断, 而 t.CInt(NaN) 在 x86 上是 INT_MIN -> 后面 2^k 缩放循环要跑
|
||||
# 21 亿次, 表现为"卡死"。故 NaN/越界必须在这里挡掉。
|
||||
if x <= t.CDouble(709.0) and x >= t.CDouble(-745.0):
|
||||
pass
|
||||
else:
|
||||
if x > t.CDouble(709.0):
|
||||
return t.CDouble(1.7976931348623157e308)
|
||||
if x < t.CDouble(-709.0):
|
||||
if x < t.CDouble(-745.0):
|
||||
return t.CDouble(0.0)
|
||||
return x # NaN: 原样返回, 让 NaN 传播而不是卡死
|
||||
# 归约: 把 x 折到 [-ln2/2, ln2/2], 整数部分累积到 k (内联取整, 避免前向引用)
|
||||
ln2: t.CDouble = U_M_LN2
|
||||
q: t.CDouble = x / ln2
|
||||
k: t.CInt = t.CInt(q)
|
||||
frac: t.CDouble = q - t.CDouble(k)
|
||||
if frac > t.CDouble(0.5):
|
||||
k = k + 1
|
||||
elif frac < t.CDouble(-0.5):
|
||||
k = k - 1
|
||||
r: t.CDouble = x - t.CDouble(k) * ln2
|
||||
# exp(r), |r| <= 0.3466: 12 项后截断误差 < 1e-17
|
||||
result: t.CDouble = t.CDouble(1.0)
|
||||
term: t.CDouble = t.CDouble(1.0)
|
||||
term = term * x / t.CDouble(1.0)
|
||||
result = result + term
|
||||
term = term * x / t.CDouble(2.0)
|
||||
result = result + term
|
||||
term = term * x / t.CDouble(3.0)
|
||||
result = result + term
|
||||
term = term * x / t.CDouble(4.0)
|
||||
result = result + term
|
||||
term = term * x / t.CDouble(5.0)
|
||||
result = result + term
|
||||
term = term * x / t.CDouble(6.0)
|
||||
result = result + term
|
||||
term = term * x / t.CDouble(7.0)
|
||||
result = result + term
|
||||
term = term * x / t.CDouble(8.0)
|
||||
result = result + term
|
||||
term = term * x / t.CDouble(9.0)
|
||||
result = result + term
|
||||
term = term * x / t.CDouble(10.0)
|
||||
result = result + term
|
||||
term = term * x / t.CDouble(11.0)
|
||||
result = result + term
|
||||
term = term * x / t.CDouble(12.0)
|
||||
result = result + term
|
||||
term = term * x / t.CDouble(13.0)
|
||||
result = result + term
|
||||
term = term * x / t.CDouble(14.0)
|
||||
result = result + term
|
||||
term = term * x / t.CDouble(15.0)
|
||||
result = result + term
|
||||
term = term * x / t.CDouble(16.0)
|
||||
result = result + term
|
||||
term = term * x / t.CDouble(17.0)
|
||||
result = result + term
|
||||
term = term * x / t.CDouble(18.0)
|
||||
n: t.CInt = 1
|
||||
while n <= 12:
|
||||
term = term * r / t.CDouble(n)
|
||||
result = result + term
|
||||
n += 1
|
||||
while k > 0:
|
||||
result = result * t.CDouble(2.0)
|
||||
k = k - 1
|
||||
while k < 0:
|
||||
result = result * t.CDouble(0.5)
|
||||
k = k + 1
|
||||
return result
|
||||
|
||||
|
||||
|
||||
+103
@@ -428,6 +428,109 @@ vzeroupper""",
|
||||
'ymm12', 'ymm13', 'ymm14', 'ymm15', 'rcx', 'rdx', 'r8', 'r9', 'r10', 'r11'])
|
||||
|
||||
|
||||
def scalar_fma_array(a: t.CPtr, b: t.CPtr, c_val: t.CPtr, out: t.CPtr, n: t.CSizeT) -> t.CVoid:
|
||||
"""标量(逐元素)融合乘加: out[i] = a[i]*b[i] + c_val[i], 一次只算 1 个 double。
|
||||
|
||||
与 simd_fma_array 是同一数学内核的"未向量化"对照实现:
|
||||
* 指令用标量 FMA `vfmadd213sd`(xmm 低位), 与 SIMD 版的 `vfmadd213pd`
|
||||
同为硬件融合乘加、同为单次舍入 -> 逐元素结果与 SIMD 版**逐位一致**,
|
||||
CHECK 可直接对照;
|
||||
* 每次循环 3 读 1 写、8 字节粒度, 相比 ×4 的 ymm 少 4 倍数据并行度,
|
||||
用来量化"向量化本身"带来的收益(SIMD 加速比)。
|
||||
n 须 >= 1; n == 0 直接跳过。"""
|
||||
c.Asm(f"""mov rcx, {c.AsmInp(n, t.ASM_DESCR.REG_ANY)}
|
||||
mov r8, {c.AsmInp(a, t.ASM_DESCR.REG_ANY)}
|
||||
mov r9, {c.AsmInp(b, t.ASM_DESCR.REG_ANY)}
|
||||
mov rdx, {c.AsmInp(c_val, t.ASM_DESCR.REG_ANY)}
|
||||
mov r10, {c.AsmInp(out, t.ASM_DESCR.REG_ANY)}
|
||||
test rcx, rcx
|
||||
jz .Lsca_end
|
||||
.Lsca_loop:
|
||||
vmovsd xmm0, [r8]
|
||||
vmovsd xmm1, [r9]
|
||||
vfmadd213sd xmm0, xmm1, [rdx]
|
||||
vmovsd [r10], xmm0
|
||||
add r8, 8
|
||||
add r9, 8
|
||||
add rdx, 8
|
||||
add r10, 8
|
||||
dec rcx
|
||||
jnz .Lsca_loop
|
||||
.Lsca_end:""",
|
||||
op=[t.ASM_DESCR.CLOBBER_MEMORY, 'xmm0', 'xmm1',
|
||||
'rcx', 'rdx', 'r8', 'r9', 'r10'])
|
||||
|
||||
|
||||
def scalar_add_array(a: t.CPtr, b: t.CPtr, out: t.CPtr, n: t.CSizeT) -> t.CVoid:
|
||||
"""标量(逐元素)加法: out[i] = a[i] + b[i], 一次 1 个 double。
|
||||
|
||||
`simd_add_array` 的未向量化对照: 标量 `vaddsd` vs SIMD `vaddpd`,
|
||||
同为 IEEE 加法 -> 结果逐位一致。"""
|
||||
c.Asm(f"""mov rcx, {c.AsmInp(n, t.ASM_DESCR.REG_ANY)}
|
||||
mov r8, {c.AsmInp(a, t.ASM_DESCR.REG_ANY)}
|
||||
mov r9, {c.AsmInp(b, t.ASM_DESCR.REG_ANY)}
|
||||
mov r10, {c.AsmInp(out, t.ASM_DESCR.REG_ANY)}
|
||||
test rcx, rcx
|
||||
jz .Lsca_add_end
|
||||
.Lsca_add_loop:
|
||||
vmovsd xmm0, [r8]
|
||||
vaddsd xmm0, xmm0, [r9]
|
||||
vmovsd [r10], xmm0
|
||||
add r8, 8
|
||||
add r9, 8
|
||||
add r10, 8
|
||||
dec rcx
|
||||
jnz .Lsca_add_loop
|
||||
.Lsca_add_end:""",
|
||||
op=[t.ASM_DESCR.CLOBBER_MEMORY, 'xmm0', 'rcx', 'r8', 'r9', 'r10'])
|
||||
|
||||
|
||||
def scalar_mul_array(a: t.CPtr, b: t.CPtr, out: t.CPtr, n: t.CSizeT) -> t.CVoid:
|
||||
"""标量(逐元素)乘法: out[i] = a[i] * b[i], 一次 1 个 double。
|
||||
|
||||
`simd_mul_array` 的未向量化对照: 标量 `vmulsd` vs SIMD `vmulpd`,
|
||||
同为 IEEE 乘法 -> 结果逐位一致。"""
|
||||
c.Asm(f"""mov rcx, {c.AsmInp(n, t.ASM_DESCR.REG_ANY)}
|
||||
mov r8, {c.AsmInp(a, t.ASM_DESCR.REG_ANY)}
|
||||
mov r9, {c.AsmInp(b, t.ASM_DESCR.REG_ANY)}
|
||||
mov r10, {c.AsmInp(out, t.ASM_DESCR.REG_ANY)}
|
||||
test rcx, rcx
|
||||
jz .Lsca_mul_end
|
||||
.Lsca_mul_loop:
|
||||
vmovsd xmm0, [r8]
|
||||
vmulsd xmm0, xmm0, [r9]
|
||||
vmovsd [r10], xmm0
|
||||
add r8, 8
|
||||
add r9, 8
|
||||
add r10, 8
|
||||
dec rcx
|
||||
jnz .Lsca_mul_loop
|
||||
.Lsca_mul_end:""",
|
||||
op=[t.ASM_DESCR.CLOBBER_MEMORY, 'xmm0', 'rcx', 'r8', 'r9', 'r10'])
|
||||
|
||||
|
||||
def scalar_sqrt_array(a: t.CPtr, out: t.CPtr, n: t.CSizeT) -> t.CVoid:
|
||||
"""标量(逐元素)平方根: out[i] = sqrt(a[i]), 一次 1 个 double。
|
||||
|
||||
`simd_sqrt_array` 的未向量化对照: 标量 `vsqrtsd` vs SIMD `vsqrtpd`,
|
||||
两者都是 IEEE 正确舍入的平方根 -> 结果逐位一致。"""
|
||||
c.Asm(f"""mov rcx, {c.AsmInp(n, t.ASM_DESCR.REG_ANY)}
|
||||
mov r8, {c.AsmInp(a, t.ASM_DESCR.REG_ANY)}
|
||||
mov r10, {c.AsmInp(out, t.ASM_DESCR.REG_ANY)}
|
||||
test rcx, rcx
|
||||
jz .Lsca_sqrt_end
|
||||
.Lsca_sqrt_loop:
|
||||
vmovsd xmm0, [r8]
|
||||
vsqrtsd xmm0, xmm0, xmm0
|
||||
vmovsd [r10], xmm0
|
||||
add r8, 8
|
||||
add r10, 8
|
||||
dec rcx
|
||||
jnz .Lsca_sqrt_loop
|
||||
.Lsca_sqrt_end:""",
|
||||
op=[t.ASM_DESCR.CLOBBER_MEMORY, 'xmm0', 'rcx', 'r8', 'r10'])
|
||||
|
||||
|
||||
def simd_div_array(a: t.CPtr, b: t.CPtr, out: t.CPtr, n: t.CSizeT) -> t.CVoid:
|
||||
"""AVX2 batch: out[i:i+4] = a[i:i+4] / b[i:i+4]"""
|
||||
c.Asm(f"""mov rcx, {c.AsmInp(n, t.ASM_DESCR.REG_ANY)}
|
||||
|
||||
@@ -27,6 +27,7 @@ find_package(Qt6 6.5 REQUIRED COMPONENTS
|
||||
Gui
|
||||
Widgets
|
||||
Network
|
||||
Charts
|
||||
)
|
||||
|
||||
# Win32 GUI 应用不需要控制台
|
||||
@@ -40,6 +41,7 @@ set(VQT6_SOURCES
|
||||
_bridge_widgets.cpp
|
||||
_bridge_gui.cpp
|
||||
_bridge_network.cpp
|
||||
_bridge_canvas.cpp
|
||||
)
|
||||
|
||||
set(VQT6_HEADERS
|
||||
@@ -47,6 +49,7 @@ set(VQT6_HEADERS
|
||||
_bridge_widgets.h
|
||||
_bridge_gui.h
|
||||
_bridge_network.h
|
||||
_bridge_canvas.h
|
||||
)
|
||||
|
||||
# Qt 模块
|
||||
@@ -55,6 +58,7 @@ set(VQT6_QT_MODULES
|
||||
Qt6::Gui
|
||||
Qt6::Widgets
|
||||
Qt6::Network
|
||||
Qt6::Charts
|
||||
)
|
||||
|
||||
# ============================================================
|
||||
|
||||
+517
-169
@@ -23,6 +23,15 @@ from vqt6._qtnetwork import *
|
||||
|
||||
# ============================================================
|
||||
# 高级 API 类(Pythonic 包装,不透明指针)
|
||||
#
|
||||
# 为什么每个类都写 __new__:
|
||||
# Viper 的类实例默认是"创建函数当前帧的 alloca"(见
|
||||
# CallGenCtor._translate_struct_ctor: tmp = build_alloca(struct_ty))。
|
||||
# 一旦包装对象被存进字段、或被 return 出函数,帧销毁后该指针即悬垂——
|
||||
# 表现为读 _ptr 得到垃圾、再把垃圾传给 Qt(如 QLayout::addWidget 崩溃)。
|
||||
# 编译器在类定义了 __new__ 时改用其返回值作存储指针,故这里统一
|
||||
# stdlib.malloc 堆分配包装壳(单字段 { _ptr } = 8 字节,进程期存活)。
|
||||
# 同模块内即用即弃的场景不受影响;跨帧/跨模块存取则必须靠它。
|
||||
# ============================================================
|
||||
|
||||
# ============================================================
|
||||
@@ -140,6 +149,9 @@ class QApplication:
|
||||
"""QApplication 包装(不透明指针)"""
|
||||
_ptr: QtAppPtr
|
||||
|
||||
def __new__(self, argv: list[str] | t.CPtr) -> t.CPtr:
|
||||
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
|
||||
|
||||
def __init__(self, argv: list[str] | t.CPtr):
|
||||
# 把 argv 转换为 argc/argv 数组
|
||||
argc: t.CInt = 1
|
||||
@@ -168,12 +180,27 @@ class QApplication:
|
||||
def desktop_height(self) -> int:
|
||||
return vqt6_qapplication_desktop_height(self._ptr)
|
||||
|
||||
def set_font_family(self, family: str):
|
||||
"""设置应用级字体族(全局生效)
|
||||
|
||||
必须在创建任何控件之前调用:Qt 是在控件构造时从应用字体继承的,
|
||||
之后再设只会影响"此后新建"的控件。
|
||||
|
||||
这里必须走应用字体而不是给主窗口挂样式表(font-family):Qt Charts 的
|
||||
标题/图例/坐标轴标签画在 QChart(QGraphicsWidget)上,样式表够不到它们,
|
||||
只有应用字体能一并覆盖。
|
||||
"""
|
||||
vqt6_qapplication_set_font_family_utf8(self._ptr, family)
|
||||
|
||||
|
||||
@t.NoVTable
|
||||
class QWidget:
|
||||
"""QWidget 包装"""
|
||||
_ptr: QtWidgetPtr
|
||||
|
||||
def __new__(self, parent: "QWidget | None" = None) -> t.CPtr:
|
||||
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
|
||||
|
||||
def __init__(self, parent: "QWidget | None" = None):
|
||||
if parent is not None and parent._ptr is not None:
|
||||
self._ptr = vqt6_qwidget_create_with_parent(parent._ptr)
|
||||
@@ -236,6 +263,9 @@ class QLabel:
|
||||
"""QLabel 包装"""
|
||||
_ptr: QtLabelPtr
|
||||
|
||||
def __new__(self, text: str = "", parent: "QWidget | None" = None) -> t.CPtr:
|
||||
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
|
||||
|
||||
def __init__(self, text: str = "", parent: "QWidget | None" = None):
|
||||
text_qs: QtStringPtr = vqt6_qstring_create(text)
|
||||
if parent is not None and parent._ptr is not None:
|
||||
@@ -273,6 +303,9 @@ class QPushButton:
|
||||
"""QPushButton 包装"""
|
||||
_ptr: QtPushButtonPtr
|
||||
|
||||
def __new__(self, text: str = "", parent: "QWidget | None" = None) -> t.CPtr:
|
||||
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
|
||||
|
||||
def __init__(self, text: str = "", parent: "QWidget | None" = None):
|
||||
text_qs: QtStringPtr = vqt6_qstring_create(text)
|
||||
if parent is not None and parent._ptr is not None:
|
||||
@@ -328,13 +361,183 @@ class QPushButton:
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 窗口:QMainWindow / QDialog
|
||||
# 菜单:QAction / QMenu / QMenuBar / QStatusBar
|
||||
#
|
||||
# 按依赖顺序定义(QAction → QMenu → QMenuBar → QStatusBar), 并整体排在
|
||||
# QMainWindow 之前, 使 addMenu()/addActionWithText() 的返回注解不再前向引用。
|
||||
#
|
||||
# 注意: 返回"包装类对象"(按值 struct)的方法无法跨模块解析返回类型——
|
||||
# 依赖模块 text.ll 里是 %"sha1.QStatusBar" 这类 struct 值(不含 *,
|
||||
# _ParseLLVMRetTyStr 解析失败), 而 vpi 缓存的是带引号的前向注解
|
||||
# ('QStatusBar'), 同样解析失败 → 调用方 fatal(mro_ret)。
|
||||
# 故 QMainWindow.menuBar/statusBar 一律返回裸指针。
|
||||
# ============================================================
|
||||
@t.NoVTable
|
||||
class QAction:
|
||||
"""QAction 包装"""
|
||||
_ptr: QtActionPtr
|
||||
|
||||
def __init__(self, text: str = ""):
|
||||
if text != "":
|
||||
qs: QtStringPtr = vqt6_qstring_create(text)
|
||||
self._ptr = vqt6_qaction_create_with_text(qs)
|
||||
vqt6_qstring_free(qs)
|
||||
else:
|
||||
self._ptr = vqt6_qaction_create()
|
||||
if self._ptr is None:
|
||||
raise RuntimeError("Failed to create QAction")
|
||||
|
||||
def setText(self, text: str):
|
||||
qs: QtStringPtr = vqt6_qstring_create(text)
|
||||
vqt6_qaction_set_text(self._ptr, qs)
|
||||
vqt6_qstring_free(qs)
|
||||
|
||||
def text(self) -> str:
|
||||
return _w_text_of(vqt6_qaction_text(self._ptr))
|
||||
|
||||
def setShortcut(self, key_seq: str):
|
||||
qs: QtStringPtr = vqt6_qstring_create(key_seq)
|
||||
vqt6_qaction_set_shortcut(self._ptr, qs)
|
||||
vqt6_qstring_free(qs)
|
||||
|
||||
def setCheckable(self, checkable: int):
|
||||
vqt6_qaction_set_checkable(self._ptr, checkable)
|
||||
|
||||
def isCheckable(self) -> int:
|
||||
return vqt6_qaction_is_checkable(self._ptr)
|
||||
|
||||
def setChecked(self, checked: int):
|
||||
vqt6_qaction_set_checked(self._ptr, checked)
|
||||
|
||||
def isChecked(self) -> int:
|
||||
return vqt6_qaction_is_checked(self._ptr)
|
||||
|
||||
def setEnabled(self, enabled: int):
|
||||
vqt6_qaction_set_enabled(self._ptr, enabled)
|
||||
|
||||
def isEnabled(self) -> int:
|
||||
return vqt6_qaction_is_enabled(self._ptr)
|
||||
|
||||
def trigger(self):
|
||||
vqt6_qaction_trigger(self._ptr)
|
||||
|
||||
def triggered(self, cb: bytes, data: bytes = 0):
|
||||
vqt6_qaction_connect_triggered(self._ptr, cb, data)
|
||||
|
||||
|
||||
@t.NoVTable
|
||||
class QMenu:
|
||||
"""QMenu 包装"""
|
||||
_ptr: QtMenuPtr
|
||||
|
||||
def __init__(self, title: str = ""):
|
||||
if title != "":
|
||||
qs: QtStringPtr = vqt6_qstring_create(title)
|
||||
self._ptr = vqt6_qmenu_create_with_title(qs)
|
||||
vqt6_qstring_free(qs)
|
||||
else:
|
||||
self._ptr = vqt6_qmenu_create()
|
||||
if self._ptr is None:
|
||||
raise RuntimeError("Failed to create QMenu")
|
||||
|
||||
def addAction(self, action: t.CPtr):
|
||||
vqt6_qmenu_add_action(self._ptr, action)
|
||||
|
||||
def addActionWithText(self, text: str) -> "QAction":
|
||||
qs: QtStringPtr = vqt6_qstring_create(text)
|
||||
ap: QtActionPtr = vqt6_qmenu_add_action_with_text(self._ptr, qs)
|
||||
vqt6_qstring_free(qs)
|
||||
a = QAction()
|
||||
a._ptr = ap
|
||||
return a
|
||||
|
||||
def addMenu(self, title: str) -> "QMenu":
|
||||
qs: QtStringPtr = vqt6_qstring_create(title)
|
||||
mp: QtMenuPtr = vqt6_qmenu_add_menu(self._ptr, qs)
|
||||
vqt6_qstring_free(qs)
|
||||
m = QMenu()
|
||||
m._ptr = mp
|
||||
return m
|
||||
|
||||
def addSeparator(self):
|
||||
vqt6_qmenu_add_separator(self._ptr)
|
||||
|
||||
def setTitle(self, title: str):
|
||||
qs: QtStringPtr = vqt6_qstring_create(title)
|
||||
vqt6_qmenu_set_title(self._ptr, qs)
|
||||
vqt6_qstring_free(qs)
|
||||
|
||||
def clear(self):
|
||||
vqt6_qmenu_clear(self._ptr)
|
||||
|
||||
|
||||
@t.NoVTable
|
||||
class QMenuBar:
|
||||
"""QMenuBar 包装"""
|
||||
_ptr: QtMenuBarPtr
|
||||
|
||||
def __init__(self, parent: "QWidget | None" = None):
|
||||
if parent is not None and parent._ptr is not None:
|
||||
self._ptr = vqt6_qmenubar_create_with_parent(parent._ptr)
|
||||
else:
|
||||
self._ptr = vqt6_qmenubar_create()
|
||||
if self._ptr is None:
|
||||
raise RuntimeError("Failed to create QMenuBar")
|
||||
|
||||
def addMenu(self, title: str) -> "QMenu":
|
||||
qs: QtStringPtr = vqt6_qstring_create(title)
|
||||
mp: QtMenuPtr = vqt6_qmenubar_add_menu(self._ptr, qs)
|
||||
vqt6_qstring_free(qs)
|
||||
m = QMenu()
|
||||
m._ptr = mp
|
||||
return m
|
||||
|
||||
def addAction(self, action: t.CPtr):
|
||||
vqt6_qmenubar_add_action(self._ptr, action)
|
||||
|
||||
def clear(self):
|
||||
vqt6_qmenubar_clear(self._ptr)
|
||||
|
||||
|
||||
@t.NoVTable
|
||||
class QStatusBar:
|
||||
"""QStatusBar 包装"""
|
||||
_ptr: QtStatusBarPtr
|
||||
|
||||
def __init__(self, parent: "QWidget | None" = None):
|
||||
if parent is not None and parent._ptr is not None:
|
||||
self._ptr = vqt6_qstatusbar_create_with_parent(parent._ptr)
|
||||
else:
|
||||
self._ptr = vqt6_qstatusbar_create()
|
||||
if self._ptr is None:
|
||||
raise RuntimeError("Failed to create QStatusBar")
|
||||
|
||||
def showMessage(self, message: str, timeout_ms: int = 0):
|
||||
qs: QtStringPtr = vqt6_qstring_create(message)
|
||||
vqt6_qstatusbar_show_message(self._ptr, qs, timeout_ms)
|
||||
vqt6_qstring_free(qs)
|
||||
|
||||
def clearMessage(self):
|
||||
vqt6_qstatusbar_clear_message(self._ptr)
|
||||
|
||||
def currentMessage(self) -> str:
|
||||
return _w_text_of(vqt6_qstatusbar_current_message(self._ptr))
|
||||
|
||||
def addWidget(self, w: t.CPtr, stretch: int = 0):
|
||||
vqt6_qstatusbar_add_widget(self._ptr, w, stretch)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 窗口:QMainWindow(依赖上面的 QMenuBar / QStatusBar, 故排在其后)
|
||||
# ============================================================
|
||||
@t.NoVTable
|
||||
class QMainWindow:
|
||||
"""QMainWindow 包装"""
|
||||
_ptr: QtMainWindowPtr
|
||||
|
||||
def __new__(self, parent: "QWidget | None" = None) -> t.CPtr:
|
||||
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
|
||||
|
||||
def __init__(self, parent: "QWidget | None" = None):
|
||||
if parent is not None and parent._ptr is not None:
|
||||
self._ptr = vqt6_qmainwindow_create_with_parent(parent._ptr)
|
||||
@@ -346,20 +549,16 @@ class QMainWindow:
|
||||
def setCentralWidget(self, w: t.CPtr):
|
||||
vqt6_qmainwindow_set_central_widget(self._ptr, w)
|
||||
|
||||
def menuBar(self) -> "QMenuBar":
|
||||
mb_ptr: QtMenuBarPtr = vqt6_qmainwindow_menu_bar(self._ptr)
|
||||
mb = QMenuBar()
|
||||
mb._ptr = mb_ptr
|
||||
return mb
|
||||
def menuBar(self) -> QtMenuBarPtr:
|
||||
"""返回窗口自带的菜单栏裸指针(不新建; 需要包装类请自行 new QMenuBar 后挂 _ptr)"""
|
||||
return vqt6_qmainwindow_menu_bar(self._ptr)
|
||||
|
||||
def setMenuBar(self, mb: t.CPtr):
|
||||
vqt6_qmainwindow_set_menu_bar(self._ptr, mb)
|
||||
|
||||
def statusBar(self) -> "QStatusBar":
|
||||
sb_ptr: QtStatusBarPtr = vqt6_qmainwindow_status_bar(self._ptr)
|
||||
sb = QStatusBar()
|
||||
sb._ptr = sb_ptr
|
||||
return sb
|
||||
def statusBar(self) -> QtStatusBarPtr:
|
||||
"""返回窗口自带的状态栏裸指针(不新建; 需要包装类请自行 new QStatusBar 后挂 _ptr)"""
|
||||
return vqt6_qmainwindow_status_bar(self._ptr)
|
||||
|
||||
def addToolBar(self, tb: t.CPtr):
|
||||
vqt6_qmainwindow_add_tool_bar(self._ptr, tb)
|
||||
@@ -385,6 +584,9 @@ class QMainWindow:
|
||||
def setWindowTitle(self, title: str):
|
||||
_w_set_window_title(self._ptr, title)
|
||||
|
||||
def setStyleSheet(self, qss: str):
|
||||
vqt6_qwidget_set_stylesheet_utf8(self._ptr, qss)
|
||||
|
||||
def setEnabled(self, enabled: int):
|
||||
_w_set_enabled(self._ptr, enabled)
|
||||
|
||||
@@ -453,6 +655,9 @@ class QLineEdit:
|
||||
"""QLineEdit 包装"""
|
||||
_ptr: QtLineEditPtr
|
||||
|
||||
def __new__(self, text: str = "", parent: "QWidget | None" = None) -> t.CPtr:
|
||||
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
|
||||
|
||||
def __init__(self, text: str = "", parent: "QWidget | None" = None):
|
||||
qs: QtStringPtr = vqt6_qstring_create(text)
|
||||
if parent is not None and parent._ptr is not None:
|
||||
@@ -518,6 +723,9 @@ class QCheckBox:
|
||||
"""QCheckBox 包装"""
|
||||
_ptr: QtCheckBoxPtr
|
||||
|
||||
def __new__(self, text: str = "", parent: "QWidget | None" = None) -> t.CPtr:
|
||||
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
|
||||
|
||||
def __init__(self, text: str = "", parent: "QWidget | None" = None):
|
||||
qs: QtStringPtr = vqt6_qstring_create(text)
|
||||
if parent is not None and parent._ptr is not None:
|
||||
@@ -578,6 +786,9 @@ class QRadioButton:
|
||||
"""QRadioButton 包装"""
|
||||
_ptr: QtRadioButtonPtr
|
||||
|
||||
def __new__(self, text: str = "", parent: "QWidget | None" = None) -> t.CPtr:
|
||||
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
|
||||
|
||||
def __init__(self, text: str = "", parent: "QWidget | None" = None):
|
||||
qs: QtStringPtr = vqt6_qstring_create(text)
|
||||
if parent is not None and parent._ptr is not None:
|
||||
@@ -760,6 +971,9 @@ class QSpinBox:
|
||||
"""QSpinBox 包装"""
|
||||
_ptr: QtSpinBoxPtr
|
||||
|
||||
def __new__(self, parent: "QWidget | None" = None) -> t.CPtr:
|
||||
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
|
||||
|
||||
def __init__(self, parent: "QWidget | None" = None):
|
||||
if parent is not None and parent._ptr is not None:
|
||||
self._ptr = vqt6_qspinbox_create_with_parent(parent._ptr)
|
||||
@@ -826,6 +1040,9 @@ class QProgressBar:
|
||||
"""QProgressBar 包装"""
|
||||
_ptr: QtProgressBarPtr
|
||||
|
||||
def __new__(self, parent: "QWidget | None" = None) -> t.CPtr:
|
||||
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
|
||||
|
||||
def __init__(self, parent: "QWidget | None" = None):
|
||||
if parent is not None and parent._ptr is not None:
|
||||
self._ptr = vqt6_qprogressbar_create_with_parent(parent._ptr)
|
||||
@@ -857,6 +1074,9 @@ class QProgressBar:
|
||||
def setTextVisible(self, visible: int):
|
||||
vqt6_qprogressbar_set_text_visible(self._ptr, visible)
|
||||
|
||||
def setStyleSheet(self, qss: str):
|
||||
vqt6_qwidget_set_stylesheet_utf8(self._ptr, qss)
|
||||
|
||||
def valueChanged(self, cb: bytes, data: bytes = 0):
|
||||
vqt6_qprogressbar_connect_value_changed(self._ptr, cb, data)
|
||||
|
||||
@@ -946,6 +1166,9 @@ class QPlainTextEdit:
|
||||
"""QPlainTextEdit 包装"""
|
||||
_ptr: QtPlainTextEditPtr
|
||||
|
||||
def __new__(self, parent: "QWidget | None" = None) -> t.CPtr:
|
||||
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
|
||||
|
||||
def __init__(self, parent: "QWidget | None" = None):
|
||||
if parent is not None and parent._ptr is not None:
|
||||
self._ptr = vqt6_qplaintextedit_create_with_parent(parent._ptr)
|
||||
@@ -1226,6 +1449,9 @@ class QTabWidget:
|
||||
"""QTabWidget 包装"""
|
||||
_ptr: QtTabWidgetPtr
|
||||
|
||||
def __new__(self, parent: "QWidget | None" = None) -> t.CPtr:
|
||||
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
|
||||
|
||||
def __init__(self, parent: "QWidget | None" = None):
|
||||
if parent is not None and parent._ptr is not None:
|
||||
self._ptr = vqt6_qtabwidget_create_with_parent(parent._ptr)
|
||||
@@ -1553,6 +1779,9 @@ class QTimer:
|
||||
"""QTimer 包装"""
|
||||
_ptr: QtTimerPtr
|
||||
|
||||
def __new__(self) -> t.CPtr:
|
||||
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
|
||||
|
||||
def __init__(self):
|
||||
self._ptr = vqt6_qtimer_create()
|
||||
if self._ptr is None:
|
||||
@@ -1580,164 +1809,6 @@ class QTimer:
|
||||
vqt6_qtimer_connect_timeout(self._ptr, cb, data)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 菜单:QMenuBar / QMenu / QAction / QStatusBar
|
||||
# ============================================================
|
||||
@t.NoVTable
|
||||
class QMenuBar:
|
||||
"""QMenuBar 包装"""
|
||||
_ptr: QtMenuBarPtr
|
||||
|
||||
def __init__(self, parent: "QWidget | None" = None):
|
||||
if parent is not None and parent._ptr is not None:
|
||||
self._ptr = vqt6_qmenubar_create_with_parent(parent._ptr)
|
||||
else:
|
||||
self._ptr = vqt6_qmenubar_create()
|
||||
if self._ptr is None:
|
||||
raise RuntimeError("Failed to create QMenuBar")
|
||||
|
||||
def addMenu(self, title: str) -> "QMenu":
|
||||
qs: QtStringPtr = vqt6_qstring_create(title)
|
||||
mp: QtMenuPtr = vqt6_qmenubar_add_menu(self._ptr, qs)
|
||||
vqt6_qstring_free(qs)
|
||||
m = QMenu()
|
||||
m._ptr = mp
|
||||
return m
|
||||
|
||||
def addAction(self, action: t.CPtr):
|
||||
vqt6_qmenubar_add_action(self._ptr, action)
|
||||
|
||||
def clear(self):
|
||||
vqt6_qmenubar_clear(self._ptr)
|
||||
|
||||
|
||||
@t.NoVTable
|
||||
class QMenu:
|
||||
"""QMenu 包装"""
|
||||
_ptr: QtMenuPtr
|
||||
|
||||
def __init__(self, title: str = ""):
|
||||
if title != "":
|
||||
qs: QtStringPtr = vqt6_qstring_create(title)
|
||||
self._ptr = vqt6_qmenu_create_with_title(qs)
|
||||
vqt6_qstring_free(qs)
|
||||
else:
|
||||
self._ptr = vqt6_qmenu_create()
|
||||
if self._ptr is None:
|
||||
raise RuntimeError("Failed to create QMenu")
|
||||
|
||||
def addAction(self, action: t.CPtr):
|
||||
vqt6_qmenu_add_action(self._ptr, action)
|
||||
|
||||
def addActionWithText(self, text: str) -> "QAction":
|
||||
qs: QtStringPtr = vqt6_qstring_create(text)
|
||||
ap: QtActionPtr = vqt6_qmenu_add_action_with_text(self._ptr, qs)
|
||||
vqt6_qstring_free(qs)
|
||||
a = QAction()
|
||||
a._ptr = ap
|
||||
return a
|
||||
|
||||
def addMenu(self, title: str) -> "QMenu":
|
||||
qs: QtStringPtr = vqt6_qstring_create(title)
|
||||
mp: QtMenuPtr = vqt6_qmenu_add_menu(self._ptr, qs)
|
||||
vqt6_qstring_free(qs)
|
||||
m = QMenu()
|
||||
m._ptr = mp
|
||||
return m
|
||||
|
||||
def addSeparator(self):
|
||||
vqt6_qmenu_add_separator(self._ptr)
|
||||
|
||||
def setTitle(self, title: str):
|
||||
qs: QtStringPtr = vqt6_qstring_create(title)
|
||||
vqt6_qmenu_set_title(self._ptr, qs)
|
||||
vqt6_qstring_free(qs)
|
||||
|
||||
def clear(self):
|
||||
vqt6_qmenu_clear(self._ptr)
|
||||
|
||||
|
||||
@t.NoVTable
|
||||
class QAction:
|
||||
"""QAction 包装"""
|
||||
_ptr: QtActionPtr
|
||||
|
||||
def __init__(self, text: str = ""):
|
||||
if text != "":
|
||||
qs: QtStringPtr = vqt6_qstring_create(text)
|
||||
self._ptr = vqt6_qaction_create_with_text(qs)
|
||||
vqt6_qstring_free(qs)
|
||||
else:
|
||||
self._ptr = vqt6_qaction_create()
|
||||
if self._ptr is None:
|
||||
raise RuntimeError("Failed to create QAction")
|
||||
|
||||
def setText(self, text: str):
|
||||
qs: QtStringPtr = vqt6_qstring_create(text)
|
||||
vqt6_qaction_set_text(self._ptr, qs)
|
||||
vqt6_qstring_free(qs)
|
||||
|
||||
def text(self) -> str:
|
||||
return _w_text_of(vqt6_qaction_text(self._ptr))
|
||||
|
||||
def setShortcut(self, key_seq: str):
|
||||
qs: QtStringPtr = vqt6_qstring_create(key_seq)
|
||||
vqt6_qaction_set_shortcut(self._ptr, qs)
|
||||
vqt6_qstring_free(qs)
|
||||
|
||||
def setCheckable(self, checkable: int):
|
||||
vqt6_qaction_set_checkable(self._ptr, checkable)
|
||||
|
||||
def isCheckable(self) -> int:
|
||||
return vqt6_qaction_is_checkable(self._ptr)
|
||||
|
||||
def setChecked(self, checked: int):
|
||||
vqt6_qaction_set_checked(self._ptr, checked)
|
||||
|
||||
def isChecked(self) -> int:
|
||||
return vqt6_qaction_is_checked(self._ptr)
|
||||
|
||||
def setEnabled(self, enabled: int):
|
||||
vqt6_qaction_set_enabled(self._ptr, enabled)
|
||||
|
||||
def isEnabled(self) -> int:
|
||||
return vqt6_qaction_is_enabled(self._ptr)
|
||||
|
||||
def trigger(self):
|
||||
vqt6_qaction_trigger(self._ptr)
|
||||
|
||||
def triggered(self, cb: bytes, data: bytes = 0):
|
||||
vqt6_qaction_connect_triggered(self._ptr, cb, data)
|
||||
|
||||
|
||||
@t.NoVTable
|
||||
class QStatusBar:
|
||||
"""QStatusBar 包装"""
|
||||
_ptr: QtStatusBarPtr
|
||||
|
||||
def __init__(self, parent: "QWidget | None" = None):
|
||||
if parent is not None and parent._ptr is not None:
|
||||
self._ptr = vqt6_qstatusbar_create_with_parent(parent._ptr)
|
||||
else:
|
||||
self._ptr = vqt6_qstatusbar_create()
|
||||
if self._ptr is None:
|
||||
raise RuntimeError("Failed to create QStatusBar")
|
||||
|
||||
def showMessage(self, message: str, timeout_ms: int = 0):
|
||||
qs: QtStringPtr = vqt6_qstring_create(message)
|
||||
vqt6_qstatusbar_show_message(self._ptr, qs, timeout_ms)
|
||||
vqt6_qstring_free(qs)
|
||||
|
||||
def clearMessage(self):
|
||||
vqt6_qstatusbar_clear_message(self._ptr)
|
||||
|
||||
def currentMessage(self) -> str:
|
||||
return _w_text_of(vqt6_qstatusbar_current_message(self._ptr))
|
||||
|
||||
def addWidget(self, w: t.CPtr, stretch: int = 0):
|
||||
vqt6_qstatusbar_add_widget(self._ptr, w, stretch)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 绘图基础:QColor / QFont
|
||||
# ============================================================
|
||||
@@ -1798,6 +1869,278 @@ class QFont:
|
||||
vqt6_qfont_set_underline(self._ptr, underline)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 剪贴板(复制链接等)
|
||||
# ============================================================
|
||||
def clipboard_set_text(text: str):
|
||||
"""把文本写入系统剪贴板"""
|
||||
vqt6_clipboard_set_text_utf8(text)
|
||||
|
||||
|
||||
def clipboard_text() -> str:
|
||||
"""读取系统剪贴板文本"""
|
||||
buf: bytes = stdlib.malloc(4096)
|
||||
vqt6_clipboard_text_utf8(buf, 4096)
|
||||
s: str = buf
|
||||
stdlib.free(buf)
|
||||
return s
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 自绘控件:Canvas / Graph3D / ChartView
|
||||
# ============================================================
|
||||
@t.NoVTable
|
||||
class Canvas:
|
||||
"""手绘板 / 28x28 灰度显示控件
|
||||
|
||||
手绘模式下鼠标左键描绘; toMnist28 把笔迹按 MNIST 预处理
|
||||
(包围盒缩放到 20x20 后质心居中)输出为 28x28 灰度字节数组。
|
||||
"""
|
||||
_ptr: QtCanvasPtr
|
||||
|
||||
def __new__(self, parent: "QWidget | None" = None) -> t.CPtr:
|
||||
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
|
||||
|
||||
def __init__(self, parent: "QWidget | None" = None):
|
||||
if parent is not None and parent._ptr is not None:
|
||||
self._ptr = vqt6_canvas_create_with_parent(parent._ptr)
|
||||
else:
|
||||
self._ptr = vqt6_canvas_create()
|
||||
if self._ptr is None:
|
||||
raise RuntimeError("Failed to create Canvas")
|
||||
|
||||
def clear(self):
|
||||
vqt6_canvas_clear(self._ptr)
|
||||
|
||||
def hasInk(self) -> int:
|
||||
return vqt6_canvas_has_ink(self._ptr)
|
||||
|
||||
def setMode(self, mode: int):
|
||||
"""0 = 手绘, 1 = 图像显示"""
|
||||
vqt6_canvas_set_mode(self._ptr, mode)
|
||||
|
||||
def mode(self) -> int:
|
||||
return vqt6_canvas_mode(self._ptr)
|
||||
|
||||
def setPen(self, r: int, g: int, b: int, width_norm: t.CDouble):
|
||||
vqt6_canvas_set_pen(self._ptr, r, g, b, width_norm)
|
||||
|
||||
def setGrid(self, on: int):
|
||||
vqt6_canvas_set_grid(self._ptr, on)
|
||||
|
||||
def setScanLine(self, y_norm: int):
|
||||
"""0..1000 扫描线位置, <0 关闭"""
|
||||
vqt6_canvas_set_scan_line(self._ptr, y_norm)
|
||||
|
||||
def setBackground(self, r: int, g: int, b: int):
|
||||
vqt6_canvas_set_bg(self._ptr, r, g, b)
|
||||
|
||||
def toMnist28(self, out: bytes) -> int:
|
||||
"""笔迹 → 28x28 灰度(写入 out); 无墨迹返回 0"""
|
||||
return vqt6_canvas_to_mnist28(self._ptr, out)
|
||||
|
||||
def setImage28(self, inp: bytes):
|
||||
"""28x28 灰度 → 显示(自动切到图像显示模式)"""
|
||||
vqt6_canvas_set_image28(self._ptr, inp)
|
||||
|
||||
def changed(self, cb: bytes, data: bytes = 0):
|
||||
"""笔迹完成(鼠标抬起)回调: def cb(slot_id: int, data: t.CPtr)"""
|
||||
vqt6_canvas_connect_changed(self._ptr, cb, data)
|
||||
|
||||
def show(self):
|
||||
_w_show(self._ptr)
|
||||
|
||||
def hide(self):
|
||||
_w_hide(self._ptr)
|
||||
|
||||
def setGeometry(self, x: int, y: int, w: int, h: int):
|
||||
_w_set_geometry(self._ptr, x, y, w, h)
|
||||
|
||||
def resize(self, w: int, h: int):
|
||||
_w_resize(self._ptr, w, h)
|
||||
|
||||
def move(self, x: int, y: int):
|
||||
_w_move(self._ptr, x, y)
|
||||
|
||||
def setEnabled(self, enabled: int):
|
||||
_w_set_enabled(self._ptr, enabled)
|
||||
|
||||
def setStyleSheet(self, qss: str):
|
||||
vqt6_qwidget_set_stylesheet_utf8(self._ptr, qss)
|
||||
|
||||
|
||||
@t.NoVTable
|
||||
class Graph3D:
|
||||
"""软件 3D 神经网络结构图控件
|
||||
|
||||
层次沿 Z 轴排布(输入层在最远端), 各层按近正方形网格铺开;
|
||||
相邻层前向全连接。左键拖拽旋转, 滚轮缩放。
|
||||
渲染 API 与后端无关, 将来换 OpenGL 实现时调用代码无需改动。
|
||||
"""
|
||||
_ptr: QtGraph3DPtr
|
||||
|
||||
def __new__(self, parent: "QWidget | None" = None) -> t.CPtr:
|
||||
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
|
||||
|
||||
def __init__(self, parent: "QWidget | None" = None):
|
||||
if parent is not None and parent._ptr is not None:
|
||||
self._ptr = vqt6_graph3d_create_with_parent(parent._ptr)
|
||||
else:
|
||||
self._ptr = vqt6_graph3d_create()
|
||||
if self._ptr is None:
|
||||
raise RuntimeError("Failed to create Graph3D")
|
||||
|
||||
def setup(self, layer_sizes: t.CInt | t.CPtr, n_layers: int):
|
||||
"""layer_sizes: int 数组指针(输入层→输出层各层节点数)"""
|
||||
vqt6_graph3d_setup(self._ptr, layer_sizes, n_layers)
|
||||
|
||||
def clear(self):
|
||||
vqt6_graph3d_clear(self._ptr)
|
||||
|
||||
def nodeCount(self) -> int:
|
||||
return vqt6_graph3d_node_count(self._ptr)
|
||||
|
||||
def edgeCount(self) -> int:
|
||||
return vqt6_graph3d_edge_count(self._ptr)
|
||||
|
||||
def setLayerAlpha(self, layer: int, alpha: t.CDouble):
|
||||
"""逐层点亮: 0..1"""
|
||||
vqt6_graph3d_set_layer_alpha(self._ptr, layer, alpha)
|
||||
|
||||
def setAllLayerAlpha(self, alpha: t.CDouble):
|
||||
vqt6_graph3d_set_all_layer_alpha(self._ptr, alpha)
|
||||
|
||||
def setValues(self, values: t.CDouble | t.CPtr, n: int):
|
||||
"""每节点激活值(0..1), 影响亮度与半径"""
|
||||
vqt6_graph3d_set_values(self._ptr, values, n)
|
||||
|
||||
def setEdgeWeights(self, w: t.CDouble | t.CPtr, n: int):
|
||||
"""边权重(按层间顺序), 影响颜色/透明度并按 |w| 裁剪"""
|
||||
vqt6_graph3d_set_edge_weights(self._ptr, w, n)
|
||||
|
||||
def setEdgeThreshold(self, tval: t.CDouble):
|
||||
vqt6_graph3d_set_edge_threshold(self._ptr, tval)
|
||||
|
||||
def setEdgeBudget(self, max_edges: int):
|
||||
vqt6_graph3d_set_edge_budget(self._ptr, max_edges)
|
||||
|
||||
def setView(self, yaw_deg: t.CDouble, pitch_deg: t.CDouble, dist: t.CDouble):
|
||||
vqt6_graph3d_set_view(self._ptr, yaw_deg, pitch_deg, dist)
|
||||
|
||||
def setNodeScale(self, s: t.CDouble):
|
||||
vqt6_graph3d_set_node_scale(self._ptr, s)
|
||||
|
||||
def setBackground(self, r: int, g: int, b: int):
|
||||
vqt6_graph3d_set_bg(self._ptr, r, g, b)
|
||||
|
||||
def resetView(self):
|
||||
vqt6_graph3d_reset_view(self._ptr)
|
||||
|
||||
def view(self, yaw_deg: t.CDouble | t.CPtr, pitch_deg: t.CDouble | t.CPtr,
|
||||
dist: t.CDouble | t.CPtr):
|
||||
vqt6_graph3d_get_view(self._ptr, yaw_deg, pitch_deg, dist)
|
||||
|
||||
def viewChanged(self, cb: bytes, data: bytes = 0):
|
||||
vqt6_graph3d_connect_view_changed(self._ptr, cb, data)
|
||||
|
||||
def show(self):
|
||||
_w_show(self._ptr)
|
||||
|
||||
def hide(self):
|
||||
_w_hide(self._ptr)
|
||||
|
||||
def setGeometry(self, x: int, y: int, w: int, h: int):
|
||||
_w_set_geometry(self._ptr, x, y, w, h)
|
||||
|
||||
def resize(self, w: int, h: int):
|
||||
_w_resize(self._ptr, w, h)
|
||||
|
||||
def move(self, x: int, y: int):
|
||||
_w_move(self._ptr, x, y)
|
||||
|
||||
def setEnabled(self, enabled: int):
|
||||
_w_set_enabled(self._ptr, enabled)
|
||||
|
||||
|
||||
@t.NoVTable
|
||||
class ChartView:
|
||||
"""Qt Charts 折线图控件(右侧 Y 轴可选)
|
||||
|
||||
典型用法:
|
||||
cv = ChartView()
|
||||
loss = cv.addLine("loss", 255, 107, 107, 0)
|
||||
acc = cv.addLine("acc", 77, 171, 247, 1)
|
||||
cv.append(loss, step, loss_value)
|
||||
cv.autoscale()
|
||||
"""
|
||||
_ptr: QtChartViewPtr
|
||||
|
||||
def __new__(self, parent: "QWidget | None" = None) -> t.CPtr:
|
||||
return stdlib.malloc(8) # 包装壳堆分配, 见本文件顶部说明
|
||||
|
||||
def __init__(self, parent: "QWidget | None" = None):
|
||||
if parent is not None and parent._ptr is not None:
|
||||
self._ptr = vqt6_chartview_create_with_parent(parent._ptr)
|
||||
else:
|
||||
self._ptr = vqt6_chartview_create()
|
||||
if self._ptr is None:
|
||||
raise RuntimeError("Failed to create ChartView")
|
||||
|
||||
def addLine(self, name: str, r: int, g: int, b: int, right_axis: int = 0) -> QtChartSeriesPtr:
|
||||
return vqt6_chartview_add_line(self._ptr, name, r, g, b, right_axis)
|
||||
|
||||
def append(self, series: QtChartSeriesPtr, x: t.CDouble, y: t.CDouble):
|
||||
vqt6_chartview_append(self._ptr, series, x, y)
|
||||
|
||||
def clearSeries(self, series: QtChartSeriesPtr):
|
||||
vqt6_chartview_clear_series(self._ptr, series)
|
||||
|
||||
def clearAll(self):
|
||||
vqt6_chartview_clear_all(self._ptr)
|
||||
|
||||
def autoscale(self):
|
||||
vqt6_chartview_autoscale(self._ptr)
|
||||
|
||||
def setTitle(self, title: str):
|
||||
vqt6_chartview_set_title(self._ptr, title)
|
||||
|
||||
def setLegendVisible(self, visible: int):
|
||||
vqt6_chartview_set_legend_visible(self._ptr, visible)
|
||||
|
||||
def setBackground(self, r: int, g: int, b: int):
|
||||
vqt6_chartview_set_background(self._ptr, r, g, b)
|
||||
|
||||
def setMaxPoints(self, max_points: int):
|
||||
"""滑动窗口: 每条折线超过 max_points 就丢弃最旧的点(<=0 表示不限制)"""
|
||||
vqt6_chartview_set_max_points(self._ptr, max_points)
|
||||
|
||||
def openZoom(self, title: str):
|
||||
"""打开(或复用)大窗查看当前曲线; 关闭只是隐藏, 不销毁"""
|
||||
vqt6_chartview_open_zoom(self._ptr, title)
|
||||
|
||||
def syncZoom(self):
|
||||
"""把当前曲线与轴范围同步到大窗(大窗未打开时什么也不做)"""
|
||||
vqt6_chartview_sync_zoom(self._ptr)
|
||||
|
||||
def show(self):
|
||||
_w_show(self._ptr)
|
||||
|
||||
def hide(self):
|
||||
_w_hide(self._ptr)
|
||||
|
||||
def setGeometry(self, x: int, y: int, w: int, h: int):
|
||||
_w_set_geometry(self._ptr, x, y, w, h)
|
||||
|
||||
def resize(self, w: int, h: int):
|
||||
_w_resize(self._ptr, w, h)
|
||||
|
||||
def move(self, x: int, y: int):
|
||||
_w_move(self._ptr, x, y)
|
||||
|
||||
def setEnabled(self, enabled: int):
|
||||
_w_set_enabled(self._ptr, enabled)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 模块导出
|
||||
# ============================================================
|
||||
@@ -1814,11 +2157,16 @@ __all__ = [
|
||||
"QMessageBox", "QFileDialog", "QTimer",
|
||||
"QMenuBar", "QMenu", "QAction", "QStatusBar",
|
||||
"QColor", "QFont",
|
||||
# 自绘控件
|
||||
"Canvas", "Graph3D", "ChartView",
|
||||
"clipboard_set_text", "clipboard_text",
|
||||
# 类型
|
||||
"QtObjectPtr", "QtStringPtr", "QtByteArrayPtr", "QtWidgetPtr", "QtAppPtr",
|
||||
"QtLabelPtr", "QtPushButtonPtr",
|
||||
"QtTreeWidgetPtr", "QtTreeWidgetItemPtr",
|
||||
"QtColorPtr", "QtFontPtr", "QtPixmapPtr", "QtImagePtr", "QtPainterPtr",
|
||||
"QtMenuBarPtr", "QtMenuPtr", "QtActionPtr", "QtStatusBarPtr",
|
||||
"QtCanvasPtr", "QtGraph3DPtr", "QtChartViewPtr", "QtChartSeriesPtr",
|
||||
"QtTcpSocketPtr", "QtHostAddressPtr", "QtNetworkRequestPtr", "QtNetworkReplyPtr",
|
||||
# 常量
|
||||
"QT_KEY_ESCAPE", "QT_KEY_RETURN", "QT_KEY_A", "QT_KEY_Z",
|
||||
|
||||
@@ -153,6 +153,13 @@ VQT6_API void vqt6_signal_unregister(int slot_id);
|
||||
// ============================================================
|
||||
VQT6_API void vqt6_qtimer_connect_timeout(void* timer, vqt6_slot_callback_t cb, void* user_data);
|
||||
|
||||
// ============================================================
|
||||
// 剪贴板(复制链接等)
|
||||
// ============================================================
|
||||
VQT6_API void vqt6_clipboard_set_text_utf8(const char* utf8);
|
||||
// 读取剪贴板文本到 buf(UTF-8); 返回写入字节数(不含结尾 NUL)
|
||||
VQT6_API int vqt6_clipboard_text_utf8(char* buf, int buf_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,99 @@
|
||||
// ============================================================
|
||||
// vqt6/_bridge_canvas.h — 自绘控件 C 桥接声明
|
||||
//
|
||||
// 三个自绘控件(不依赖 Q_OBJECT/moc, 回调直接走 vqt6_trigger_slot):
|
||||
// * Canvas — 手绘板 / 28x28 灰度图显示(带扫描线、网格)
|
||||
// * Graph3D — 软件 3D 神经网络结构图(QPainter 透视投影)
|
||||
// * ChartView — Qt Charts 折线图(loss/acc 曲线)
|
||||
//
|
||||
// 设计约定: Graph3D 的 API 与渲染后端无关(只暴露
|
||||
// setup/set_values/set_edge_weights/set_layer_alpha/set_view),
|
||||
// 将来若换成 OpenGL 后端, Viper 侧调用代码无需改动。
|
||||
// ============================================================
|
||||
|
||||
#ifndef VQT6_BRIDGE_CANVAS_H
|
||||
#define VQT6_BRIDGE_CANVAS_H
|
||||
|
||||
#include "_bridge.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// ============================================================
|
||||
// Canvas — 手绘板 / 28x28 灰度显示
|
||||
// 坐标系约定: 内部统一用 0..1 归一化坐标, 与控件像素尺寸解耦
|
||||
// ============================================================
|
||||
VQT6_API void* vqt6_canvas_create(void);
|
||||
VQT6_API void* vqt6_canvas_create_with_parent(void* parent);
|
||||
VQT6_API void vqt6_canvas_clear(void* canvas);
|
||||
VQT6_API int vqt6_canvas_has_ink(void* canvas);
|
||||
VQT6_API void vqt6_canvas_set_mode(void* canvas, int mode); // 0=手绘 1=图像显示
|
||||
VQT6_API int vqt6_canvas_mode(void* canvas);
|
||||
VQT6_API void vqt6_canvas_set_pen(void* canvas, int r, int g, int b, double width_norm);
|
||||
VQT6_API void vqt6_canvas_set_grid(void* canvas, int on); // 28x28 辅助网格
|
||||
VQT6_API void vqt6_canvas_set_scan_line(void* canvas, int y_norm); // 0..1000, <0=关
|
||||
VQT6_API void vqt6_canvas_set_bg(void* canvas, int r, int g, int b);
|
||||
// 手绘内容 → 28x28 灰度(MNIST 预处理: 包围盒缩放到 20x20 后按质心居中); 无墨迹返回 0
|
||||
VQT6_API int vqt6_canvas_to_mnist28(void* canvas, unsigned char* out);
|
||||
// 28x28 灰度 → 显示(自动切到图像显示模式)
|
||||
VQT6_API void vqt6_canvas_set_image28(void* canvas, const unsigned char* in);
|
||||
// 笔迹完成(鼠标抬起)回调
|
||||
VQT6_API void vqt6_canvas_connect_changed(void* canvas, vqt6_slot_callback_t cb, void* user_data);
|
||||
|
||||
// ============================================================
|
||||
// Graph3D — 软件 3D 神经网络结构图
|
||||
// 层布局: 每层按近正方形网格排在 XY 平面, 各层沿 Z 轴依次排列
|
||||
// ============================================================
|
||||
VQT6_API void* vqt6_graph3d_create(void);
|
||||
VQT6_API void* vqt6_graph3d_create_with_parent(void* parent);
|
||||
// layer_sizes: 自输入层到输出层各层节点数; 建立节点与前向全连接边
|
||||
VQT6_API void vqt6_graph3d_setup(void* g, const int* layer_sizes, int n_layers);
|
||||
VQT6_API void vqt6_graph3d_clear(void* g);
|
||||
VQT6_API int vqt6_graph3d_node_count(void* g);
|
||||
VQT6_API int vqt6_graph3d_edge_count(void* g);
|
||||
// 逐层点亮: alpha 0..1(节点与相邻边同时生效)
|
||||
VQT6_API void vqt6_graph3d_set_layer_alpha(void* g, int layer, double alpha);
|
||||
VQT6_API void vqt6_graph3d_set_all_layer_alpha(void* g, double alpha);
|
||||
// 每节点激活值(长度 = node_count), 取值 0..1, 影响节点亮度与半径
|
||||
VQT6_API void vqt6_graph3d_set_values(void* g, const double* values, int n);
|
||||
// 边权重(长度 = edge_count, 按层间顺序), 影响颜色/透明度, 并按 |w| 裁剪
|
||||
VQT6_API void vqt6_graph3d_set_edge_weights(void* g, const double* w, int n);
|
||||
VQT6_API void vqt6_graph3d_set_edge_threshold(void* g, double t); // |w|归一后低于此值不画
|
||||
VQT6_API void vqt6_graph3d_set_edge_budget(void* g, int max_edges); // 最多画多少条(取 |w| 最大)
|
||||
VQT6_API void vqt6_graph3d_set_view(void* g, double yaw_deg, double pitch_deg, double dist);
|
||||
VQT6_API void vqt6_graph3d_get_view(void* g, double* yaw_deg, double* pitch_deg, double* dist);
|
||||
VQT6_API void vqt6_graph3d_set_bg(void* g, int r, int gcol, int b);
|
||||
VQT6_API void vqt6_graph3d_set_node_scale(void* g, double s);
|
||||
VQT6_API void vqt6_graph3d_connect_view_changed(void* g, vqt6_slot_callback_t cb, void* user_data);
|
||||
// 原点复位
|
||||
VQT6_API void vqt6_graph3d_reset_view(void* g);
|
||||
|
||||
// ============================================================
|
||||
// ChartView — Qt Charts 折线图(loss/acc)
|
||||
// ============================================================
|
||||
VQT6_API void* vqt6_chartview_create(void);
|
||||
VQT6_API void* vqt6_chartview_create_with_parent(void* parent);
|
||||
// 新增一条折线, 返回 series 句柄; right_axis!=0 时挂到右侧 Y 轴
|
||||
VQT6_API void* vqt6_chartview_add_line(void* view, const char* name,
|
||||
int r, int g, int b, int right_axis);
|
||||
VQT6_API void vqt6_chartview_append(void* view, void* series, double x, double y);
|
||||
VQT6_API void vqt6_chartview_clear_series(void* view, void* series);
|
||||
VQT6_API void vqt6_chartview_clear_all(void* view);
|
||||
// 按当前数据重算坐标轴范围(左轴/右轴各自独立)
|
||||
VQT6_API void vqt6_chartview_autoscale(void* view);
|
||||
VQT6_API void vqt6_chartview_set_title(void* view, const char* utf8);
|
||||
VQT6_API void vqt6_chartview_set_legend_visible(void* view, int visible);
|
||||
VQT6_API void vqt6_chartview_set_background(void* view, int r, int g, int b);
|
||||
// 滑动窗口: 每条折线超过 max_points 时丢弃最旧的点(max_points<=0 表示不限制)
|
||||
VQT6_API void vqt6_chartview_set_max_points(void* view, int max_points);
|
||||
// 打开(或复用)只读大窗; 同一 view 只建一次, 关闭仅隐藏不销毁
|
||||
VQT6_API void* vqt6_chartview_open_zoom(void* view, const char* title);
|
||||
// 把源图当前曲线与轴范围同步到大窗(仅在大窗可见时生效)
|
||||
VQT6_API void vqt6_chartview_sync_zoom(void* view);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // VQT6_BRIDGE_CANVAS_H
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <QByteArray>
|
||||
#include <QObject>
|
||||
#include <QCoreApplication>
|
||||
#include <QGuiApplication>
|
||||
#include <QClipboard>
|
||||
#include <QTimer>
|
||||
#include <QVariant>
|
||||
#include <QEvent>
|
||||
@@ -477,3 +479,27 @@ extern "C" VQT6_API void vqt6_qtimer_connect_timeout(void* timer, vqt6_slot_call
|
||||
vqt6_trigger_slot(slot_id);
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 剪贴板
|
||||
// ============================================================
|
||||
extern "C" VQT6_API void vqt6_clipboard_set_text_utf8(const char* utf8) {
|
||||
QClipboard* cb = QGuiApplication::clipboard();
|
||||
if (cb == nullptr || utf8 == nullptr) return;
|
||||
cb->setText(QString::fromUtf8(utf8));
|
||||
}
|
||||
|
||||
extern "C" VQT6_API int vqt6_clipboard_text_utf8(char* buf, int buf_size) {
|
||||
if (buf == nullptr || buf_size <= 0) return 0;
|
||||
QClipboard* cb = QGuiApplication::clipboard();
|
||||
if (cb == nullptr) {
|
||||
buf[0] = '\0';
|
||||
return 0;
|
||||
}
|
||||
QByteArray u = cb->text().toUtf8();
|
||||
int n = static_cast<int>(u.size());
|
||||
if (n > buf_size - 1) n = buf_size - 1;
|
||||
std::memcpy(buf, u.constData(), static_cast<size_t>(n));
|
||||
buf[n] = '\0';
|
||||
return n;
|
||||
}
|
||||
+133
-3
@@ -171,6 +171,19 @@ extern "C" VQT6_API int vqt6_qapplication_quit_on_last_window_closed(void* app)
|
||||
return QApplication::quitOnLastWindowClosed() ? 1 : 0;
|
||||
}
|
||||
|
||||
extern "C" VQT6_API void vqt6_qapplication_set_font_family_utf8(void* app, const char* family) {
|
||||
if (family == nullptr) return;
|
||||
QApplication* a = (app != nullptr) ? static_cast<QApplication*>(app)
|
||||
: static_cast<QApplication*>(QApplication::instance());
|
||||
if (a == nullptr) return;
|
||||
// 只换字体族:从当前应用字体克隆,字号/字重/斜体等全部保持不变。
|
||||
// 不能直接 QFont(family) —— 那个构造把 pointSize 置为 -1,实际字号会被
|
||||
// 平台默认值改写,整个界面的字号会跟着变。
|
||||
QFont f = QApplication::font();
|
||||
f.setFamily(QString::fromUtf8(family));
|
||||
QApplication::setFont(f);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// QWidget
|
||||
// ============================================================
|
||||
@@ -1167,18 +1180,135 @@ extern "C" VQT6_API void vqt6_enable_custom_item_checks(void* app) {
|
||||
a->setStyle(new vqt6_CheckProxyStyle());
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 自绘 QRadioButton:圆形指示器(圆环 + 内点)
|
||||
// 与自绘 QCheckBox 同源:同样的 QPalette 取色、同样的 16ms 指数衰减过渡
|
||||
// (参考 ViperQtProject 里 vqt6_CheckBox / vqt6_CheckProxyStyle 的做法)。
|
||||
// ------------------------------------------------------------------
|
||||
// 圆形的绘制区域:在 r 内取居中的正方形(半径一致才是个正圆)
|
||||
static QRectF vqt6_radio_box(const QRect& r) {
|
||||
qreal side = qMin(r.width(), r.height()) - 2.0;
|
||||
if (side < 10.0) side = 14.0;
|
||||
QRectF box(0, 0, side, side);
|
||||
box.moveCenter(QRectF(r).center());
|
||||
return box;
|
||||
}
|
||||
|
||||
// 统一的单选指示器绘制:圆环 + 随进度长大的内点
|
||||
// box —— 绘制区域(正方形)
|
||||
// pal —— 控件调色板(决定深浅主题配色)
|
||||
// enabled —— 是否可用
|
||||
// progress —— 选中过渡进度(0=未选 1=选中)
|
||||
static void vqt6_paint_radio_indicator(QPainter* p, const QRectF& box, const QPalette& pal,
|
||||
bool enabled, qreal progress) {
|
||||
p->setRenderHint(QPainter::Antialiasing, true);
|
||||
const qreal t = progress < 0.0 ? 0.0 : (progress > 1.0 ? 1.0 : progress);
|
||||
const QColor cBase = enabled ? pal.base().color() : pal.window().color();
|
||||
const QColor cOn = enabled ? pal.highlight().color() : pal.button().color();
|
||||
const QColor cB0 = enabled ? pal.text().color() : pal.placeholderText().color();
|
||||
const QColor cB1 = enabled ? pal.highlight().color() : pal.buttonText().color();
|
||||
const QColor fill = vqt6_mix_color(cBase, cOn, t);
|
||||
const QColor border = vqt6_mix_color(cB0, cB1, t);
|
||||
|
||||
QPainterPath path;
|
||||
path.addEllipse(box);
|
||||
p->fillPath(path, fill);
|
||||
QPen bpen(border, 1.4);
|
||||
p->setPen(bpen);
|
||||
p->drawEllipse(box);
|
||||
|
||||
if (!enabled || t <= 0.003) return;
|
||||
// 内点:半径从 0 涨到 0.32*边长,颜色取 highlightedText 并按进度淡入
|
||||
QColor gc = pal.highlightedText().color();
|
||||
gc.setAlphaF(gc.alphaF() * t);
|
||||
const qreal side = qMin(box.width(), box.height());
|
||||
const qreal rd = side * 0.32 * t;
|
||||
p->setPen(Qt::NoPen);
|
||||
p->setBrush(gc);
|
||||
p->drawEllipse(box.center(), rd, rd);
|
||||
}
|
||||
|
||||
class vqt6_RadioButton : public QRadioButton {
|
||||
public:
|
||||
vqt6_RadioButton(QWidget* parent = nullptr) : QRadioButton(parent) {
|
||||
initAnim();
|
||||
}
|
||||
vqt6_RadioButton(const QString& text, QWidget* parent = nullptr)
|
||||
: QRadioButton(text, parent) {
|
||||
initAnim();
|
||||
}
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent*) override {
|
||||
QPainter p(this);
|
||||
p.setRenderHint(QPainter::Antialiasing, true);
|
||||
const QPalette& pal = palette();
|
||||
QStyleOptionButton opt;
|
||||
initStyleOption(&opt);
|
||||
QRect r = style()->subElementRect(QStyle::SE_RadioButtonIndicator, &opt, this);
|
||||
if (r.isEmpty() || r.isNull()) {
|
||||
r = QRect(0, 0, 16, 16);
|
||||
r.moveCenter(QPoint(rect().left() + 12, rect().center().y()));
|
||||
}
|
||||
// 圆环 + 内点:与自绘复选框共用配色与动画进度
|
||||
vqt6_paint_radio_indicator(&p, vqt6_radio_box(r), pal, isEnabled(), m_progress);
|
||||
// 文本
|
||||
QRect lr = style()->subElementRect(QStyle::SE_RadioButtonContents, &opt, this);
|
||||
if (!lr.isEmpty() && !lr.isNull()) {
|
||||
p.setPen(QPen(isEnabled() ? pal.text() : pal.placeholderText(), 1.0));
|
||||
p.drawText(lr, Qt::AlignLeft | Qt::AlignVCenter, text());
|
||||
}
|
||||
}
|
||||
|
||||
void showEvent(QShowEvent* e) override {
|
||||
snapToState();
|
||||
QRadioButton::showEvent(e);
|
||||
}
|
||||
|
||||
private:
|
||||
QTimer m_timer;
|
||||
qreal m_progress = 0.0;
|
||||
qreal m_target = 0.0;
|
||||
|
||||
void initAnim() {
|
||||
m_timer.setInterval(16);
|
||||
QObject::connect(&m_timer, &QTimer::timeout, this, [this]() {
|
||||
m_progress += (m_target - m_progress) * 0.25;
|
||||
if (qAbs(m_target - m_progress) < 0.005) {
|
||||
m_progress = m_target;
|
||||
m_timer.stop();
|
||||
}
|
||||
update();
|
||||
});
|
||||
QObject::connect(this, &QRadioButton::toggled, this, [this](bool on) {
|
||||
m_target = on ? 1.0 : 0.0;
|
||||
if (!isVisible()) {
|
||||
m_progress = m_target;
|
||||
} else if (!m_timer.isActive()) {
|
||||
m_timer.start();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void snapToState() {
|
||||
m_target = isChecked() ? 1.0 : 0.0;
|
||||
m_progress = m_target;
|
||||
m_timer.stop();
|
||||
}
|
||||
};
|
||||
|
||||
extern "C" VQT6_API void* vqt6_qradiobutton_create(void) {
|
||||
return new QRadioButton();
|
||||
return new vqt6_RadioButton();
|
||||
}
|
||||
|
||||
extern "C" VQT6_API void* vqt6_qradiobutton_create_with_text(void* text) {
|
||||
if (text == nullptr) return nullptr;
|
||||
return new QRadioButton(*static_cast<QString*>(text));
|
||||
return new vqt6_RadioButton(*static_cast<QString*>(text));
|
||||
}
|
||||
|
||||
extern "C" VQT6_API void* vqt6_qradiobutton_create_with_text_parent(void* text, void* parent) {
|
||||
if (text == nullptr || parent == nullptr) return nullptr;
|
||||
return new QRadioButton(*static_cast<QString*>(text), static_cast<QWidget*>(parent));
|
||||
return new vqt6_RadioButton(*static_cast<QString*>(text), static_cast<QWidget*>(parent));
|
||||
}
|
||||
|
||||
extern "C" VQT6_API void vqt6_qradiobutton_set_checked(void* rb, int checked) {
|
||||
|
||||
@@ -25,6 +25,7 @@ VQT6_API int vqt6_qapplication_desktop_height(void* app);
|
||||
VQT6_API void vqt6_qapplication_set_quit_on_last_window_closed(void* app, int quit);
|
||||
VQT6_API int vqt6_qapplication_quit_on_last_window_closed(void* app);
|
||||
VQT6_API void vqt6_qapplication_set_theme_palette(void* app, int theme);
|
||||
VQT6_API void vqt6_qapplication_set_font_family_utf8(void* app, const char* family);
|
||||
VQT6_API void vqt6_enable_custom_item_checks(void* app);
|
||||
|
||||
// ============================================================
|
||||
|
||||
@@ -158,3 +158,10 @@ def vqt6_qbytearraylist_free(list: QtByteArrayListPtr) -> t.State: pass
|
||||
def vqt6_register_slot(slot_id: t.CInt, user_data: t.CVoid | t.CPtr) -> t.State: pass
|
||||
def vqt6_unregister_slot(slot_id: t.CInt) -> t.State: pass
|
||||
def vqt6_trigger_slot(slot_id: t.CInt) -> t.State: pass # 测试用
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 剪贴板 — 复制链接等
|
||||
# ============================================================
|
||||
def vqt6_clipboard_set_text_utf8(text: t.CChar | t.CPtr) -> t.State: pass
|
||||
def vqt6_clipboard_text_utf8(buf: t.CChar | t.CPtr, buf_size: t.CInt) -> t.CInt | t.State: pass
|
||||
@@ -21,6 +21,7 @@ def vqt6_qapplication_desktop_height(app: QtAppPtr) -> t.CInt | t.State: pass
|
||||
def vqt6_qapplication_set_quit_on_last_window_closed(app: QtAppPtr, quit: t.CInt) -> t.State: pass
|
||||
def vqt6_qapplication_quit_on_last_window_closed(app: QtAppPtr) -> t.CInt | t.State: pass
|
||||
def vqt6_qapplication_set_theme_palette(app: QtAppPtr, theme: t.CInt) -> t.State: pass
|
||||
def vqt6_qapplication_set_font_family_utf8(app: QtAppPtr, family: t.CChar | t.CPtr) -> t.State: pass
|
||||
def vqt6_enable_custom_item_checks(app: QtAppPtr) -> t.State: pass
|
||||
|
||||
|
||||
@@ -546,3 +547,64 @@ def vqt6_qtabwidget_connect_current_changed(tw: QtTabWidgetPtr, cb: bytes, user_
|
||||
def vqt6_qaction_connect_triggered(action: QtActionPtr, cb: bytes, user_data: bytes) -> t.State: pass
|
||||
def vqt6_qtreewidget_connect_item_changed(tw: QtTreeWidgetPtr, cb: bytes, user_data: bytes) -> t.State: pass
|
||||
def vqt6_qtreewidget_connect_item_clicked(tw: QtTreeWidgetPtr, cb: bytes, user_data: bytes) -> t.State: pass
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Canvas — 手绘板 / 28x28 灰度显示(自绘控件)
|
||||
# 坐标约定: 内部 0..1 归一化, 与控件像素尺寸解耦
|
||||
# ============================================================
|
||||
def vqt6_canvas_create() -> QtCanvasPtr | t.State: pass
|
||||
def vqt6_canvas_create_with_parent(parent: QtWidgetPtr) -> QtCanvasPtr | t.State: pass
|
||||
def vqt6_canvas_clear(canvas: QtCanvasPtr) -> t.State: pass
|
||||
def vqt6_canvas_has_ink(canvas: QtCanvasPtr) -> t.CInt | t.State: pass
|
||||
def vqt6_canvas_set_mode(canvas: QtCanvasPtr, mode: t.CInt) -> t.State: pass
|
||||
def vqt6_canvas_mode(canvas: QtCanvasPtr) -> t.CInt | t.State: pass
|
||||
def vqt6_canvas_set_pen(canvas: QtCanvasPtr, r: t.CInt, g: t.CInt, b: t.CInt, width_norm: t.CDouble) -> t.State: pass
|
||||
def vqt6_canvas_set_grid(canvas: QtCanvasPtr, on: t.CInt) -> t.State: pass
|
||||
def vqt6_canvas_set_scan_line(canvas: QtCanvasPtr, y_norm: t.CInt) -> t.State: pass
|
||||
def vqt6_canvas_set_bg(canvas: QtCanvasPtr, r: t.CInt, g: t.CInt, b: t.CInt) -> t.State: pass
|
||||
def vqt6_canvas_to_mnist28(canvas: QtCanvasPtr, out: t.CUnsignedChar | t.CPtr) -> t.CInt | t.State: pass
|
||||
def vqt6_canvas_set_image28(canvas: QtCanvasPtr, inp: t.CUnsignedChar | t.CPtr) -> t.State: pass
|
||||
def vqt6_canvas_connect_changed(canvas: QtCanvasPtr, cb: bytes, user_data: bytes) -> t.State: pass
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Graph3D — 软件 3D 神经网络结构图(自绘控件)
|
||||
# API 与渲染后端无关, 后续可替换为 OpenGL 实现
|
||||
# ============================================================
|
||||
def vqt6_graph3d_create() -> QtGraph3DPtr | t.State: pass
|
||||
def vqt6_graph3d_create_with_parent(parent: QtWidgetPtr) -> QtGraph3DPtr | t.State: pass
|
||||
def vqt6_graph3d_setup(g: QtGraph3DPtr, layer_sizes: t.CInt | t.CPtr, n_layers: t.CInt) -> t.State: pass
|
||||
def vqt6_graph3d_clear(g: QtGraph3DPtr) -> t.State: pass
|
||||
def vqt6_graph3d_node_count(g: QtGraph3DPtr) -> t.CInt | t.State: pass
|
||||
def vqt6_graph3d_edge_count(g: QtGraph3DPtr) -> t.CInt | t.State: pass
|
||||
def vqt6_graph3d_set_layer_alpha(g: QtGraph3DPtr, layer: t.CInt, alpha: t.CDouble) -> t.State: pass
|
||||
def vqt6_graph3d_set_all_layer_alpha(g: QtGraph3DPtr, alpha: t.CDouble) -> t.State: pass
|
||||
def vqt6_graph3d_set_values(g: QtGraph3DPtr, values: t.CDouble | t.CPtr, n: t.CInt) -> t.State: pass
|
||||
def vqt6_graph3d_set_edge_weights(g: QtGraph3DPtr, w: t.CDouble | t.CPtr, n: t.CInt) -> t.State: pass
|
||||
def vqt6_graph3d_set_edge_threshold(g: QtGraph3DPtr, tval: t.CDouble) -> t.State: pass
|
||||
def vqt6_graph3d_set_edge_budget(g: QtGraph3DPtr, max_edges: t.CInt) -> t.State: pass
|
||||
def vqt6_graph3d_set_view(g: QtGraph3DPtr, yaw_deg: t.CDouble, pitch_deg: t.CDouble, dist: t.CDouble) -> t.State: pass
|
||||
def vqt6_graph3d_get_view(g: QtGraph3DPtr, yaw_deg: t.CDouble | t.CPtr, pitch_deg: t.CDouble | t.CPtr, dist: t.CDouble | t.CPtr) -> t.State: pass
|
||||
def vqt6_graph3d_set_bg(g: QtGraph3DPtr, r: t.CInt, gcol: t.CInt, b: t.CInt) -> t.State: pass
|
||||
def vqt6_graph3d_set_node_scale(g: QtGraph3DPtr, s: t.CDouble) -> t.State: pass
|
||||
def vqt6_graph3d_connect_view_changed(g: QtGraph3DPtr, cb: bytes, user_data: bytes) -> t.State: pass
|
||||
def vqt6_graph3d_reset_view(g: QtGraph3DPtr) -> t.State: pass
|
||||
|
||||
|
||||
# ============================================================
|
||||
# ChartView — Qt Charts 折线图(loss/acc 曲线)
|
||||
# ============================================================
|
||||
def vqt6_chartview_create() -> QtChartViewPtr | t.State: pass
|
||||
def vqt6_chartview_create_with_parent(parent: QtWidgetPtr) -> QtChartViewPtr | t.State: pass
|
||||
def vqt6_chartview_add_line(view: QtChartViewPtr, name: t.CChar | t.CPtr, r: t.CInt, g: t.CInt, b: t.CInt, right_axis: t.CInt) -> QtChartSeriesPtr | t.State: pass
|
||||
def vqt6_chartview_append(view: QtChartViewPtr, series: QtChartSeriesPtr, x: t.CDouble, y: t.CDouble) -> t.State: pass
|
||||
def vqt6_chartview_clear_series(view: QtChartViewPtr, series: QtChartSeriesPtr) -> t.State: pass
|
||||
def vqt6_chartview_clear_all(view: QtChartViewPtr) -> t.State: pass
|
||||
def vqt6_chartview_autoscale(view: QtChartViewPtr) -> t.State: pass
|
||||
def vqt6_chartview_set_title(view: QtChartViewPtr, title: t.CChar | t.CPtr) -> t.State: pass
|
||||
def vqt6_chartview_set_legend_visible(view: QtChartViewPtr, visible: t.CInt) -> t.State: pass
|
||||
def vqt6_chartview_set_background(view: QtChartViewPtr, r: t.CInt, g: t.CInt, b: t.CInt) -> t.State: pass
|
||||
def vqt6_chartview_set_max_points(view: QtChartViewPtr, max_points: t.CInt) -> t.State: pass
|
||||
def vqt6_chartview_open_zoom(view: QtChartViewPtr, title: t.CChar | t.CPtr) -> QtWidgetPtr | t.State: pass
|
||||
def vqt6_chartview_sync_zoom(view: QtChartViewPtr) -> t.State: pass
|
||||
@@ -80,6 +80,12 @@ QtColorDialogPtr: t.CTypedef = t.CVoid | t.CPtr # QColorDialog*
|
||||
QtFontDialogPtr: t.CTypedef = t.CVoid | t.CPtr # QFontDialog*
|
||||
QtInputDialogPtr: t.CTypedef = t.CVoid | t.CPtr # QInputDialog*
|
||||
|
||||
# vqt6 自绘控件(_bridge_canvas)
|
||||
QtCanvasPtr: t.CTypedef = t.CVoid | t.CPtr # Vqt6Canvas*(QWidget* 兼容)
|
||||
QtGraph3DPtr: t.CTypedef = t.CVoid | t.CPtr # Vqt6Graph3D*(QWidget* 兼容)
|
||||
QtChartViewPtr: t.CTypedef = t.CVoid | t.CPtr # QChartView*
|
||||
QtChartSeriesPtr: t.CTypedef = t.CVoid | t.CPtr # QLineSeries*
|
||||
|
||||
# QtNetwork
|
||||
QtTcpSocketPtr: t.CTypedef = t.CVoid | t.CPtr # QTcpSocket*
|
||||
QtUdpSocketPtr: t.CTypedef = t.CVoid | t.CPtr # QUdpSocket*
|
||||
|
||||
+50
-13
@@ -1,6 +1,7 @@
|
||||
import t, c
|
||||
from stdint import *
|
||||
import stdio
|
||||
import stdlib
|
||||
import w32.win32base
|
||||
import w32.win32file
|
||||
|
||||
@@ -36,6 +37,26 @@ SHARE_WRITE: t.CDefine = 0x00000002
|
||||
SHARE_DELETE: t.CDefine = 0x00000004
|
||||
|
||||
|
||||
def create_file_utf8(path: str, access: ULONG, share: ULONG, disposition: ULONG,
|
||||
flags: ULONG) -> w32.win32base.HANDLE:
|
||||
"""按路径编码打开/创建文件。
|
||||
合法 UTF-8(源码字面量、Qt 返回的中文目录) → 转 UTF-16 走 CreateFileW;
|
||||
非法 UTF-8(如命令行 argv 的 ANSI 串) → 退回 CreateFileA。详见 win32base"""
|
||||
if w32.win32base.utf8_is_valid(path) == 0:
|
||||
return w32.win32file.CreateFileA(path, access, share, None, disposition,
|
||||
flags, None)
|
||||
wp: w32.win32base.WCHARPTR = w32.win32base.utf8_to_utf16_dup(path)
|
||||
if wp is None:
|
||||
# 宽字符缓冲分配失败: 走 A 路径, 非 ASCII 路径在此自然失败并返回
|
||||
# INVALID_HANDLE_VALUE(不掩盖错误, 只是复用同一失败出口)
|
||||
return w32.win32file.CreateFileA(path, access, share, None, disposition,
|
||||
flags, None)
|
||||
h: w32.win32base.HANDLE = w32.win32file.CreateFileW(
|
||||
t.CPtr(wp), access, share, None, disposition, flags, None)
|
||||
stdlib.free(wp)
|
||||
return h
|
||||
|
||||
|
||||
class File:
|
||||
handle: w32.win32base.HANDLE
|
||||
closed: bool
|
||||
@@ -97,9 +118,9 @@ class File:
|
||||
disposition = w32.win32file.OPEN_EXISTING
|
||||
self.can_read = True
|
||||
|
||||
self.handle = w32.win32file.CreateFileA(
|
||||
filename, access, self._share_mode,
|
||||
None, disposition, w32.win32file.FILE_ATTRIBUTE_NORMAL, None
|
||||
self.handle = create_file_utf8(
|
||||
filename, access, self._share_mode, disposition,
|
||||
w32.win32file.FILE_ATTRIBUTE_NORMAL
|
||||
)
|
||||
|
||||
if self.handle == w32.win32base.INVALID_HANDLE_VALUE:
|
||||
@@ -189,22 +210,30 @@ class File:
|
||||
buf[total] = 0
|
||||
return LONG(total)
|
||||
|
||||
def read_all(self, buf: bytes, max_count: ULONG) -> LONG:
|
||||
def read_all(self, buf: bytes, buf_size: ULONG) -> LONG:
|
||||
"""把整个文件读进 buf 并写成 C 字符串(内部先 seek 到 0)
|
||||
|
||||
契约(注意与 write 的 count 口径不同):
|
||||
buf_size 是缓冲区**容量, 含终止符** —— 最多读 buf_size-1 字节,
|
||||
再在 buf[返回值] 写一个 NUL 字节, 故返回值 <= buf_size-1。
|
||||
文件比 buf_size-1 更长时**静默截断**, 不报错; 要区分"读完了"还是
|
||||
"被截断了", 请在调用前自己比一次 f.size()。
|
||||
返回实际读到的字节数, 负数为错误码。"""
|
||||
if self.closed: return FRESULT.ERR_CLOSED
|
||||
if not self.can_read: return FRESULT.ERR_PERM
|
||||
if max_count < 2: return FRESULT.ERR
|
||||
if buf_size < 2: return FRESULT.ERR
|
||||
file_size: LONGLONG = self.size()
|
||||
if file_size < 0: return FRESULT.ERR_IO
|
||||
to_read: ULONG = 0
|
||||
if file_size < max_count - 1:
|
||||
if file_size < buf_size - 1:
|
||||
to_read = ULONG(file_size)
|
||||
else:
|
||||
to_read = max_count - 1
|
||||
to_read = buf_size - 1
|
||||
self.seek(0, SEEK_SET)
|
||||
bytes_read: ULONG = 0
|
||||
result: BOOL = w32.win32file.ReadFile(self.handle, buf, to_read, t.CPtr(c.Addr(bytes_read)), None)
|
||||
if result == 0: return FRESULT.ERR_IO
|
||||
if bytes_read < max_count:
|
||||
if bytes_read < buf_size:
|
||||
buf[bytes_read] = 0
|
||||
return LONG(bytes_read)
|
||||
|
||||
@@ -362,22 +391,30 @@ class FileW:
|
||||
buf[total] = 0
|
||||
return LONG(total)
|
||||
|
||||
def read_all(self, buf: bytes, max_count: ULONG) -> LONG:
|
||||
def read_all(self, buf: bytes, buf_size: ULONG) -> LONG:
|
||||
"""把整个文件读进 buf 并写成 C 字符串(内部先 seek 到 0)
|
||||
|
||||
契约(注意与 write 的 count 口径不同):
|
||||
buf_size 是缓冲区**容量, 含终止符** —— 最多读 buf_size-1 字节,
|
||||
再在 buf[返回值] 写一个 NUL 字节, 故返回值 <= buf_size-1。
|
||||
文件比 buf_size-1 更长时**静默截断**, 不报错; 要区分"读完了"还是
|
||||
"被截断了", 请在调用前自己比一次 f.size()。
|
||||
返回实际读到的字节数, 负数为错误码。"""
|
||||
if self.closed: return FRESULT.ERR_CLOSED
|
||||
if not self.can_read: return FRESULT.ERR_PERM
|
||||
if max_count < 2: return FRESULT.ERR
|
||||
if buf_size < 2: return FRESULT.ERR
|
||||
file_size: LONGLONG = self.size()
|
||||
if file_size < 0: return FRESULT.ERR_IO
|
||||
to_read: ULONG = 0
|
||||
if file_size < max_count - 1:
|
||||
if file_size < buf_size - 1:
|
||||
to_read = ULONG(file_size)
|
||||
else:
|
||||
to_read = max_count - 1
|
||||
to_read = buf_size - 1
|
||||
self.seek(0, SEEK_SET)
|
||||
bytes_read: ULONG = 0
|
||||
result: BOOL = w32.win32file.ReadFile(self.handle, buf, to_read, t.CPtr(c.Addr(bytes_read)), None)
|
||||
if result == 0: return FRESULT.ERR_IO
|
||||
if bytes_read < max_count:
|
||||
if bytes_read < buf_size:
|
||||
buf[bytes_read] = 0
|
||||
return LONG(bytes_read)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import t
|
||||
from stdint import *
|
||||
import stdlib
|
||||
|
||||
HANDLE: t.CTypedef = VOIDPTR
|
||||
LPCSTR: t.CTypedef = t.CConst | t.CChar | t.CPtr
|
||||
@@ -78,3 +79,44 @@ extern def SleepEx(dwMilliseconds: ULONG, bAlertable: BOOL) -> ULONG
|
||||
extern def GetTickCount() -> ULONG
|
||||
extern def GetTickCount64() -> ULONGLONG
|
||||
extern def GetCommandLineA() -> CHARPTR
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# 路径编码: Viper 字符串统一 UTF-8, 而 Windows 的 A 系列 API 走系统 ANSI 代码页
|
||||
# (中文机器上为 GBK)。直接拿 UTF-8 路径调 CreateFileA / CreateDirectoryA,
|
||||
# 遇到中文目录名会被按 GBK 误读 → 误报"文件/目录不存在"。故路径统一转 UTF-16
|
||||
# 走 W 系列 API; 仅当字节串不是合法 UTF-8(如命令行 argv 传来的 ANSI 串)时才
|
||||
# 退回 A 系列, 保持既有行为。
|
||||
# =============================================================================
|
||||
UTF8_CODEPAGE: t.CDefine = 65001
|
||||
MB_ERR_INVALID_CHARS: t.CDefine = 0x00000008
|
||||
|
||||
extern def MultiByteToWideChar(CodePage: UINT, dwFlags: DWORD, lpMultiByteStr: LPCSTR,
|
||||
cbMultiByte: int, lpWideCharStr: WCHARPTR,
|
||||
cchWideChar: int) -> int
|
||||
|
||||
|
||||
def utf8_is_valid(s: str) -> int:
|
||||
"""整串(含结尾 NUL)是否为合法 UTF-8。1=是(按 UTF-8 处理), 0=否(按 ANSI)"""
|
||||
if s is None:
|
||||
return 0
|
||||
n: int = MultiByteToWideChar(UTF8_CODEPAGE, MB_ERR_INVALID_CHARS, s, -1,
|
||||
t.CPtr(0), 0)
|
||||
if n <= 0:
|
||||
return 0
|
||||
return 1
|
||||
|
||||
|
||||
def utf8_to_utf16_dup(s: str) -> WCHARPTR:
|
||||
"""UTF-8 → 新分配的 UTF-16 缓冲(含结尾 NUL), 调用方负责 stdlib.free;
|
||||
失败(含非法 UTF-8)返回 None"""
|
||||
if s is None:
|
||||
return None
|
||||
n: int = MultiByteToWideChar(UTF8_CODEPAGE, 0, s, -1, t.CPtr(0), 0)
|
||||
if n <= 0:
|
||||
return None
|
||||
buf: WCHARPTR = stdlib.malloc(t.CSizeT(n) * 2)
|
||||
if buf is None:
|
||||
return None
|
||||
MultiByteToWideChar(UTF8_CODEPAGE, 0, s, -1, buf, n)
|
||||
return buf
|
||||
Reference in new issue
Block a user