Loading lang_cpp_app_01...
#!/usr/bin/env python # -*- coding: utf-8 -*- #-----------------------------------------------------------------------------
import sys import os import platform import ctypes import traceback import time
try: import Tkinter # python 2 except: import tkinter as Tkinter # python 3
class Record: __init__=lambda self, **kw: self.__dict__.update(**kw) __repr__=lambda self: repr(self.__dict__) __eq__=lambda self, oth: oth is not None and self.__dict__==oth.__dict__ __getitem__=lambda self, attr: self.__dict__.__getitem__(attr) __setitem__=lambda self, attr, value: self.__dict__.__setitem__(attr, value) get=lambda self, attr, default=None: self.__dict__.get(attr, default)
class CB: def __init__(self, cb, *args, **kw): self.cb=cb self.args=args self.kw=kw # def __call__(self, *args, **kw): return self.cb(self, *args, **kw)
def makeFrame(app, fnct, img1, img2): app.fnct=fnct app.w1=img1[0] app.h1=img1[1] app.d1=img1[2] pfx=('P6\n%d %d\n255\n'%(app.w1, app.h1)).encode() offset=len(pfx) app.d=bytearray(offset+len(app.d1)) for i in range(offset): app.d[i]=pfx[i] if img2: app.w2=img2[0] app.h2=img2[1] app.d2=img2[2] app.work=lambda a: a.fnct(a.w1, a.h1, a.d1, a.w2, a.h2, a.d2, a.sclValue, a.d) else: app.w2=0 app.h2=0 app.d2=None app.work=lambda a: a.fnct(a.w1, a.h1, a.d1, a.sclValue, a.d) app.outFile='output_'+fnct.__name__+'.ppm' if not Tkinter._default_root: Tkinter.Tk().withdraw() app.top=Tkinter.Toplevel() app.top.title(app.outFile) app.top.wm_protocol('WM_DELETE_WINDOW', CB(quitCmd, app)) app.top.rowconfigure(0, weight=1) app.top.rowconfigure(1, weight=0) app.top.columnconfigure(0, weight=1) app.photo=Tkinter.PhotoImage(width=app.w1, height=app.h1) app.ppmConvert=None app.ppm=None app.lbl=Tkinter.Label(app.top) app.lbl.config(width=app.w1, height=app.h1, anchor='nw', image=app.photo) app.lbl.grid(row=0, column=0, sticky='nsew') if fnct.__name__=='imgBlend': sclMax=100 app.sclScroll=5 else: sclMax=8 app.sclScroll=1 app.scl=Tkinter.Scale(app.top, orient=Tkinter.HORIZONTAL, from_=0, to=sclMax, resolution=1, command=CB(scaleCmd, app)) app.scl.grid(row=1, column=0, sticky='nsew') app.top.minsize(2+app.w1, 4+app.h1+app.scl.winfo_reqheight()) app.top.bind("<KeyPress-Escape>", CB(quitCmd, app)) app.top.bind("<KeyPress-q>", CB(quitCmd, app)) app.top.bind('<ButtonPress-4>', CB(mouseWheelCmd, app)) app.top.bind('<ButtonPress-5>', CB(mouseWheelCmd, app)) app.lastMouseWheel=0 app.sclValue=-1 app.times=None app.timeIdx=0 app.scl.set(sclMax) if app.d2: app.scl.set(0)
def updatePpm(app): if app.ppmConvert is None: # depending on python/tkinter versions (and bugs), some conversions # are required when providing Tkinter.PhotoImage data # - python 3 wants bytes() and directly accesses them # - python 2 wants utf-8 encoded str() but decodes it to bytes again! app.ppmConvert=[] # one of these should work... if 0: # memory leak!!! app.ppmConvert.append(lambda ppm: app.top.tk._createbytearray(ppm)) if Tkinter.__name__=='tkinter': # bad utf-8 conversions in python 2 app.ppmConvert.append(lambda ppm: bytes(ppm)) app.ppmConvert.append(lambda ppm: ppm.decode('latin-1').encode('utf-8')) app.ppmConvert.append(lambda ppm: ppm.decode('latin-1').encode('utf-8') \ .replace(b'\x00', b'\xc0\x80')) while app.ppmConvert: try: ppm=app.ppmConvert[0](app.d) # FIXME: some versions of Tkinter.PhotoImage have a memory leak! # (probably in tk._createbytearray) # thus we perform a direct call to Tcl/Tk if 0: app.photo.config(width=app.w1, height=app.h1, format='ppm', data=ppm) else: app.top.tk.call(app.photo.name, 'configure', '-width', app.w1, '-height', app.h1, '-format', 'ppm', '-data', ppm) break # this conversion was suitable except: # traceback.print_exc() del app.ppmConvert[0] # forget this conversion which was not suitable
def quitCmd(helper, *args, **kw): app=helper.args[0] sys.stdout.write('Saving `%s\'\n'%app.outFile) open(app.outFile, 'wb').write(app.d) Tkinter._default_root.quit()
def mouseWheelCmd(helper, evt, *args, **kw): app=helper.args[0] if evt.serial!=app.lastMouseWheel: app.lastMouseWheel=evt.serial if evt.num==4: app.scl.set(app.sclValue+app.sclScroll) elif evt.num==5: app.scl.set(app.sclValue-app.sclScroll)
def scaleCmd(helper, sclValue, *args, **kw): app=helper.args[0] sclValue=int(sclValue) if sclValue!=app.sclValue: app.sclValue=sclValue timeFnct=time.time workFnct=app.work t0=timeFnct(); workFnct(app); t1=timeFnct() if not app.times: app.times=[t1-t0]*64 app.times=(ctypes.c_double*len(app.times))(*app.times) app.times[app.timeIdx]=t1-t0 app.timeIdx=(app.timeIdx+1)%len(app.times) sys.stdout.write('average %s() time: %g\n'% (app.fnct.__name__, sum(app.times)/len(app.times))) updatePpm(app)
def imgBlend(w1, h1, d1, w2, h2, d2, k, d): offset=len(d)-3*w1*h1 ratio=k/100.0 compRatio=1.0-ratio for y1 in range(h1): for x1 in range(w1): x2=int(x1*w2/w1) y2=int(y1*h2/h1) i1=x1+y1*w1 i2=x2+y2*w2 d[offset+3*i1+0]=int(d1[3*i1+0]*compRatio+d2[3*i2+0]*ratio) d[offset+3*i1+1]=int(d1[3*i1+1]*compRatio+d2[3*i2+1]*ratio) d[offset+3*i1+2]=int(d1[3*i1+2]*compRatio+d2[3*i2+2]*ratio)
def imgReveal(w1, h1, d1, k, d): offset=len(d)-3*w1*h1 shift=8-k for i in range(w1*h1): d[offset+3*i+0]=(d1[3*i+0]<<shift)&0x00FF d[offset+3*i+1]=(d1[3*i+1]<<shift)&0x00FF d[offset+3*i+2]=(d1[3*i+2]<<shift)&0x00FF
def imgHide(w1, h1, d1, w2, h2, d2, k, d): offset=len(d)-3*w1*h1 shift=8-k mask=(~0)<<k for y1 in range(h1): for x1 in range(w1): x2=int(x1*w2/w1) y2=int(y1*h2/h1) i1=x1+y1*w1 i2=x2+y2*w2 d[offset+3*i1+0]=((d1[3*i1+0]&mask)|(d2[3*i2+0]>>shift))&0x00FF d[offset+3*i1+1]=((d1[3*i1+1]&mask)|(d2[3*i2+1]>>shift))&0x00FF d[offset+3*i1+2]=((d1[3*i1+2]&mask)|(d2[3*i2+2]>>shift))&0x00FF
def loadPpm(fileName): try: input=open(fileName, 'rb') words=[] while len(words)<4: words+=input.readline().split(b'#')[0].split() if len(words)!=4 or words[0]!=b'P6' or words[3]!=b'255': return None (w,h)=(int(words[1]),int(words[2])) nb=3*w*h d=bytearray(input.read(nb)) if len(d)!=nb: return None return (w, h, d) except: # traceback.print_exc() return None
def main(): app=Record() if len(sys.argv)>1: fnct=sys.argv[1] if fnct in ['blend', 'reveal', 'hide']: fnct='img%s%s'%(fnct[0].upper(),fnct[1:]) fnct=getattr(sys.modules[__name__], fnct) expectedArgs=3 if fnct.__name__=='imgReveal' else 4 if len(sys.argv)==expectedArgs: img1=loadPpm(sys.argv[2]) if not img1: sys.stderr.write('\nCannot open image `%s\'\n'%sys.argv[2]) sys.exit(1) if expectedArgs==4: img2=loadPpm(sys.argv[3]) if not img2: sys.stderr.write('\nCannot open image `%s\'\n'%sys.argv[3]) sys.exit(1) else: img2=None makeFrame(app, fnct, img1, img2) if app.get('top') is not None: Tkinter._default_root.mainloop() else: sys.stderr.write('\n') sys.stderr.write('usage: %s blend image1 image2\n'%sys.argv[0]) sys.stderr.write(' %s reveal image\n'%sys.argv[0]) sys.stderr.write(' %s hide image1 image2\n'%sys.argv[0]) sys.stderr.write('\n')
def tryToLoadNativeFunctions(): try: dirName=os.path.dirname(sys.modules[__name__].__file__) if not dirName: dirName=os.path.curdir libName='NativeImageUtils' if platform.system().startswith('Windows'): libName='%s.dll'%libName elif platform.system().startswith('Darwin'): libName='lib%s.dylib'%libName else: libName='lib%s.so'%libName path=os.path.join(dirName, libName) ntvLib=ctypes.CDLL(os.path.abspath(path)) ntvHello=ntvLib['hello'] ntvHello.restype=ctypes.c_int ntvHello.argtypes=[ctypes.c_char_p] # msg sys.stdout.write('native hello() returned: %d\n'% ntvHello(b'native code is accessible!')) try: ntvImgBlend=ntvLib['imgBlend'] ntvImgBlend.restype=None ntvImgBlend.argtypes=[ctypes.c_int, # w1 ctypes.c_int, # h1 ctypes.c_void_p, # d1 ctypes.c_int, # w2 ctypes.c_int, # h2 ctypes.c_void_p, # d2 ctypes.c_int, # k ctypes.c_void_p] # d def imgBlend(w1, h1, d1, w2, h2, d2, k, d): nb1=3*w1*h1 nb2=3*w2*h2 ntvImgBlend(w1, h1, (ctypes.c_uint8*nb1).from_buffer(d1), w2, h2, (ctypes.c_uint8*nb2).from_buffer(d2), k, (ctypes.c_uint8*nb1).from_buffer(d, len(d)-nb1)) setattr(sys.modules[__name__], 'imgBlend', imgBlend) sys.stdout.write('native version of imgBlend() made available\n') except: #traceback.print_exc() pass try: ntvImgReveal=ntvLib['imgReveal'] ntvImgReveal.restype=None ntvImgReveal.argtypes=[ctypes.c_int, # w1 ctypes.c_int, # h1 ctypes.c_void_p, # d1 ctypes.c_int, # k ctypes.c_void_p] # d def imgReveal(w1, h1, d1, k, d): nb1=3*w1*h1 ntvImgReveal(w1, h1, (ctypes.c_uint8*nb1).from_buffer(d1), k, (ctypes.c_uint8*nb1).from_buffer(d, len(d)-nb1)) setattr(sys.modules[__name__], 'imgReveal', imgReveal) sys.stdout.write('native version of imgReveal() made available\n') except: #traceback.print_exc() pass try: ntvImgHide=ntvLib['imgHide'] ntvImgHide.restype=None ntvImgHide.argtypes=[ctypes.c_int, # w1 ctypes.c_int, # h1 ctypes.c_void_p, # d1 ctypes.c_int, # w2 ctypes.c_int, # h2 ctypes.c_void_p, # d2 ctypes.c_int, # k ctypes.c_void_p] # d def imgHide(w1, h1, d1, w2, h2, d2, k, d): nb1=3*w1*h1 nb2=3*w2*h2 ntvImgHide(w1, h1, (ctypes.c_uint8*nb1).from_buffer(d1), w2, h2, (ctypes.c_uint8*nb2).from_buffer(d2), k, (ctypes.c_uint8*nb1).from_buffer(d, len(d)-nb1)) setattr(sys.modules[__name__], 'imgHide', imgHide) sys.stdout.write('native version of imgHide() made available\n') except: #traceback.print_exc() pass except: #traceback.print_exc() pass
if __name__=='__main__': tryToLoadNativeFunctions() main()
#-----------------------------------------------------------------------------
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # this GNU-makefile relies on the GCC toolchain # nb: on Windows, the Mingw-w64 package provides the mingw32-make.exe command # ( see https://web.enib.fr/~harrouet/dev_tools/#windows.mingw )
#~~~~ control global settings ~~~~ # make opt=2 --> enable optimisation, then disable debug, but keep symbols # make opt=1 --> enable optimisation, then disable debug # make opt=0 --> disable optimisation, then enable debug opt=0 # make clang=1 --> use clang/clang++, not gcc/g++ # make clang=0 --> use gcc/g++, not clang/clang++ clang=0 # make wasm=1 --> target webassembly rather than the native platform # make wasm=0 --> target the native platform rather than webassembly wasm=0
#~~~~ build library or programs ~~~~ # if LIB_TARGET is provided, this library will be built (with its # platform-specific name), otherwise ${EXE_PREFIX}* programs will be built LIB_TARGET=NativeImageUtils EXE_PREFIX=prog
#~~~~ detect operating system ~~~~ ifneq (${OS},Windows_NT) ifneq (,${findstring Ubuntu,${shell lsb_release -i 2>/dev/null}}) OS:=Ubuntu else ifneq (,${findstring Raspbian,${shell lsb_release -i 2>/dev/null}}) # Standard distribution for Raspberry-Pi OS:=Raspbian else OS:=${strip ${shell uname -s}} endif ifneq (,${findstring Microsoft,${shell cat /proc/version 2>/dev/null}}) # Windows-Subsystem-for-Linux OS:=${OS}_WSL endif endif
#~~~~ adjust project-specific settings ~~~~ CPPFLAGS= # CPPFLAGS+=-I header/path LDFLAGS= # LDFLAGS+=-L library/path -Wl,-rpath,library/path -l library_name CFLAGS= CXXFLAGS= BINFLAGS= ifneq (,${findstring Windows_NT,${OS}}) # nothing special for now else ifneq (,${strip ${LIB_TARGET}}) BINFLAGS+=-fPIC endif ifneq (,${findstring Darwin,${OS}}) # nothing special for now else ifneq (,${findstring Ubuntu,${OS}}) # sanitizer sometimes requires gold-linker on Ubuntu LDFLAGS+=-fuse-ld=gold else ifneq (,${findstring Raspbian,${OS}}) # some warnings may appear when mixing g++-6 and g++-7 on Raspbian CXXFLAGS+=-Wno-psabi else # nothing special for now endif endif endif
#~~~~ adjust platform-specific features (Posix/Windows/Emscripten...) ~~~~ ifneq (,${findstring Windows_NT,${OS}}) LIB_PREFIX= LIB_SUFFIX=.dll EXE_SUFFIX=.exe SKIP_LINE=echo. REMOVE=del /q else LIB_PREFIX=lib ifneq (,${findstring Darwin,${OS}}) LIB_SUFFIX=.dylib else LIB_SUFFIX=.so endif EXE_SUFFIX= SKIP_LINE=echo REMOVE=rm -rf endif ifeq (${strip ${wasm}},1) LIB_PREFIX:=lib LIB_SUFFIX:=.bc EXE_SUFFIX:=.html endif
#~~~~ deduce file names ~~~~ ifneq (,${strip ${LIB_TARGET}}) LIB_TARGET:=${LIB_PREFIX}${strip ${LIB_TARGET}}${LIB_SUFFIX} MAIN_C_FILES= MAIN_CXX_FILES= else LIB_TARGET:= MAIN_C_FILES=${wildcard ${strip ${EXE_PREFIX}}*.c} MAIN_CXX_FILES=${wildcard ${strip ${EXE_PREFIX}}*.cpp} endif COMMON_C_FILES=${filter-out ${MAIN_C_FILES},${wildcard *.c}} COMMON_CXX_FILES=${filter-out ${MAIN_CXX_FILES},${wildcard *.cpp}} # MAIN_OBJECT_FILES=${sort ${patsubst %.c,%.o,${MAIN_C_FILES}} \ ${patsubst %.cpp,%.o,${MAIN_CXX_FILES}}} COMMON_OBJECT_FILES=${sort ${patsubst %.c,%.o,${COMMON_C_FILES}} \ ${patsubst %.cpp,%.o,${COMMON_CXX_FILES}}} OBJECT_FILES=${MAIN_OBJECT_FILES} ${COMMON_OBJECT_FILES} DEPEND_FILES=${patsubst %.o,%.d,${OBJECT_FILES}} EXE_FILES=${sort ${patsubst %.c,%${EXE_SUFFIX},${MAIN_C_FILES}} \ ${patsubst %.cpp,%${EXE_SUFFIX},${MAIN_CXX_FILES}}} # GENERATED_FILES=${DEPEND_FILES} ${OBJECT_FILES} ${LIB_TARGET} ${EXE_FILES} ifneq (,${findstring Darwin,${OS}}) GENERATED_FILES+=${wildcard *.dSYM} endif ifeq (${strip ${wasm}},1) GENERATED_FILES+=${patsubst %.html,%.js,${EXE_FILES}} GENERATED_FILES+=${patsubst %.html,%.html.mem,${EXE_FILES}} GENERATED_FILES+=${patsubst %.html,%.wasm,${EXE_FILES}} GENERATED_FILES+=${patsubst %.html,%.wast,${EXE_FILES}} endif GENERATED_FILES+=${wildcard output_* *~}
#~~~~ compiler/linker settings ~~~~ CPPFLAGS+=-MMD -pedantic -Wall -Wextra -Wconversion -Wno-sign-conversion CPPFLAGS+=-Wno-unused -Wno-unused-parameter CPPFLAGS+=-Werror -Wfatal-errors CFLAGS+=-std=c99 -Wc++-compat -Wwrite-strings -Wold-style-definition -Wvla CXXFLAGS+=-std=c++17 -Wno-missing-braces LDFLAGS+= BINFLAGS+= ifeq (${strip ${wasm}},1) CC=emcc CXX=em++ CPPFLAGS+=-Wno-dollar-in-identifier-extension LDFLAGS+=-s ALLOW_MEMORY_GROWTH=1 else ifeq (${strip ${clang}},1) CC=clang CXX=clang++ else CC=gcc CXX=g++ endif # ifneq (,${strip ${MAIN_CXX_FILES} ${COMMON_CXX_FILES}}) # force c++ link if there is at least one c++ source file LD:=${CXX} else LD:=${CC} endif
#~~~~ debug/optimisation settings ~~~~ ifneq (${strip ${opt}},0) CPPFLAGS+=-DNDEBUG BINFLAGS+=-O3 -ffast-math # BINFLAGS+=-fopt-info-vec-optimized ifneq (${strip ${wasm}},1) BINFLAGS+=-march=native endif ifeq (${strip ${opt}},2) # optimise but keep symbols for profiling BINFLAGS+=-g -fno-omit-frame-pointer else BINFLAGS+=-fomit-frame-pointer endif else CPPFLAGS+=-UNDEBUG BINFLAGS+=-g -O0 ifeq (${strip ${wasm}},1) # sanitizer is not available yet with Emscripten else ifneq (,${findstring Windows_NT,${OS}}) # sanitizer is not available yet on Windows else # BINFLAGS+=-fsanitize=address,undefined ifneq (,${findstring Raspbian,${OS}}) # dynamic sanitizer libraries may not be found on Raspbian # BINFLAGS+=-static-libasan -static-libubsan endif endif endif
#~~~~ main target ~~~~ all : ${EXE_FILES} ${LIB_TARGET}
rebuild : clean all
.SUFFIXES: .SECONDARY: .PHONY: all clean rebuild
#~~~~ linker command to produce the library (if any) ~~~~ ${LIB_TARGET} : ${COMMON_OBJECT_FILES} @echo ==== linking [opt=${opt}] $@ ==== ${LD} -shared -o $@ $^ ${BINFLAGS} ${LDFLAGS} @${SKIP_LINE}
#~~~~ linker command to produce the executable files (if any) ~~~~ %${EXE_SUFFIX} : %.o ${COMMON_OBJECT_FILES} @echo ==== linking [opt=${opt}] $@ ==== ${LD} -o $@ $^ ${BINFLAGS} ${LDFLAGS} @${SKIP_LINE}
#~~~~ compiler command for every source file ~~~~ %.o : %.c @echo ==== compiling [opt=${opt}] $< ==== ${CC} -o $@ $< -c ${BINFLAGS} ${CPPFLAGS} ${CFLAGS} @${SKIP_LINE}
%.o : %.cpp @echo ==== compiling [opt=${opt}] $< ==== ${CXX} -o $@ $< -c ${BINFLAGS} ${CPPFLAGS} ${CXXFLAGS} @${SKIP_LINE}
-include ${DEPEND_FILES}
#~~~~ remove generated files ~~~~ clean : @echo ==== cleaning ==== ${REMOVE} ${GENERATED_FILES} @${SKIP_LINE}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//----------------------------------------------------------------------------
#ifndef IMG_PIXEL_HPP #define IMG_PIXEL_HPP
#include <cstdint>
namespace img {
struct Pixel { std::uint8_t r{}, g{}, b{}; };
// Pixel <-- Pixel * double
// Pixel <-- Pixel + Pixel
// Pixel <-- Pixel << int
// Pixel <-- Pixel >> int
// Pixel <-- Pixel & int
// Pixel <-- Pixel | Pixel
// merge pixels from image1 with pixels from image2 // - image1 is read-only and has dimensions w1*h1 // - image2 is read-only and has dimensions w2*h2 // - resulting image has dimensions w1*h1 // using a generic merge operation on pixels // - Pixel <-- oper(Pixel, Pixel)
} // namespace img
#endif // IMG_PIXEL_HPP
//----------------------------------------------------------------------------
//----------------------------------------------------------------------------
#if defined _WIN32 # define NATIVE_IMAGE_UTILS_API extern "C" __declspec(dllexport) #else # define NATIVE_IMAGE_UTILS_API extern "C" #endif
#include <cstdint> #include <iostream> #include <string> #include <algorithm> #include "pixel.hpp"
//---------------------------------------------------------------------------- // Minimal function called from Python //----------------------------------------------------------------------------
NATIVE_IMAGE_UTILS_API int hello(const char *msg) { std::string s{msg}; int result=int(s.size()); std::cout << "native hello() received '" << msg << "' and is about to return " << result << '\n'; return result; }
//---------------------------------------------------------------------------- // Blend images //----------------------------------------------------------------------------
NATIVE_IMAGE_UTILS_API void imgBlend(int w1, // first input image (and result) width int h1, // first input image (and result) height const img::Pixel *d1, // first input image pixels (w1*h1) int w2, // second input image width int h2, // second input image height const img::Pixel *d2, // second input image pixels (w2*h2) int k, // cursor value within range [0;100] img::Pixel *d) // resulting image pixels (w1*h1) { // ... À COMPLÉTER ... // // renseigner les `w1*h1' pixels de `d' //
#if 1 // uniform colour
#elif 0 // uniform colour dimmed according to k
#elif 0 // copy of d1 dimmed according to k
#elif 0 // full problem (hardcoded loops)
#else // full problem (generic function)
#endif }
//---------------------------------------------------------------------------- // Reveal an image hidden into another one //----------------------------------------------------------------------------
NATIVE_IMAGE_UTILS_API void imgReveal(int w1, // input image (and result) width int h1, // input image (and result) height const img::Pixel *d1, // input image pixels (w1*h1) int k, // cursor value within range [0;8] img::Pixel *d) // resulting image pixels (w1*h1) { // ... À COMPLÉTER ... // // renseigner les `w1*h1' pixels de `d' //
// similar to copy of d1 dimmed according to k
}
//---------------------------------------------------------------------------- // Hide an image into another one //----------------------------------------------------------------------------
NATIVE_IMAGE_UTILS_API void imgHide(int w1, // first input image (and result) width int h1, // first input image (and result) height const img::Pixel *d1, // first input image pixels (w1*h1) int w2, // second input image width int h2, // second input image height const img::Pixel *d2, // second input image pixels (w2*h2) int k, // cursor value within range [0;8] img::Pixel *d) // resulting image pixels (w1*h1) { // ... À COMPLÉTER ... // // renseigner les `w1*h1' pixels de `d' //
#if 1 // similar to blending (hardcoded loops)
#else // similar to blending (generic function)
#endif }
//----------------------------------------------------------------------------
LIB_TARGET=NativeImageUtilsEn effet, nous ne cherchons plus à fabriquer un programme exécutable autonome mais une bibliothèque dynamique nommée NativeImageUtils✍
# BINFLAGS+=-fsanitize=address,undefined