More file formats supported. Actual MIDI. Framefiles now lazily open handles to prevent running out of them.
This commit is contained in:
parent
ca49f64086
commit
5bf69bb402
2492 changed files with 1066065 additions and 77 deletions
79
CHANGELOG
79
CHANGELOG
|
|
@ -748,6 +748,85 @@ API Changes
|
|||
deterministic in settings.cfg. With the option absent nothing
|
||||
changes.
|
||||
|
||||
- Every image format the bundled FFmpeg can decode now loads, not only
|
||||
the ones SDL_image reads: TIFF, OpenEXR, JPEG 2000, DDS, PSD, DPX,
|
||||
and AVIF and HEIC through a vendored dav1d. spriteLoad,
|
||||
spriteLoadData, spriteLoadFrames, meshHeightmap, --bezel artwork,
|
||||
glTF embedded textures and the GUI's images all take the same path:
|
||||
SDL_image first, FFmpeg for whatever it turns down. sceneSetSky
|
||||
gains the same fallback and keeps a floating point source's range, so
|
||||
an OpenEXR sky is lit correctly rather than clipped.
|
||||
|
||||
- AV1 video plays. FFmpeg's own AV1 decoder is hardware only and said
|
||||
"Your platform doesn't support hardware accelerated AV1 decoding" on
|
||||
any machine without an AV1 capable GPU; the vendored dav1d is the
|
||||
software decoder, and it is what brings AVIF with it.
|
||||
|
||||
- Every audio format the bundled FFmpeg can decode now loads as a sound
|
||||
or as music: AAC, ALAC, AC-3, WMA, APE, TTA, Speex and AMR among
|
||||
them. An AAC track used to play when it was the audio of a video and
|
||||
fail when it was a sound effect. Chiptunes arrive with a vendored
|
||||
game-music-emu (NSF, NSFE, SPC, VGM, GBS, AY, GYM, HES, KSS and
|
||||
SAP), and the tracker
|
||||
formats libxmp was already reading -- MOD, S3M, XM, IT and fifty-odd
|
||||
others -- are now documented rather than merely present.
|
||||
|
||||
- MIDI files play, synthesised with TinySoundFont. Singe ships no
|
||||
sound bank, because a good one is tens of megabytes for a format
|
||||
almost no game uses: name one with --soundfont FILE, put one at
|
||||
Singe/soundfont.sf2 so a packed game carries its own, or let it find
|
||||
the one your distribution installed. The trace header says which was
|
||||
used. SDL_mixer's own MIDI decoder wants a GUS patch set almost
|
||||
nobody has, so a .mid failed to load while the trace claimed a MIDI
|
||||
decoder was there.
|
||||
|
||||
- MIDI ports, in and out, for a cabinet driving a real sound module or
|
||||
reading a keyboard or a control surface as an input device:
|
||||
midiInputCount, midiInputName, midiOpenInput, midiCloseInput,
|
||||
midiIsInputOpen and the same five for output; midiSend for any
|
||||
message at all; midiNoteOn, midiNoteOff, midiProgramChange,
|
||||
midiControlChange and midiPitchBend; midiRescan for a device plugged
|
||||
in while the game runs; and an onMidiMessage(status, data1, data2,
|
||||
bytes) callback. Channels are 1 to 16. ALSA's sequencer on Linux,
|
||||
CoreMIDI on macOS, the multimedia MIDI calls on Windows. On Linux
|
||||
the library is opened at run time, as SDL opens the same one for
|
||||
audio, so a machine without it simply has no ports.
|
||||
|
||||
- spriteLoad(name, width, height) rasterises a vector picture -- an SVG
|
||||
-- to fit that box, keeping its proportions, instead of taking the
|
||||
file's own size, which for an icon is usually 16 or 24 pixels. Every
|
||||
other format ignores the size.
|
||||
|
||||
- --deinterlace off|auto|on, automatic by default, for a laserdisc rip
|
||||
that kept its interlaced fields and combs on a progressive display.
|
||||
Automatic touches only the frames a file marks interlaced, so a
|
||||
progressive disc pays nothing; on is for a file whose flags are
|
||||
wrong. Settable as deinterlace in settings.cfg.
|
||||
|
||||
- Fonts gain what FreeType was built without: WOFF and WOFF2, colour
|
||||
bitmap glyphs, colour vector glyphs, and complex script shaping
|
||||
through HarfBuzz, so Arabic, Hebrew and the Indic scripts join and
|
||||
order correctly instead of coming out as unjoined letters.
|
||||
|
||||
- A framefile opens its segments as the disc reaches them instead of
|
||||
opening every one at startup. A long framefile -- typing-md2 has 213
|
||||
segments -- held a demuxer open for each, which cost hundreds of file
|
||||
descriptors and could push a game past the 1024 that glibc's select
|
||||
refuses to look past, aborting it inside a fortify check. At most
|
||||
four segments are open now: the one playing, the one after it (opened
|
||||
ahead so the changeover costs nothing), and room for a seek to move
|
||||
between two. Measured on a twelve-segment game, 75 open descriptors
|
||||
became 24. A framefile naming a file that does not exist still fails
|
||||
at startup, as it always has.
|
||||
|
||||
- Subtitles carried inside the video file: discGetSubtitleTracks(),
|
||||
discGetSubtitleLanguage(track) and srtLoadTrack(track), which reads
|
||||
one track out of the disc's own container and loads it exactly as
|
||||
srtLoad loads a .srt beside the game. SubRip, WebVTT, ASS and MOV
|
||||
text are read, with ASS override tags stripped; picture subtitles
|
||||
(VobSub, PGS) are counted so the numbering matches other players, but
|
||||
cannot be read, because there are no words in them.
|
||||
|
||||
|
||||
|
||||
Fixes
|
||||
|
|
|
|||
|
|
@ -277,6 +277,8 @@ endif()
|
|||
|
||||
set(SINGE_SOURCE
|
||||
src/common.h
|
||||
src/decode.c
|
||||
src/decode.h
|
||||
src/embedded.h
|
||||
src/frameFile.c
|
||||
src/rotoZoom.c
|
||||
|
|
@ -303,6 +305,12 @@ set(SINGE_SOURCE
|
|||
src/model.h
|
||||
src/math3d.c
|
||||
src/math3d.h
|
||||
src/midi.c
|
||||
src/midi.h
|
||||
src/midiIo.c
|
||||
src/midiIo.h
|
||||
thirdparty/tinysoundfont/tsf.h
|
||||
thirdparty/tinysoundfont/tml.h
|
||||
src/scene.c
|
||||
src/scene.h
|
||||
src/singe.c
|
||||
|
|
@ -609,6 +617,7 @@ elseif(KANGAROO_OS STREQUAL "macos")
|
|||
-Wl,-weak_framework,Metal
|
||||
-Wl,-weak_framework,QuartzCore
|
||||
-Wl,-weak_framework,CoreHaptics
|
||||
-Wl,-weak_framework,CoreMIDI
|
||||
-Wl,-weak_framework,UniformTypeIdentifiers
|
||||
-liconv
|
||||
-lc++
|
||||
|
|
@ -641,6 +650,7 @@ elseif(KANGAROO_OS STREQUAL "windows")
|
|||
-loleaut32
|
||||
-lversion
|
||||
-luuid
|
||||
-lrpcrt4
|
||||
-ladvapi32
|
||||
-lsetupapi
|
||||
-lshell32
|
||||
|
|
@ -711,11 +721,12 @@ set(STATIC_LIBS
|
|||
${BUILD_DIR}/lib/libcrypto.a
|
||||
${BUILD_DIR}/lib/libssl.a
|
||||
)
|
||||
# SDL3_image builds its own jpeg and png decoders where the platform has none (macOS uses ImageIO
|
||||
# for jpeg); link whichever archives it produced.
|
||||
foreach(imageLib libjpeg.a libpng16.a)
|
||||
if(EXISTS ${BUILD_DIR}/lib/${imageLib})
|
||||
list(APPEND STATIC_LIBS ${BUILD_DIR}/lib/${imageLib})
|
||||
# Not every archive exists in every tree: SDL3_image builds its own jpeg and png decoders where the
|
||||
# platform has none (macOS uses ImageIO for jpeg), brotli arrives with FreeType's WOFF2 support, and
|
||||
# libgme with the chiptune decoder. Link whichever ones are there.
|
||||
foreach(optionalLib libjpeg.a libpng16.a libbrotlidec.a libbrotlicommon.a libgme.a libdav1d.a libjxl_dec.a libhwy.a)
|
||||
if(EXISTS ${BUILD_DIR}/lib/${optionalLib})
|
||||
list(APPEND STATIC_LIBS ${BUILD_DIR}/lib/${optionalLib})
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
|
|
|
|||
15
INSTALL
15
INSTALL
|
|
@ -59,13 +59,14 @@ library (zlib, zstd, SDL3 and its satellites, OpenSSL, FFmpeg) into
|
|||
copied to .builddir/Singe-v<version>-<Os>-<arch>.
|
||||
|
||||
Host packages (what build-all.sh installs): build-essential cmake
|
||||
git-lfs pkg-config perl nasm llvm autoconf automake libtool imagemagick
|
||||
ffmpeg lua5.4 asciidoctor ruby-asciidoctor-pdf libva-dev libvdpau-dev
|
||||
libdrm-dev libgl-dev libegl-dev libgles-dev libgbm-dev libasound2-dev
|
||||
libpulse-dev libpipewire-0.3-dev libjack-jackd2-dev libsndio-dev
|
||||
libudev-dev libdbus-1-dev libibus-1.0-dev libxkbcommon-dev libx11-dev
|
||||
libxext-dev libxfixes-dev libxi-dev libxcursor-dev libxrandr-dev
|
||||
libxss-dev libxtst-dev libwayland-dev wayland-protocols libdecor-0-dev.
|
||||
git-lfs pkg-config perl nasm llvm autoconf automake libtool meson
|
||||
ninja-build imagemagick ffmpeg lua5.4 asciidoctor ruby-asciidoctor-pdf
|
||||
libva-dev libvdpau-dev libdrm-dev libgl-dev libegl-dev libgles-dev
|
||||
libgbm-dev libasound2-dev libpulse-dev libpipewire-0.3-dev
|
||||
libjack-jackd2-dev libsndio-dev libudev-dev libdbus-1-dev
|
||||
libibus-1.0-dev libxkbcommon-dev libx11-dev libxext-dev libxfixes-dev
|
||||
libxi-dev libxcursor-dev libxrandr-dev libxss-dev libxtst-dev
|
||||
libwayland-dev wayland-protocols libdecor-0-dev.
|
||||
|
||||
The artwork, the font and the menu video in assets/ are stored with Git
|
||||
LFS. Clone with git-lfs present, or those files arrive as small text
|
||||
|
|
|
|||
12
LICENSES
12
LICENSES
|
|
@ -10,14 +10,20 @@ trees carry). Projects without releases are listed by snapshot date.
|
|||
arg_parser 1.21 BSD-2-Clause http://savannah.nongnu.org/projects/arg-parser
|
||||
basis_universal 2.50 Apache-2.0 https://github.com/BinomialLLC/basis_universal
|
||||
binaryheap.lua 0.4 MIT http://tieske.github.io/binaryheap.lua
|
||||
brotli 1.2.0 MIT https://github.com/google/brotli
|
||||
cgltf 1.15 MIT https://github.com/jkuhlmann/cgltf
|
||||
copas 4.12.0 MIT https://lunarmodules.github.io/copas
|
||||
dav1d 1.5.4 BSD-2-Clause https://code.videolan.org/videolan/dav1d
|
||||
DirectXShaderCompiler 1.10.2605.37 NCSA https://github.com/microsoft/DirectXShaderCompiler (prebuilt, downloaded at build time; host tool only)
|
||||
ffmpeg 9.0.1 LGPL-2.1 https://ffmpeg.org
|
||||
freetype 2.13.2 FTL https://freetype.org (bundled with SDL3_ttf)
|
||||
game-music-emu 0.6.6 LGPL-2.1 https://github.com/libgme/game-music-emu (bundled with SDL3_mixer)
|
||||
harfbuzz 14.4.0 MIT https://harfbuzz.github.io (bundled with SDL3_ttf)
|
||||
highway 1.4.0 Apache-2.0 https://github.com/google/highway (bundled with libjxl)
|
||||
JoltPhysics 5.6.0 MIT https://github.com/jrouwe/JoltPhysics
|
||||
json.lua 0.1.2 MIT https://github.com/rxi/json.lua
|
||||
libjpeg 9f IJG https://ijg.org (bundled with SDL3_image)
|
||||
libjxl 0.7.3-SDL BSD-3-Clause https://github.com/libsdl-org/libjxl (bundled with SDL3_image)
|
||||
libogg 1.3.5 BSD-3-Clause https://xiph.org/ogg (bundled with SDL3_mixer)
|
||||
libpng 1.6.58 libpng-2.0 http://www.libpng.org (bundled with SDL3_image)
|
||||
librs232 1.0.4 MIT https://github.com/srdgame/librs232
|
||||
|
|
@ -32,16 +38,20 @@ manymouse 0.0.3 Zlib https://icculus.org/manymouse
|
|||
openssl 3.5.8 Apache-2.0 https://www.openssl.org
|
||||
opus 1.4 BSD-3-Clause https://opus-codec.org (bundled with SDL3_mixer)
|
||||
opusfile 0.12 BSD-3-Clause https://opus-codec.org (bundled with SDL3_mixer)
|
||||
plutosvg 0.0.8 MIT https://github.com/sammycage/plutosvg (bundled with SDL3_ttf)
|
||||
plutovg 1.3.3 MIT https://github.com/sammycage/plutovg (bundled with SDL3_ttf)
|
||||
recastnavigation 1.6.0 Zlib https://github.com/recastnavigation/recastnavigation
|
||||
RmlUi 6.3 MIT https://github.com/mikke89/RmlUi
|
||||
SDL3 3.4.16 Zlib https://www.libsdl.org
|
||||
SDL_shadercross main 2026-09 Zlib https://github.com/libsdl-org/SDL_shadercross (host tool only)
|
||||
SDL3_image 3.4.6 Zlib https://www.libsdl.org
|
||||
SDL3_mixer 3.2.4 Zlib https://www.libsdl.org
|
||||
SDL3_ttf 3.2.2 Zlib https://www.libsdl.org
|
||||
SDL_shadercross main 2026-09 Zlib https://github.com/libsdl-org/SDL_shadercross (host tool only)
|
||||
skcms 0.7.3-SDL BSD-3-Clause https://skia.googlesource.com/skcms (bundled with libjxl)
|
||||
SPIRV-Cross main 2026-09 Apache-2.0 https://github.com/KhronosGroup/SPIRV-Cross (host tool only)
|
||||
sqlite 3.53.4 Public-Domain https://sqlite.org
|
||||
timerwheel.lua 1.0.2 MIT https://tieske.github.io/timerwheel.lua
|
||||
TinySoundFont 2026-07-19 MIT https://github.com/schellingb/TinySoundFont
|
||||
uthash 2.4.0 BSD-1-Clause https://troydhanson.github.io/uthash
|
||||
vlc 3.0 headers LGPL-2.1 https://www.videolan.org/vlc (two language-code headers only)
|
||||
wavpack 5.9.0 BSD-3-Clause https://www.wavpack.com (bundled with SDL3_mixer)
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@
|
|||
-- audio = 0 -- which audio track of the disc video to play
|
||||
-- altaudio = "" -- play <base><suffix>.ogg beside the disc video instead
|
||||
-- audiodelay = 0 -- milliseconds the audio is heard late (negative if early)
|
||||
-- soundfont = "" -- the .sf2 a MIDI file is synthesised with; without it, a search
|
||||
|
||||
|
||||
-- Input ------------------------------------------------------------------
|
||||
|
|
@ -122,6 +123,9 @@
|
|||
-- trace = false -- trace every Lua call to trace.txt
|
||||
-- showcalculated = false -- print the frame ranges of every framefile segment
|
||||
-- softwarevideo = false -- decode video in software even when the machine can do it in hardware
|
||||
-- deinterlace = "auto" -- what an interlaced picture gets: off, auto or on. A laserdisc held
|
||||
-- interlaced fields, and a rip that kept them combs on a progressive
|
||||
-- display; auto touches only the frames the file marks interlaced
|
||||
-- deterministic = 15 -- testing only: ignore real time, run on a virtual clock stepped this
|
||||
-- many milliseconds a frame and seed the random generators with the same
|
||||
-- number, so a run repeats to the pixel. true takes the default 15.
|
||||
|
|
|
|||
|
|
@ -50,6 +50,9 @@ G_HOSTPACKAGES=(
|
|||
autoconf
|
||||
automake
|
||||
libtool
|
||||
# dav1d, the AV1 decoder, is the one vendored library whose own build system is Meson
|
||||
meson
|
||||
ninja-build
|
||||
# Content the build generates: embedded images, the menu video, the LuaSec table, the manual
|
||||
imagemagick
|
||||
ffmpeg
|
||||
|
|
|
|||
|
|
@ -197,16 +197,37 @@ singeCmakeProject(recastnavigation ${SB_THIRDPARTY}/recastnavigation ""
|
|||
|
||||
set(sdl3Dir -DSDL3_DIR=${SB_PREFIX}/lib/cmake/SDL3)
|
||||
|
||||
singeCmakeProject(SDL3_image ${SB_THIRDPARTY}/SDL3_image "SDL3;zlib"
|
||||
"-DBUILD_SHARED_LIBS=off;-DSDLIMAGE_DEPS_SHARED=off;-DSDLIMAGE_SAMPLES=off;-DSDLIMAGE_TESTS=off;-DSDLIMAGE_VENDORED=on;-DSDLIMAGE_BACKEND_STB=off;-DSDLIMAGE_AVIF=off;-DSDLIMAGE_JXL=off;-DSDLIMAGE_TIF=off;-DSDLIMAGE_WEBP=on;${sdl3Dir};-DWEBP_BUILD_ANIM_UTILS=off;-DWEBP_BUILD_CWEBP=off;-DWEBP_BUILD_DWEBP=off;-DWEBP_BUILD_GIF2WEBP=off;-DWEBP_BUILD_IMG2WEBP=off;-DWEBP_BUILD_VWEBP=off;-DWEBP_BUILD_WEBPINFO=off;-DWEBP_BUILD_WEBPMUX=off;-DWEBP_BUILD_EXTRAS=off"
|
||||
# Brotli, for the WOFF2 web fonts FreeType reads only when it is present. It is built before
|
||||
# SDL3_image and SDL3_ttf so that it is in the prefix when FreeType looks for it.
|
||||
#
|
||||
# libjxl vendors brotli as well, and its install writes libbrotlidec.a and libbrotlicommon.a over
|
||||
# the ones built here. That is harmless and is left alone: it is the same upstream library, the
|
||||
# two FreeType links come from libjxl's tree as a matched pair, and only the decoder is ever linked
|
||||
# -- libbrotlienc.a is the one file of this project's own build that survives, and nothing asks for
|
||||
# it. Telling libjxl to use the copy already installed (JPEGXL_FORCE_SYSTEM_BROTLI) does work, but
|
||||
# SDL3_image then tries to install brotli targets that no longer exist, and patching its install
|
||||
# list is a worse trade than one duplicated build.
|
||||
singeCmakeProject(brotli ${SB_THIRDPARTY}/brotli "" "-DBUILD_SHARED_LIBS=off;-DBROTLI_BUILD_TOOLS=off;-DBROTLI_DISABLE_TESTS=on" "${SB_ENV}")
|
||||
|
||||
singeCmakeProject(SDL3_image ${SB_THIRDPARTY}/SDL3_image "SDL3;zlib;brotli"
|
||||
"-DBUILD_SHARED_LIBS=off;-DSDLIMAGE_DEPS_SHARED=off;-DSDLIMAGE_SAMPLES=off;-DSDLIMAGE_TESTS=off;-DSDLIMAGE_VENDORED=on;-DSDLIMAGE_BACKEND_STB=off;-DSDLIMAGE_AVIF=off;-DSDLIMAGE_JXL=on;-DSDLIMAGE_JXL_SHARED=off;-DBUILD_TESTING=OFF;-DSDLIMAGE_TIF=off;-DSDLIMAGE_WEBP=on;${sdl3Dir};-DWEBP_BUILD_ANIM_UTILS=off;-DWEBP_BUILD_CWEBP=off;-DWEBP_BUILD_DWEBP=off;-DWEBP_BUILD_GIF2WEBP=off;-DWEBP_BUILD_IMG2WEBP=off;-DWEBP_BUILD_VWEBP=off;-DWEBP_BUILD_WEBPINFO=off;-DWEBP_BUILD_WEBPMUX=off;-DWEBP_BUILD_EXTRAS=off"
|
||||
"${SB_ENV_NO_PREFIX_INCLUDE}")
|
||||
|
||||
# SDL3_image builds libpng and installs the archive but not its headers, and FreeType needs both
|
||||
# before it will read a font's colour bitmap glyphs. pnglibconf.h is generated, so it comes from
|
||||
# the build tree rather than the source one.
|
||||
ExternalProject_Add_Step(SDL3_image pngHeaders
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${SB_THIRDPARTY}/SDL3_image/external/libpng/png.h ${SB_THIRDPARTY}/SDL3_image/external/libpng/pngconf.h ${SB_PREFIX}/build/SDL3_image/external/libpng-build/pnglibconf.h ${SB_PREFIX}/include
|
||||
DEPENDEES install
|
||||
COMMENT "Installing the libpng headers"
|
||||
)
|
||||
|
||||
singeCmakeProject(SDL3_mixer ${SB_THIRDPARTY}/SDL3_mixer "SDL3"
|
||||
"-DBUILD_SHARED_LIBS=off;-DSDLMIXER_DEPS_SHARED=off;-DSDLMIXER_VENDORED=on;-DSDLMIXER_EXAMPLES=off;-DSDLMIXER_TESTS=off;-DSDLMIXER_FLAC_LIBFLAC=off;-DSDLMIXER_MP3_MPG123=off;-DSDLMIXER_VORBIS_STB=on;-DSDLMIXER_VORBIS_VORBISFILE=off;-DSDLMIXER_VORBIS_TREMOR=off;-DSDLMIXER_GME=off;-DSDLMIXER_MIDI_FLUIDSYNTH=off;${sdl3Dir};-DWAVPACK_ENABLE_ASM=no"
|
||||
"-DBUILD_SHARED_LIBS=off;-DSDLMIXER_DEPS_SHARED=off;-DSDLMIXER_VENDORED=on;-DSDLMIXER_EXAMPLES=off;-DSDLMIXER_TESTS=off;-DSDLMIXER_FLAC_LIBFLAC=off;-DSDLMIXER_MP3_MPG123=off;-DSDLMIXER_VORBIS_STB=on;-DSDLMIXER_VORBIS_VORBISFILE=off;-DSDLMIXER_VORBIS_TREMOR=off;-DSDLMIXER_GME=on;-DSDLMIXER_GME_SHARED=off;-DGME_BUILD_EXAMPLES=off;-DGME_BUILD_TESTING=off;-DGME_YM2612_EMU=MAME;-DSDLMIXER_MIDI_FLUIDSYNTH=off;${sdl3Dir};-DWAVPACK_ENABLE_ASM=no"
|
||||
"${SB_ENV}")
|
||||
|
||||
singeCmakeProject(SDL3_ttf ${SB_THIRDPARTY}/SDL3_ttf "SDL3"
|
||||
"-DBUILD_SHARED_LIBS=off;-DSDLTTF_VENDORED=on;-DSDLTTF_HARFBUZZ=off;-DSDLTTF_PLUTOSVG=off;-DSDLTTF_SAMPLES=off;${sdl3Dir}"
|
||||
singeCmakeProject(SDL3_ttf ${SB_THIRDPARTY}/SDL3_ttf "SDL3;zlib;brotli;SDL3_image"
|
||||
"-DBUILD_SHARED_LIBS=off;-DSDLTTF_VENDORED=on;-DSDLTTF_HARFBUZZ=on;-DSDLTTF_PLUTOSVG=on;-DHB_HAVE_CORETEXT=off;-DSDLTTF_SAMPLES=off;-DFT_DISABLE_ZLIB=off;-DFT_DISABLE_BROTLI=off;-DZLIB_INCLUDE_DIR=${SB_PREFIX}/include;-DZLIB_LIBRARY=${SB_PREFIX}/lib/libz.a;-DBROTLIDEC_INCLUDE_DIRS=${SB_PREFIX}/include;-DBROTLIDEC_LIBRARIES=${SB_PREFIX}/lib/libbrotlidec.a;-DFT_DISABLE_PNG=off;-DPNG_PNG_INCLUDE_DIR=${SB_PREFIX}/include;-DPNG_LIBRARY=${SB_PREFIX}/lib/libpng16.a;${sdl3Dir}"
|
||||
"${SB_ENV}")
|
||||
# SDL3_ttf builds its vendored FreeType but does not install it, and Singe links it directly.
|
||||
ExternalProject_Add_Step(SDL3_ttf freetype
|
||||
|
|
@ -321,6 +342,48 @@ if(SINGE_ROCKCHIP_MPP)
|
|||
message(STATUS "Rockchip MPP will be built and linked statically; the rkmpp decoders are enabled")
|
||||
endif()
|
||||
|
||||
# ===== dav1d, the AV1 decoder =====
|
||||
|
||||
# FFmpeg's own AV1 decoder needs hardware behind it: with no hwaccel it says "Your platform doesn't
|
||||
# support hardware accelerated AV1 decoding" and decodes nothing, so an AV1 video does not play at
|
||||
# all on a machine without an AV1 capable GPU. dav1d is the software decoder, and it brings AVIF
|
||||
# stills with it, an AVIF being an AV1 picture in a HEIF box. It is the one vendored library whose
|
||||
# own build system is Meson rather than CMake or a configure script.
|
||||
set(dav1dBinary ${SB_PREFIX}/build/dav1d)
|
||||
set(dav1dCross "")
|
||||
if(SINGE_ZIG_TARGET)
|
||||
# Meson learns a cross build from a file rather than from the environment, and names both the
|
||||
# system and some architectures its own way. The system name is not cosmetic: Meson picks
|
||||
# nasm's output format from it, and calling Windows "mingw32" -- which is what FFmpeg calls it
|
||||
# -- leaves nasm emitting ELF objects that the Windows linker will not read.
|
||||
if(KANGAROO_OS STREQUAL "windows")
|
||||
set(dav1dSystem windows)
|
||||
elseif(KANGAROO_OS STREQUAL "macos")
|
||||
set(dav1dSystem darwin)
|
||||
else()
|
||||
set(dav1dSystem linux)
|
||||
endif()
|
||||
if(KANGAROO_ARCH STREQUAL "armhf")
|
||||
set(dav1dCpuFamily arm)
|
||||
else()
|
||||
set(dav1dCpuFamily ${KANGAROO_ARCH})
|
||||
endif()
|
||||
set(dav1dCrossFile ${SB_PREFIX}/build/dav1dCross.txt)
|
||||
file(WRITE ${dav1dCrossFile}
|
||||
"[binaries]\nc = '${SB_CC}'\ncpp = '${SB_CXX}'\nar = '${CMAKE_AR}'\nranlib = '${SB_RANLIB}'\nstrip = 'true'\npkg-config = 'pkg-config'\nnasm = 'nasm'\n\n[host_machine]\nsystem = '${dav1dSystem}'\ncpu_family = '${dav1dCpuFamily}'\ncpu = '${KANGAROO_ARCH}'\nendian = 'little'\n")
|
||||
set(dav1dCross --cross-file ${dav1dCrossFile})
|
||||
endif()
|
||||
ExternalProject_Add(dav1d
|
||||
SOURCE_DIR ${SB_THIRDPARTY}/dav1d
|
||||
BINARY_DIR ${dav1dBinary}
|
||||
CONFIGURE_COMMAND ${SB_ENV} meson setup --wipe --prefix=${SB_PREFIX} --libdir=lib --buildtype=release --default-library=static -Denable_tools=false -Denable_tests=false ${dav1dCross} ${dav1dBinary} ${SB_THIRDPARTY}/dav1d
|
||||
BUILD_COMMAND ${SB_ENV} ninja -C ${dav1dBinary}
|
||||
INSTALL_COMMAND ${SB_ENV} ninja -C ${dav1dBinary} install
|
||||
LOG_CONFIGURE ON LOG_BUILD ON LOG_INSTALL ON LOG_OUTPUT_ON_FAILURE ON
|
||||
)
|
||||
singeRebuildTarget(dav1d)
|
||||
|
||||
|
||||
if(KANGAROO_OS STREQUAL "linux")
|
||||
# ARM boards decode through a V4L2 memory-to-memory device; desktops have VAAPI or VDPAU.
|
||||
if(KANGAROO_ARCH MATCHES "^(aarch64|armhf)$")
|
||||
|
|
@ -362,8 +425,8 @@ set(ffmpegBinary ${SB_PREFIX}/build/ffmpeg)
|
|||
ExternalProject_Add(ffmpeg
|
||||
SOURCE_DIR ${SB_THIRDPARTY}/ffmpeg
|
||||
BINARY_DIR ${ffmpegBinary}
|
||||
DEPENDS ${rkmppDep}
|
||||
CONFIGURE_COMMAND ${SB_ENV} ${SB_THIRDPARTY}/ffmpeg/configure --enable-static --disable-shared --disable-debug --disable-muxers ${hwaccel} --disable-encoders --disable-filters --disable-network --disable-devices --disable-vulkan --disable-d3d12va --disable-bzlib --disable-lzma --disable-doc --disable-programs --enable-gpl --enable-version3 --extra-ldflags=-L${SB_PREFIX}/lib --prefix=${SB_PREFIX} --arch=${KANGAROO_ARCH} --target-os=${SINGE_CROSS_OS} ${ffmpegTools}
|
||||
DEPENDS dav1d ${rkmppDep}
|
||||
CONFIGURE_COMMAND ${SB_ENV} ${SB_THIRDPARTY}/ffmpeg/configure --enable-static --disable-shared --disable-debug --disable-muxers ${hwaccel} --disable-encoders --disable-filters --enable-filter=bwdif --enable-filter=yadif --enable-filter=buffer --enable-filter=buffersink --enable-filter=format --disable-network --disable-devices --disable-vulkan --disable-d3d12va --disable-bzlib --disable-lzma --disable-doc --disable-programs --enable-libdav1d --enable-gpl --enable-version3 --extra-ldflags=-L${SB_PREFIX}/lib --prefix=${SB_PREFIX} --arch=${KANGAROO_ARCH} --target-os=${SINGE_CROSS_OS} ${ffmpegTools}
|
||||
BUILD_COMMAND ${SB_ENV} make
|
||||
INSTALL_COMMAND ${SB_ENV} make install
|
||||
LOG_CONFIGURE ON LOG_BUILD ON LOG_INSTALL ON LOG_OUTPUT_ON_FAILURE ON
|
||||
|
|
|
|||
|
|
@ -50,21 +50,36 @@ endif()
|
|||
function(singeZigToolchain target extra)
|
||||
set(dir ${CMAKE_BINARY_DIR}/zig-${target})
|
||||
file(MAKE_DIRECTORY ${dir})
|
||||
# The same triple without its glibc version. zig cc refuses the versioned form when it is only
|
||||
# preprocessing -- "version '.2.28' in target triple 'x86_64-unknown-linux-gnu.2.28' is invalid"
|
||||
# -- and asking a compiler for its predefined macros that way is exactly how Meson identifies
|
||||
# one, so dav1d would not configure. Preprocessing generates no code, so the version it loses
|
||||
# there cannot reach anything built.
|
||||
string(REGEX REPLACE "\\.[0-9].*$" "" targetBase "${target}")
|
||||
# The compiler wrappers drop two flags FFmpeg's configure adds to every test link and zig's linker
|
||||
# driver refuses: binutils' --pic-executable ASLR workaround on Windows and Apple's
|
||||
# -dynamic,-search_paths_first on macOS. They also add
|
||||
set(filter "for a in \"$@\"; do [[ $a == -Wl,--pic-executable* || $a == -Wl,-dynamic* ]] || args+=(\"$a\"); done")
|
||||
# -fno-sanitize=undefined: zig cc instruments for UBSan by default and expects its runtime at
|
||||
# link time; a release build wants neither.
|
||||
# "zig cc -Wl,--version" prints zig's own banner rather than the linker's, and Meson reads that
|
||||
# answer to decide which linker it is driving. The linker really is LLD, and asking it directly
|
||||
# says so in the words Meson knows, so the probe is passed straight through to it.
|
||||
set(version "[[ $a == -Wl,--version ]] && exec \"${ZIG_EXECUTABLE}\" ld.lld --version;")
|
||||
set(filter "for a in \"$@\"; do ${version} [[ $a == -Wl,--pic-executable* || $a == -Wl,-dynamic* ]] || args+=(\"$a\"); [[ $a == -E ]] && target=\"-target ${targetBase}\"; done")
|
||||
# extra goes after the caller's arguments so the caller's -L directories are searched first.
|
||||
# ld.lld and objcopy are here for vendored projects that shell out to a linker or a strip of
|
||||
# their own rather than going through the compiler driver: Rockchip's MPP merges its archives
|
||||
# with "ld -r" and then strips the result, and without these it would reach for the host's.
|
||||
foreach(tool "cc;-target ${target} -fno-sanitize=undefined;${extra}" "c++;-target ${target} -fno-sanitize=undefined;${extra}" "ar;;" "ranlib;;" "rc;;" "ld.lld;;")
|
||||
foreach(tool "cc;$target -fno-sanitize=undefined;${extra};yes" "c++;$target -fno-sanitize=undefined;${extra};yes" "ar;;;no" "ranlib;;;no" "rc;;;no" "ld.lld;;;no")
|
||||
list(GET tool 0 name)
|
||||
list(GET tool 1 args)
|
||||
list(GET tool 2 after)
|
||||
file(WRITE ${dir}/zig-${name} "#!/bin/bash\nargs=()\n${filter}\nexec \"${ZIG_EXECUTABLE}\" ${name} ${args} \"$\{args[@]}\" ${after}\n")
|
||||
list(GET tool 3 compiler)
|
||||
if(compiler STREQUAL "yes")
|
||||
file(WRITE ${dir}/zig-${name} "#!/bin/bash\nargs=()\ntarget=\"-target ${target}\"\n${filter}\nexec \"${ZIG_EXECUTABLE}\" ${name} ${args} \"$\{args[@]}\" ${after}\n")
|
||||
else()
|
||||
file(WRITE ${dir}/zig-${name} "#!/bin/bash\nargs=()\n${filter}\nexec \"${ZIG_EXECUTABLE}\" ${name} ${args} \"$\{args[@]}\" ${after}\n")
|
||||
endif()
|
||||
file(CHMOD ${dir}/zig-${name} PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE)
|
||||
endforeach()
|
||||
set(CMAKE_C_COMPILER ${dir}/zig-cc CACHE FILEPATH "C compiler" FORCE)
|
||||
|
|
|
|||
586
docs/Manual.adoc
586
docs/Manual.adoc
|
|
@ -420,6 +420,7 @@ name and any extension FFmpeg can demux, then for a `.txt` framefile.
|
|||
| `--absolutes_only` | Keep only the mice that report an absolute position, which is what a real light gun does and an ordinary mouse does not. ManyMouse cannot be asked what a device is, so a device counts as absolute once it has reported an absolute position and not before; see <<mousedevices,Mice, Guns, and Who Chooses>>. Hypseus writes it `-absolutes-only`; the name here uses an underscore because a settings file key has to be a Lua name. Default: off.
|
||||
| `--altaudio=SUFFIX` | Play `<base><SUFFIX>.ogg` beside the disc's video instead of the audio inside it, for a release whose other languages ship as separate files: `--altaudio=-es` next to `lair.m2v` plays `lair-es.ogg`. Every segment of a framefile is switched together. A file that is not there leaves the game's own audio playing and prints a warning. The `AUDIO_SUFFIX` key in `games.dat` does the same for one game, and a script changes it while running with `discAudioSuffix`. Default: none.
|
||||
| `--apiversion` | Print one machine readable line describing this build to standard output and exit, for front ends. See <<apiversion,The Version Line>>. Nothing else is printed.
|
||||
| `--deinterlace=MODE` | What an interlaced picture gets: `auto` (the default) deinterlaces only the frames the file marks interlaced, `on` deinterlaces every frame for a file whose flags are wrong, and `off` never touches the picture. A laserdisc held interlaced fields and a rip that kept them combs on a progressive display; a rip that was deinterlaced when it was made needs nothing here. One picture comes out for each one that goes in, so frame numbers never move. Default: `auto`.
|
||||
| `--deterministic[=MS]` | For testing only: ignore real time and run the whole engine on a virtual clock that moves `MS` milliseconds every frame, so the same frame number always means the same moment. The disc steps one video frame a frame with it, and the random generators are seeded from the same number, so a run repeats to the pixel. The value is optional and is both the step and the seed, `1` to `1000`. Default when given without one: `15`, the frame time the engine's own rate implies. Audio and pacing are meaningless in this mode; see <<deterministic,Deterministic Test Mode>>. Default: off.
|
||||
| `--fvalue=NUMBER` | One number handed from the launcher to the game, which reads it with `getFValue()`. Singe does nothing with it. `0` to `100000`, kept to three decimals as Hypseus keeps it. Default: `0`.
|
||||
| `--gamepad_reorder=DIGITS` | Which physical pad fills which gamepad slot, as enumeration positions counting from `0`, one for each slot in turn: `--gamepad_reorder=10` makes the second pad found player one and the first player two. Written as bare digits (`3210`) or separated by commas or spaces, as Hypseus writes it; a repeated position is refused. Positions not named fill the slots that are left, in the order SDL found them, and a position with no pad behind it leaves its slot empty. Default: SDL's own order.
|
||||
|
|
@ -434,6 +435,7 @@ name and any extension FFmpeg can demux, then for a `.txt` framefile.
|
|||
| `--monochrome` | Start with the disc picture in grey. The overlay, the GUIs and the 3D scene keep their colour; a script does the same with `vldpSetMonochrome`, and undoes it. Default: off.
|
||||
| `--nogamepad` | Ignore every gamepad, the counterpart of `--nomouse`. None is opened and no pad event reaches the game, so a stuck arcade encoder cannot press anything. Default: off.
|
||||
| `--screen=N` | Open the window on display `N`, counting from `1` as Hypseus counts them. A number larger than the number of displays lists the displays that are there and exits. Default: the primary display.
|
||||
| `--soundfont=FILE` | The SoundFont (`.sf2`) MIDI files are synthesised with. Singe ships none, because a good one is tens of megabytes for a format almost no game uses. Without this option it looks for `Singe/soundfont.sf2` -- so a packed game can carry its own -- and then in the places a distribution installs one. The `SoundFont:` line of the trace header says which was used, or that none was found. See <<midi,MIDI>>. Default: the search.
|
||||
| `--startsilent` | Start muted and stay muted until the first input of any kind, then play at the configured volumes. For an attract cabinet in a quiet room. `--nosound` outranks it. Default: off.
|
||||
| `--trigger_threshold=PERCENT` | How far an analogue trigger must travel before it counts as a button, as a per cent of full travel -- Hypseus's unit, whose own default is `99.5`. `0` leaves the triggers on `DEAD_ZONE`, which is what Singe has always given them. Wins over `TRIGGER_THRESHOLD` in `controls.cfg`; `DEAD_ZONES` wins over both. `0` to `100`; see <<deadzones,Dead Zones and Triggers>>. Default: `0`.
|
||||
| `--xratio=FACTOR` | The horizontal scale a light gun game reads with `ratioGetX()` to stretch its own gun coordinates for a display whose shape does not match the video. Singe does nothing with it; the game does the arithmetic, exactly as in Hypseus. `0` to `100`, kept to two decimals as Hypseus keeps it. Default: `0`, which every game that reads it treats as `1`.
|
||||
|
|
@ -3194,23 +3196,31 @@ can absorb. And when the first video frame is timestamped later than the first
|
|||
audio sample, Singe honours the container's timing, so leading audio is not
|
||||
lost.
|
||||
|
||||
[#formats]
|
||||
=== Video, Audio, and Container Formats
|
||||
|
||||
Singe decodes video with FFmpeg's libraries directly, using the platform's hardware
|
||||
decoder when it offers one for the codec (VA-API or VDPAU on Linux, D3D11VA on
|
||||
Windows, VideoToolbox on macOS, and on the Pi the V4L2 memory-to-memory decoder,
|
||||
which on a Pi 4 covers H.264; a Pi 5 has no H.264 decoder and its HEVC
|
||||
decoder needs a kind of driver FFmpeg does not yet ship, so encode Pi games
|
||||
as H.264 and expect software decoding on a Pi 5) and its own software
|
||||
Windows, VideoToolbox on macOS, and on an ARM board the V4L2 decoders -- the
|
||||
memory-to-memory one, which on a Pi 4 covers H.264, the stateless Request API
|
||||
one a Pi 5 uses for HEVC, and Rockchip's own on an RK3588) and its own software
|
||||
decoder otherwise; the program trace reports which, and `--softwarevideo` forces
|
||||
software. Any container
|
||||
and codec the
|
||||
bundled FFmpeg can demux and decode will play: MP4, MKV, MPEG program streams,
|
||||
AVI, and the classic Daphne `.m2v` elementary streams with a matching `.ogg`
|
||||
AVI, AV1 and the classic Daphne `.m2v` elementary streams with a matching `.ogg`
|
||||
audio file next to them. Every audio track in the file is available to
|
||||
`discSetAudioTrack` / `videoSetAudioTrack`; all tracks must share one sample
|
||||
format, channel count, and rate.
|
||||
|
||||
A laserdisc held interlaced fields, and a rip that kept them combs on every
|
||||
progressive display. `--deinterlace` decides what happens: `auto`, the default,
|
||||
runs bwdif over the frames the file marks interlaced and leaves everything else
|
||||
alone, so a progressive disc pays nothing; `on` is for a file whose flags are
|
||||
wrong; `off` never touches the picture. One picture comes out for each one that
|
||||
goes in -- a disc is addressed by frame number, so a deinterlacer that turned
|
||||
each frame into two fields would move every frame in the index.
|
||||
|
||||
The first time a video is opened, Singe indexes it and stores the index next
|
||||
to the game's other data (`<name>-<hash>.index`, the hash made from the
|
||||
video's full name so two videos with one base name keep separate indexes). Indexing is one pass over the file
|
||||
|
|
@ -3222,6 +3232,12 @@ has to decode forward from the previous keyframe. Decoding happens on a separate
|
|||
so a slow seek shows the previous frame a little longer instead of stalling
|
||||
the game.
|
||||
|
||||
A framefile's segments are opened as the disc reaches them, not all at once: the
|
||||
one playing, the one after it, and room for a seek to move between two. A long
|
||||
framefile can have hundreds of segments -- one released game has 213 -- and
|
||||
holding a decoder open for every one of them costs hundreds of open files for no
|
||||
gain. A framefile that names a file which is not there still says so at startup.
|
||||
|
||||
For laserdisc footage the constraints are frame accuracy and seek speed, not
|
||||
compression. H.264 in MP4 or MKV with a short keyframe interval (one or two
|
||||
seconds) seeks quickly and plays on every supported platform; long keyframe
|
||||
|
|
@ -3231,9 +3247,34 @@ spot. High definition sources work but cost proportionally more CPU on
|
|||
Raspberry Pi class hardware, and the overlay defaults to half the video
|
||||
resolution, so oversized video buys little.
|
||||
|
||||
Sound effects go through SDL_mixer: WAV, OGG, FLAC, MP3, Opus, WavPack, and
|
||||
tracker modules are supported. Short uncompressed WAV files give the lowest
|
||||
latency.
|
||||
Sound effects and music are read by SDL_mixer first and by FFmpeg for anything
|
||||
it turns down, so between them they cover:
|
||||
|
||||
* WAV, AIFF, AU and VOC for uncompressed audio. Short uncompressed WAV files
|
||||
give the lowest latency, and are what a gunshot should be.
|
||||
* Ogg Vorbis, Opus, MP3, FLAC and WavPack.
|
||||
* AAC (`.m4a`, `.aac`), ALAC, AC-3, WMA, APE, TTA, Speex and AMR -- everything
|
||||
else the bundled FFmpeg decodes.
|
||||
* Tracker modules through libxmp: MOD, S3M, XM and IT, and fifty-odd others
|
||||
including 669, AMF, MED, OKT, PTM, STM and ULT.
|
||||
* Chiptunes through game-music-emu: NSF and NSFE, SPC, VGM, GBS, AY, GYM, HES,
|
||||
KSS and SAP. The gzipped VGZ form is not read; ungzip it to VGM first.
|
||||
* MIDI, synthesised from a SoundFont. See <<midi,MIDI>>.
|
||||
|
||||
The `Audio:` line of the trace header (`--program` or `--trace`) lists the
|
||||
decoders the build actually carries.
|
||||
|
||||
Pictures work the same way: SDL_image first, FFmpeg for the rest. That covers
|
||||
PNG, JPEG, WebP, GIF, BMP, TGA, PCX, PNM, LBM, QOI, XCF, XPM and SVG from
|
||||
SDL_image, and TIFF, OpenEXR, JPEG 2000, DDS, PSD, DPX, AVIF and HEIC from
|
||||
FFmpeg. An animated GIF or WEBP loads as a sprite animation; see
|
||||
<<spriteload,`spriteLoad`>>, which also takes a size for rasterising an SVG.
|
||||
Radiance `.hdr` and OpenEXR both work as a sky (<<scenesetsky,`sceneSetSky`>>)
|
||||
and keep their full range.
|
||||
|
||||
Fonts are read by FreeType: TrueType, OpenType, WOFF, WOFF2, Type 1, BDF and
|
||||
PCF, with colour glyphs (both the bitmap and the vector kind) and HarfBuzz
|
||||
shaping for Arabic, Hebrew and the Indic scripts.
|
||||
|
||||
[#reference]
|
||||
== Lua API Reference
|
||||
|
|
@ -5094,6 +5135,53 @@ end
|
|||
----
|
||||
|
||||
[#discgetframe]
|
||||
==== discGetSubtitleLanguage
|
||||
|
||||
[source,text]
|
||||
----
|
||||
language = discGetSubtitleLanguage(track)
|
||||
----
|
||||
|
||||
The language a subtitle track inside the disc's own container is labelled with, as the ISO 639 code the file carries (`eng`, `fra`, `spa`). A track the file labels with nothing answers an empty string, which is not an error -- plenty of files label nothing.
|
||||
|
||||
*Parameters:*
|
||||
|
||||
* `track` -- number; `0` to `discGetSubtitleTracks() - 1`.
|
||||
|
||||
*Returns:* a string.
|
||||
|
||||
*Since:* 3.00.
|
||||
*See also:* <<discgetsubtitletracks,discGetSubtitleTracks>>, <<srtloadtrack,srtLoadTrack>>
|
||||
|
||||
==== discGetSubtitleTracks
|
||||
|
||||
[source,text]
|
||||
----
|
||||
count = discGetSubtitleTracks()
|
||||
----
|
||||
|
||||
How many subtitle tracks the disc's own container holds. `0` with no disc, and `0` for a disc whose subtitles ship as a separate `.srt` -- which is the usual arrangement, and what <<srtload,`srtLoad`>> is for.
|
||||
|
||||
Tracks that hold pictures rather than words (the DVD and Blu-ray kinds, VobSub and PGS) are counted here so the numbering matches what any other player shows, but <<srtloadtrack,`srtLoadTrack`>> cannot read them: there is nothing to read, only bitmaps of the words.
|
||||
|
||||
*Parameters:*
|
||||
|
||||
* None.
|
||||
|
||||
*Returns:* a number.
|
||||
|
||||
*Since:* 3.00.
|
||||
*See also:* <<srtloadtrack,srtLoadTrack>>, <<discgetsubtitlelanguage,discGetSubtitleLanguage>>
|
||||
|
||||
.Example
|
||||
[source,lua]
|
||||
----
|
||||
-- Offer whatever languages the disc itself carries.
|
||||
for track = 0, discGetSubtitleTracks() - 1 do
|
||||
languages[#languages + 1] = { track = track, code = discGetSubtitleLanguage(track) }
|
||||
end
|
||||
----
|
||||
|
||||
==== discGetFrame
|
||||
|
||||
[source,text]
|
||||
|
|
@ -8674,6 +8762,395 @@ nodeSetRotation(ring, 90, 0, 0)
|
|||
----
|
||||
|
||||
[#model]
|
||||
[#midi]
|
||||
=== MIDI
|
||||
|
||||
Two unrelated things share this name, and it is worth keeping them apart.
|
||||
|
||||
A **MIDI file** is music, and it plays like any other music: hand `.mid` to
|
||||
<<musicload,`musicLoad`>>. Singe synthesises it from a SoundFont, because a MIDI
|
||||
file holds notes rather than sound. It ships no SoundFont -- a good one is tens
|
||||
of megabytes for a format almost no game uses -- so name one with
|
||||
`--soundfont`, put one at `Singe/soundfont.sf2` so a packed game carries its
|
||||
own, or let Singe find the one your distribution installed. The `SoundFont:`
|
||||
line of the trace header says which was used. With no SoundFont anywhere,
|
||||
loading a `.mid` fails and says so.
|
||||
|
||||
**MIDI ports** are the calls below. They send and receive live messages on a
|
||||
real port: a cabinet driving an external sound module, or a keyboard, a fader
|
||||
box or a control surface read as another input device. Nothing here synthesises
|
||||
anything, and nothing here touches a MIDI file.
|
||||
|
||||
Ports are numbered from `0`. Nothing is opened until a script first asks -- a
|
||||
MIDI port costs a file descriptor and a round of driver configuration, and a
|
||||
game that never mentions MIDI never pays for it -- so the first of these calls
|
||||
is what takes stock of the machine, and <<midirescan,`midiRescan`>> looks again
|
||||
for a device plugged in since. One input
|
||||
port and one output port may be open at a time. Channels are `1` to `16` as a
|
||||
person counts them, not `0` to `15` as the wire does, and every other value is
|
||||
`0` to `127`. On a machine with no MIDI at all -- no ports, or no MIDI library
|
||||
installed -- the counts are `0`, the open calls answer `false`, and the sending
|
||||
calls answer `false`; nothing fails.
|
||||
|
||||
Linux reaches every port on the ALSA sequencer, which is both the hardware
|
||||
interfaces and any synthesiser or other program registering one. macOS uses
|
||||
CoreMIDI and Windows the multimedia MIDI calls, where output device `0` is
|
||||
usually the synthesiser the system itself provides.
|
||||
|
||||
==== midiCloseInput
|
||||
|
||||
[source,text]
|
||||
----
|
||||
midiCloseInput()
|
||||
----
|
||||
|
||||
Stops listening to the input port. <<onmidimessage,`onMidiMessage`>> stops firing. Closing a port that is not open does nothing.
|
||||
|
||||
*Parameters:*
|
||||
|
||||
* None.
|
||||
|
||||
*Returns:* nothing.
|
||||
|
||||
*Since:* 3.00.
|
||||
*See also:* <<midiopeninput,midiOpenInput>>
|
||||
|
||||
==== midiCloseOutput
|
||||
|
||||
[source,text]
|
||||
----
|
||||
midiCloseOutput()
|
||||
----
|
||||
|
||||
Releases the output port. The sending calls answer `false` from now on. Closing a port that is not open does nothing.
|
||||
|
||||
*Parameters:*
|
||||
|
||||
* None.
|
||||
|
||||
*Returns:* nothing.
|
||||
|
||||
*Since:* 3.00.
|
||||
*See also:* <<midiopenoutput,midiOpenOutput>>
|
||||
|
||||
==== midiControlChange
|
||||
|
||||
[source,text]
|
||||
----
|
||||
sent = midiControlChange(channel, control, value)
|
||||
----
|
||||
|
||||
Moves a controller on the open output port: volume is controller `7`, pan is `10`, the sustain pedal is `64`.
|
||||
|
||||
*Parameters:*
|
||||
|
||||
* `channel` -- number; `1` to `16`.
|
||||
* `control` -- number; `0` to `127`.
|
||||
* `value` -- number; `0` to `127`.
|
||||
|
||||
*Returns:* `true` when the message went out.
|
||||
|
||||
*Since:* 3.00.
|
||||
*See also:* <<midisend,midiSend>>
|
||||
|
||||
.Example
|
||||
[source,lua]
|
||||
----
|
||||
-- Duck the module's volume while a character speaks.
|
||||
midiControlChange(1, 7, 40)
|
||||
----
|
||||
|
||||
==== midiInputCount
|
||||
|
||||
[source,text]
|
||||
----
|
||||
count = midiInputCount()
|
||||
----
|
||||
|
||||
How many MIDI input ports this machine has. `0` when there are none, or when the platform has no MIDI at all.
|
||||
|
||||
*Parameters:*
|
||||
|
||||
* None.
|
||||
|
||||
*Returns:* a number.
|
||||
|
||||
*Since:* 3.00.
|
||||
*See also:* <<midiinputname,midiInputName>>
|
||||
|
||||
==== midiInputName
|
||||
|
||||
[source,text]
|
||||
----
|
||||
name = midiInputName(index)
|
||||
----
|
||||
|
||||
The name of an input port, for a settings screen that lets a player choose one.
|
||||
|
||||
*Parameters:*
|
||||
|
||||
* `index` -- number; `0` to `midiInputCount() - 1`.
|
||||
|
||||
*Returns:* a string, or `nil` when the index names no port.
|
||||
|
||||
*Since:* 3.00.
|
||||
*See also:* <<midiinputcount,midiInputCount>>
|
||||
|
||||
.Example
|
||||
[source,lua]
|
||||
----
|
||||
for i = 0, midiInputCount() - 1 do
|
||||
printOverlay(midiInputName(i))
|
||||
end
|
||||
----
|
||||
|
||||
==== midiIsInputOpen
|
||||
|
||||
[source,text]
|
||||
----
|
||||
open = midiIsInputOpen()
|
||||
----
|
||||
|
||||
Whether an input port is open.
|
||||
|
||||
*Parameters:*
|
||||
|
||||
* None.
|
||||
|
||||
*Returns:* `true` or `false`.
|
||||
|
||||
*Since:* 3.00.
|
||||
*See also:* <<midiopeninput,midiOpenInput>>
|
||||
|
||||
==== midiIsOutputOpen
|
||||
|
||||
[source,text]
|
||||
----
|
||||
open = midiIsOutputOpen()
|
||||
----
|
||||
|
||||
Whether an output port is open. Worth asking before a burst of messages, since each one would otherwise answer `false` separately.
|
||||
|
||||
*Parameters:*
|
||||
|
||||
* None.
|
||||
|
||||
*Returns:* `true` or `false`.
|
||||
|
||||
*Since:* 3.00.
|
||||
*See also:* <<midiopenoutput,midiOpenOutput>>
|
||||
|
||||
==== midiNoteOff
|
||||
|
||||
[source,text]
|
||||
----
|
||||
sent = midiNoteOff(channel, key)
|
||||
----
|
||||
|
||||
Releases a note on the open output port. A note left on sounds until something stops it, so every `midiNoteOn` needs one of these.
|
||||
|
||||
*Parameters:*
|
||||
|
||||
* `channel` -- number; `1` to `16`.
|
||||
* `key` -- number; `0` to `127`, where `60` is middle C.
|
||||
|
||||
*Returns:* `true` when the message went out.
|
||||
|
||||
*Since:* 3.00.
|
||||
*See also:* <<midinoteon,midiNoteOn>>
|
||||
|
||||
==== midiNoteOn
|
||||
|
||||
[source,text]
|
||||
----
|
||||
sent = midiNoteOn(channel, key, velocity)
|
||||
----
|
||||
|
||||
Sounds a note on the open output port.
|
||||
|
||||
*Parameters:*
|
||||
|
||||
* `channel` -- number; `1` to `16`. Channel `10` is percussion on a General MIDI device.
|
||||
* `key` -- number; `0` to `127`, where `60` is middle C.
|
||||
* `velocity` -- number; `0` to `127`, how hard the key was struck.
|
||||
|
||||
*Returns:* `true` when the message went out.
|
||||
|
||||
*Since:* 3.00.
|
||||
*See also:* <<midinoteoff,midiNoteOff>>
|
||||
|
||||
.Example
|
||||
[source,lua]
|
||||
----
|
||||
-- A stinger on an external module when the player is hit.
|
||||
midiNoteOn(10, 49, 110)
|
||||
midiNoteOff(10, 49)
|
||||
----
|
||||
|
||||
==== midiOpenInput
|
||||
|
||||
[source,text]
|
||||
----
|
||||
opened = midiOpenInput(index)
|
||||
----
|
||||
|
||||
Listens to one input port. <<onmidimessage,`onMidiMessage`>> then fires for every message that arrives. Whichever input port was open is closed first.
|
||||
|
||||
*Parameters:*
|
||||
|
||||
* `index` -- number; `0` to `midiInputCount() - 1`.
|
||||
|
||||
*Returns:* `true` when the port opened.
|
||||
|
||||
*Since:* 3.00.
|
||||
*See also:* <<midicloseinput,midiCloseInput>>, <<onmidimessage,onMidiMessage>>
|
||||
|
||||
.Example
|
||||
[source,lua]
|
||||
----
|
||||
if midiInputCount() > 0 then
|
||||
midiOpenInput(0)
|
||||
end
|
||||
----
|
||||
|
||||
==== midiOpenOutput
|
||||
|
||||
[source,text]
|
||||
----
|
||||
opened = midiOpenOutput(index)
|
||||
----
|
||||
|
||||
Takes one output port for the sending calls. Whichever output port was open is closed first.
|
||||
|
||||
*Parameters:*
|
||||
|
||||
* `index` -- number; `0` to `midiOutputCount() - 1`.
|
||||
|
||||
*Returns:* `true` when the port opened.
|
||||
|
||||
*Since:* 3.00.
|
||||
*See also:* <<midicloseoutput,midiCloseOutput>>
|
||||
|
||||
==== midiOutputCount
|
||||
|
||||
[source,text]
|
||||
----
|
||||
count = midiOutputCount()
|
||||
----
|
||||
|
||||
How many MIDI output ports this machine has.
|
||||
|
||||
*Parameters:*
|
||||
|
||||
* None.
|
||||
|
||||
*Returns:* a number.
|
||||
|
||||
*Since:* 3.00.
|
||||
*See also:* <<midioutputname,midiOutputName>>
|
||||
|
||||
==== midiOutputName
|
||||
|
||||
[source,text]
|
||||
----
|
||||
name = midiOutputName(index)
|
||||
----
|
||||
|
||||
The name of an output port.
|
||||
|
||||
*Parameters:*
|
||||
|
||||
* `index` -- number; `0` to `midiOutputCount() - 1`.
|
||||
|
||||
*Returns:* a string, or `nil` when the index names no port.
|
||||
|
||||
*Since:* 3.00.
|
||||
*See also:* <<midioutputcount,midiOutputCount>>
|
||||
|
||||
==== midiPitchBend
|
||||
|
||||
[source,text]
|
||||
----
|
||||
sent = midiPitchBend(channel, value)
|
||||
----
|
||||
|
||||
Bends a channel. `8192` is the middle, meaning no bend; how far `0` and `16383` reach is the receiving device's own setting, usually two semitones.
|
||||
|
||||
*Parameters:*
|
||||
|
||||
* `channel` -- number; `1` to `16`.
|
||||
* `value` -- number; `0` to `16383`.
|
||||
|
||||
*Returns:* `true` when the message went out.
|
||||
|
||||
*Since:* 3.00.
|
||||
|
||||
==== midiProgramChange
|
||||
|
||||
[source,text]
|
||||
----
|
||||
sent = midiProgramChange(channel, program)
|
||||
----
|
||||
|
||||
Chooses the sound a channel plays. The numbers are the receiving device's; on a General MIDI device `0` is a grand piano.
|
||||
|
||||
*Parameters:*
|
||||
|
||||
* `channel` -- number; `1` to `16`.
|
||||
* `program` -- number; `0` to `127`.
|
||||
|
||||
*Returns:* `true` when the message went out.
|
||||
|
||||
*Since:* 3.00.
|
||||
|
||||
==== midiRescan
|
||||
|
||||
[source,text]
|
||||
----
|
||||
midiRescan()
|
||||
----
|
||||
|
||||
Looks for ports again, for a device plugged in while the game is running. An open port stays open. Indexes are only as stable as the list, so read the names again after this rather than assuming port `2` is still what it was.
|
||||
|
||||
*Parameters:*
|
||||
|
||||
* None.
|
||||
|
||||
*Returns:* nothing.
|
||||
|
||||
*Since:* 3.00.
|
||||
*See also:* <<midiinputcount,midiInputCount>>, <<midioutputcount,midiOutputCount>>
|
||||
|
||||
==== midiSend
|
||||
|
||||
[source,text]
|
||||
----
|
||||
sent = midiSend(byte [, byte [, byte]])
|
||||
sent = midiSend(string)
|
||||
----
|
||||
|
||||
Any message at all, for the ones the named calls do not cover. Given numbers it sends them as they are, which is a status byte and up to two data bytes -- and note that the status byte carries the channel in its low four bits, counting from `0`, unlike every other call here. Given a string it sends the whole string, which is how a system exclusive message goes out.
|
||||
|
||||
*Parameters:*
|
||||
|
||||
* `byte` -- number; `0` to `255`, one to three of them.
|
||||
* `string` -- a string of bytes, sent unchanged.
|
||||
|
||||
*Returns:* `true` when the message went out.
|
||||
|
||||
*Since:* 3.00.
|
||||
*See also:* <<midinoteon,midiNoteOn>>, <<midicontrolchange,midiControlChange>>
|
||||
|
||||
.Example
|
||||
[source,lua]
|
||||
----
|
||||
-- All notes off on channel 1, which has no named call.
|
||||
midiSend(0xB0, 123, 0)
|
||||
|
||||
-- A system exclusive message, bytes and all.
|
||||
midiSend(string.char(0xF0, 0x7E, 0x7F, 0x09, 0x01, 0xF7))
|
||||
----
|
||||
|
||||
=== Model
|
||||
|
||||
Models are glTF 2.0 binary files (`.glb`) loaded once and placed any number
|
||||
|
|
@ -14644,15 +15121,24 @@ end
|
|||
[source,text]
|
||||
----
|
||||
result = spriteLoad(filename)
|
||||
result = spriteLoad(filename, width, height)
|
||||
----
|
||||
|
||||
Loads an image and returns an integer handle for the other `sprite*` calls. The name goes through the engine's virtual file system, so it may live loose on disk or inside a packed game; it is resolved relative to the directory Singe was started in, so prepend `DIR` for files shipped with your game. An animated GIF or WEBP with two or more frames loads as an animation, parked on frame `0`, not playing and not looping; every other file, including a single-frame GIF, loads as a still image. An image with more than 8 bits per channel, or floating point pixels, is converted to 8-bit RGBA on load and treated as one from then on. Pixels with a raw value of `0` in the file's own pixel format become transparent. Loading is synchronous, and a file that cannot be opened or decoded ends the script with the loader's error message rather than returning `nil`.
|
||||
Loads an image and returns an integer handle for the other `sprite*` calls. The name goes through the engine's virtual file system, so it may live loose on disk or inside a packed game; it is resolved relative to the directory Singe was started in, so prepend `DIR` for files shipped with your game. Every format in <<formats,Video, Audio, and Container Formats>> is read. An animated GIF or WEBP with two or more frames loads as an animation, parked on frame `0`, not playing and not looping; every other file, including a single-frame GIF, loads as a still image. An image with more than 8 bits per channel, or floating point pixels, is converted to 8-bit RGBA on load and treated as one from then on. Pixels with a raw value of `0` in the file's own pixel format become transparent. Loading is synchronous, and a file that cannot be opened or decoded ends the script with the loader's error message rather than returning `nil`.
|
||||
|
||||
The optional size is for a vector picture -- an SVG -- which has no size of its own worth having: the picture is rasterised to fit that box, keeping its own proportions, rather than rasterised at the file's size and scaled up. Ask <<spritegetwidth,`spriteGetWidth`>> and <<spritegetheight,`spriteGetHeight`>> what it came out as, because fitting a square drawing into a wide box leaves the width unused. Every other format ignores the size.
|
||||
|
||||
*Parameters:*
|
||||
|
||||
* `filename` -- string; the image to load.
|
||||
* `width` -- optional number; the width to fit an SVG into, `1` or more.
|
||||
* `height` -- optional number; the height to fit an SVG into, `1` or more. Both must be given together.
|
||||
|
||||
*Returns:* integer sprite handle.
|
||||
|
||||
*Notes:* Animations do not start by themselves. Call `spriteLoop(id, true)` and `spritePlay(id)` after loading if the animation should run and repeat.
|
||||
|
||||
*Since:* 1.x. Animated GIF/WEBP support added in 2.10.
|
||||
*Since:* 1.x. Animated GIF/WEBP support added in 2.10; the vector size in 3.00.
|
||||
*See also:* <<spriteunload,spriteUnload>>, <<spriteplay,spritePlay>>, <<spriteloop,spriteLoop>>, <<fonttosprite,fontToSprite>>
|
||||
|
||||
.Example
|
||||
|
|
@ -15161,6 +15647,40 @@ end
|
|||
----
|
||||
|
||||
[#srtposition]
|
||||
==== srtLoadTrack
|
||||
|
||||
[source,text]
|
||||
----
|
||||
loaded = srtLoadTrack(track)
|
||||
----
|
||||
|
||||
The same as <<srtload,`srtLoad`>>, except that the subtitles come from inside the disc's own container rather than from a `.srt` file beside it. Everything after loading is identical: the cues become disc frame numbers at the disc's frame rate, so they are found again after any seek, and <<srtenable,`srtEnable`>>, <<srtposition,`srtPosition`>> and <<srtclear,`srtClear`>> work the same.
|
||||
|
||||
The whole file is walked once to collect the track, decoding no pictures, so a long disc takes a moment -- do it while something else is on screen rather than between two frames of gameplay.
|
||||
|
||||
A track that holds pictures rather than words (VobSub or PGS) cannot be read: it answers `false`, as an unreadable `.srt` does, and leaves the subtitles cleared. So does a track number that names nothing, and so does a game with no disc.
|
||||
|
||||
*Parameters:*
|
||||
|
||||
* `track` -- number; `0` to `discGetSubtitleTracks() - 1`.
|
||||
|
||||
*Returns:* `true` when at least one cue was read.
|
||||
|
||||
*Since:* 3.00.
|
||||
*See also:* <<srtload,srtLoad>>, <<discgetsubtitletracks,discGetSubtitleTracks>>, <<srtenable,srtEnable>>
|
||||
|
||||
.Example
|
||||
[source,lua]
|
||||
----
|
||||
-- Subtitles the rip carried, with a .srt beside the game as the fallback.
|
||||
if discGetSubtitleTracks() > 0 then
|
||||
srtLoadTrack(0)
|
||||
else
|
||||
srtLoad(DIR .. "subtitles.srt")
|
||||
end
|
||||
srtEnable(true)
|
||||
----
|
||||
|
||||
==== srtPosition
|
||||
|
||||
[source,text]
|
||||
|
|
@ -17144,6 +17664,52 @@ end
|
|||
----
|
||||
|
||||
[#onmousemoved]
|
||||
==== onMidiMessage
|
||||
|
||||
[source,text]
|
||||
----
|
||||
function onMidiMessage(status, data1, data2, bytes)
|
||||
end
|
||||
----
|
||||
|
||||
Called for every message that arrives on the MIDI input port
|
||||
<<midiopeninput,`midiOpenInput`>> opened, once per message, with the rest of the
|
||||
frame's input. The three numbers are the message as a device sends it: `status`
|
||||
carries the kind in its high four bits and the channel in its low four, counting
|
||||
from `0`, so a note on channel 1 arrives as `0x90`. `bytes` is the whole message
|
||||
as a string, which is what a system exclusive message needs and what a message
|
||||
longer than three bytes has to be read from.
|
||||
|
||||
Messages that arrive while no port is open are not queued; nothing is delivered
|
||||
from before the port was opened.
|
||||
|
||||
*Parameters:*
|
||||
|
||||
* `status` -- number; the status byte, `0x80` to `0xFF`.
|
||||
* `data1` -- number; the first data byte, or `0` when the message has none.
|
||||
* `data2` -- number; the second data byte, or `0` when the message has none.
|
||||
* `bytes` -- string; the whole message, however long it is.
|
||||
|
||||
*Since:* 3.00.
|
||||
*See also:* <<midiopeninput,midiOpenInput>>, <<midisend,midiSend>>
|
||||
|
||||
.Example
|
||||
[source,lua]
|
||||
----
|
||||
-- A MIDI keyboard plays the game: middle C and up are the four fire buttons.
|
||||
local MIDI_NOTE_ON = 0x90
|
||||
|
||||
function onMidiMessage(status, data1, data2, bytes)
|
||||
-- The kind is the top four bits; the channel in the bottom four is ignored here.
|
||||
if (status & 0xF0) == MIDI_NOTE_ON and data2 > 0 then
|
||||
local button = data1 - 60
|
||||
if button >= 0 and button <= 3 then
|
||||
fire(button)
|
||||
end
|
||||
end
|
||||
end
|
||||
----
|
||||
|
||||
==== onMouseMoved
|
||||
|
||||
[source,text]
|
||||
|
|
|
|||
578
src/decode.c
Normal file
578
src/decode.c
Normal file
|
|
@ -0,0 +1,578 @@
|
|||
/*
|
||||
*
|
||||
* Singe 3
|
||||
* Copyright (C) 2006-2026 Scott Duensing <scott@kangaroopunch.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
// Singe: the fallback decoder. SDL_image and SDL_mixer each read a fixed set of formats, and the
|
||||
// FFmpeg linked in for video reads a great many more, so anything they turn down is offered here
|
||||
// before the caller gives up. That is how a TIFF, an OpenEXR sky, a JPEG 2000 or an AAC sound
|
||||
// effect loads: not by giving SDL_image or SDL_mixer another backend, but by asking the decoder
|
||||
// that was in the binary all along. (AVIF is the exception that proves it -- an AVIF is an AV1
|
||||
// picture, and FFmpeg's own AV1 decoder needs hardware, so that one did cost a vendored dav1d.)
|
||||
|
||||
#include <string.h>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3_image/SDL_image.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/pixdesc.h>
|
||||
#include <libswresample/swresample.h>
|
||||
#include <libswscale/swscale.h>
|
||||
#include "common.h"
|
||||
#include "decode.h"
|
||||
#include "midi.h"
|
||||
|
||||
#define AVIO_BUFFER_BYTES 4096
|
||||
#define MIDI_PEEK_BYTES 8
|
||||
#define AUDIO_MAX_BYTES (512u * 1024u * 1024u) // A decoded file this big is a mistake, not a sound
|
||||
#define AUDIO_MAX_CHANNELS 2
|
||||
#define IMAGE_MAX_DIMENSION 16384 // Pixels a side; larger claims are corrupt or hostile
|
||||
#define SRGB_LINEAR_CUTOFF 0.04045f
|
||||
#define SRGB_LINEAR_SLOPE 12.92f
|
||||
#define SRGB_GAMMA_OFFSET 0.055f
|
||||
#define SRGB_GAMMA_SCALE 1.055f
|
||||
#define SRGB_GAMMA_EXPONENT 2.4f
|
||||
|
||||
|
||||
typedef struct {
|
||||
const uint8_t *bytes;
|
||||
size_t size;
|
||||
size_t offset;
|
||||
} MemoryFileT;
|
||||
|
||||
|
||||
typedef struct {
|
||||
AVFormatContext *format;
|
||||
AVIOContext *io;
|
||||
AVCodecContext *codec;
|
||||
MemoryFileT file;
|
||||
int32_t stream;
|
||||
} SourceT;
|
||||
|
||||
|
||||
static bool _appendAudio(uint8_t **pcm, size_t *bytes, size_t *room, const uint8_t *samples, size_t length);
|
||||
static int _avioRead(void *opaque, uint8_t *buffer, int size);
|
||||
static int64_t _avioSeek(void *opaque, int64_t offset, int whence);
|
||||
static void _close(SourceT *source);
|
||||
static AVFrame *_firstFrame(SourceT *source);
|
||||
static float _linear(float value);
|
||||
static bool _open(SourceT *source, const void *bytes, size_t size, enum AVMediaType type);
|
||||
|
||||
|
||||
// ===== Internal helpers =====
|
||||
|
||||
// Grows the decoded buffer by doubling and copies the block onto its end.
|
||||
static bool _appendAudio(uint8_t **pcm, size_t *bytes, size_t *room, const uint8_t *samples, size_t length) {
|
||||
uint8_t *grown = NULL;
|
||||
size_t want = *room;
|
||||
|
||||
if (length == 0) {
|
||||
return true;
|
||||
}
|
||||
if ((*bytes + length) > AUDIO_MAX_BYTES) {
|
||||
SDL_SetError("The decoded audio is larger than %u bytes.", AUDIO_MAX_BYTES);
|
||||
return false;
|
||||
}
|
||||
while ((*bytes + length) > want) {
|
||||
want = (want == 0) ? AVIO_BUFFER_BYTES : (want * 2);
|
||||
}
|
||||
if (want != *room) {
|
||||
grown = (uint8_t *)SDL_realloc(*pcm, want);
|
||||
if (grown == NULL) {
|
||||
SDL_SetError("Out of memory decoding audio.");
|
||||
return false;
|
||||
}
|
||||
*pcm = grown;
|
||||
*room = want;
|
||||
}
|
||||
memcpy(*pcm + *bytes, samples, length);
|
||||
*bytes += length;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static int _avioRead(void *opaque, uint8_t *buffer, int size) {
|
||||
MemoryFileT *file = (MemoryFileT *)opaque;
|
||||
size_t remaining = file->size - file->offset;
|
||||
|
||||
if (remaining == 0) {
|
||||
return AVERROR_EOF;
|
||||
}
|
||||
if ((size_t)size > remaining) {
|
||||
size = (int)remaining;
|
||||
}
|
||||
memcpy(buffer, file->bytes + file->offset, (size_t)size);
|
||||
file->offset += (size_t)size;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
|
||||
static int64_t _avioSeek(void *opaque, int64_t offset, int whence) {
|
||||
MemoryFileT *file = (MemoryFileT *)opaque;
|
||||
int64_t where = offset;
|
||||
|
||||
if (whence == AVSEEK_SIZE) {
|
||||
return (int64_t)file->size;
|
||||
}
|
||||
if (whence == SEEK_CUR) {
|
||||
where = (int64_t)file->offset + offset;
|
||||
} else if (whence == SEEK_END) {
|
||||
where = (int64_t)file->size + offset;
|
||||
}
|
||||
if ((where < 0) || (where > (int64_t)file->size)) {
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
file->offset = (size_t)where;
|
||||
|
||||
return where;
|
||||
}
|
||||
|
||||
|
||||
static void _close(SourceT *source) {
|
||||
avcodec_free_context(&source->codec);
|
||||
if (source->format != NULL) {
|
||||
avformat_close_input(&source->format);
|
||||
}
|
||||
if (source->io != NULL) {
|
||||
av_freep(&source->io->buffer);
|
||||
avio_context_free(&source->io);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// The first frame the opened stream decodes, or NULL when it yields none. Caller frees it.
|
||||
static AVFrame *_firstFrame(SourceT *source) {
|
||||
AVPacket *packet = av_packet_alloc();
|
||||
AVFrame *frame = av_frame_alloc();
|
||||
bool sent = false;
|
||||
int result = 0;
|
||||
|
||||
if ((packet == NULL) || (frame == NULL)) {
|
||||
SDL_SetError("Out of memory decoding.");
|
||||
av_packet_free(&packet);
|
||||
av_frame_free(&frame);
|
||||
return NULL;
|
||||
}
|
||||
while (av_read_frame(source->format, packet) >= 0) {
|
||||
if (packet->stream_index == source->stream) {
|
||||
sent = (avcodec_send_packet(source->codec, packet) >= 0);
|
||||
av_packet_unref(packet);
|
||||
if (sent) {
|
||||
result = avcodec_receive_frame(source->codec, frame);
|
||||
if (result >= 0) {
|
||||
av_packet_free(&packet);
|
||||
return frame;
|
||||
}
|
||||
if (result != AVERROR(EAGAIN)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
av_packet_unref(packet);
|
||||
}
|
||||
}
|
||||
// Nothing came out while feeding, so drain: a one-frame file often holds its picture back.
|
||||
avcodec_send_packet(source->codec, NULL);
|
||||
if (avcodec_receive_frame(source->codec, frame) >= 0) {
|
||||
av_packet_free(&packet);
|
||||
return frame;
|
||||
}
|
||||
SDL_SetError("No frame decoded.");
|
||||
av_packet_free(&packet);
|
||||
av_frame_free(&frame);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
// An sRGB value between zero and one as linear light.
|
||||
static float _linear(float value) {
|
||||
if (value <= SRGB_LINEAR_CUTOFF) {
|
||||
return value / SRGB_LINEAR_SLOPE;
|
||||
}
|
||||
|
||||
return SDL_powf((value + SRGB_GAMMA_OFFSET) / SRGB_GAMMA_SCALE, SRGB_GAMMA_EXPONENT);
|
||||
}
|
||||
|
||||
|
||||
// Demuxes the bytes and opens a decoder for the best stream of the wanted kind.
|
||||
static bool _open(SourceT *source, const void *bytes, size_t size, enum AVMediaType type) {
|
||||
const AVCodec *codec = NULL;
|
||||
unsigned char *buffer = NULL;
|
||||
int stream = 0;
|
||||
|
||||
memset(source, 0, sizeof(*source));
|
||||
source->file.bytes = (const uint8_t *)bytes;
|
||||
source->file.size = size;
|
||||
buffer = (unsigned char *)av_malloc(AVIO_BUFFER_BYTES);
|
||||
if (buffer == NULL) {
|
||||
SDL_SetError("Out of memory decoding.");
|
||||
return false;
|
||||
}
|
||||
source->io = avio_alloc_context(buffer, AVIO_BUFFER_BYTES, 0, &source->file, _avioRead, NULL, _avioSeek);
|
||||
if (source->io == NULL) {
|
||||
av_freep(&buffer);
|
||||
SDL_SetError("Out of memory decoding.");
|
||||
return false;
|
||||
}
|
||||
source->format = avformat_alloc_context();
|
||||
if (source->format == NULL) {
|
||||
_close(source);
|
||||
SDL_SetError("Out of memory decoding.");
|
||||
return false;
|
||||
}
|
||||
source->format->pb = source->io;
|
||||
source->format->flags |= AVFMT_FLAG_CUSTOM_IO;
|
||||
// open_input frees the context itself on failure and leaves the pointer NULL; the buffer is ours.
|
||||
if (avformat_open_input(&source->format, NULL, NULL, NULL) < 0) {
|
||||
_close(source);
|
||||
SDL_SetError("Not a file this build decodes.");
|
||||
return false;
|
||||
}
|
||||
if (avformat_find_stream_info(source->format, NULL) < 0) {
|
||||
_close(source);
|
||||
SDL_SetError("The file's streams could not be read.");
|
||||
return false;
|
||||
}
|
||||
stream = av_find_best_stream(source->format, type, -1, -1, &codec, 0);
|
||||
if ((stream < 0) || (codec == NULL)) {
|
||||
_close(source);
|
||||
SDL_SetError("The file holds nothing this build decodes.");
|
||||
return false;
|
||||
}
|
||||
source->stream = stream;
|
||||
source->codec = avcodec_alloc_context3(codec);
|
||||
if (source->codec == NULL) {
|
||||
_close(source);
|
||||
SDL_SetError("Out of memory decoding.");
|
||||
return false;
|
||||
}
|
||||
if ((avcodec_parameters_to_context(source->codec, source->format->streams[stream]->codecpar) < 0) || (avcodec_open2(source->codec, codec, NULL) < 0)) {
|
||||
_close(source);
|
||||
SDL_SetError("The %s decoder would not open.", codec->name);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ===== Public =====
|
||||
|
||||
bool decodeAudio(const void *bytes, size_t size, DecodedAudioT *out) {
|
||||
SourceT source;
|
||||
AVChannelLayout layout;
|
||||
SwrContext *swr = NULL;
|
||||
AVPacket *packet = NULL;
|
||||
AVFrame *frame = NULL;
|
||||
uint8_t *pcm = NULL;
|
||||
uint8_t *samples = NULL;
|
||||
size_t used = 0;
|
||||
size_t room = 0;
|
||||
int32_t channels = 0;
|
||||
int32_t capacity = 0;
|
||||
int32_t produced = 0;
|
||||
bool ok = true;
|
||||
|
||||
memset(out, 0, sizeof(*out));
|
||||
if (!_open(&source, bytes, size, AVMEDIA_TYPE_AUDIO)) {
|
||||
return false;
|
||||
}
|
||||
channels = (source.codec->ch_layout.nb_channels > AUDIO_MAX_CHANNELS) ? AUDIO_MAX_CHANNELS : source.codec->ch_layout.nb_channels;
|
||||
av_channel_layout_default(&layout, channels);
|
||||
if ((swr_alloc_set_opts2(&swr, &layout, AV_SAMPLE_FMT_FLT, source.codec->sample_rate, &source.codec->ch_layout, source.codec->sample_fmt, source.codec->sample_rate, 0, NULL) < 0) || (swr_init(swr) < 0)) {
|
||||
SDL_SetError("The resampler would not start.");
|
||||
av_channel_layout_uninit(&layout);
|
||||
swr_free(&swr);
|
||||
_close(&source);
|
||||
return false;
|
||||
}
|
||||
packet = av_packet_alloc();
|
||||
frame = av_frame_alloc();
|
||||
if ((packet == NULL) || (frame == NULL)) {
|
||||
SDL_SetError("Out of memory decoding audio.");
|
||||
ok = false;
|
||||
}
|
||||
while (ok && (av_read_frame(source.format, packet) >= 0)) {
|
||||
if (packet->stream_index != source.stream) {
|
||||
av_packet_unref(packet);
|
||||
continue;
|
||||
}
|
||||
if (avcodec_send_packet(source.codec, packet) >= 0) {
|
||||
while (avcodec_receive_frame(source.codec, frame) >= 0) {
|
||||
capacity = swr_get_out_samples(swr, frame->nb_samples);
|
||||
samples = (uint8_t *)SDL_malloc((size_t)capacity * (size_t)channels * sizeof(float));
|
||||
if (samples == NULL) {
|
||||
SDL_SetError("Out of memory decoding audio.");
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
produced = swr_convert(swr, &samples, capacity, (const uint8_t **)frame->extended_data, frame->nb_samples);
|
||||
if (produced > 0) {
|
||||
ok = _appendAudio(&pcm, &used, &room, samples, (size_t)produced * (size_t)channels * sizeof(float));
|
||||
}
|
||||
SDL_free(samples);
|
||||
av_frame_unref(frame);
|
||||
if (!ok) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
av_packet_unref(packet);
|
||||
}
|
||||
// Whatever the resampler still holds.
|
||||
if (ok) {
|
||||
capacity = swr_get_out_samples(swr, 0);
|
||||
if (capacity > 0) {
|
||||
samples = (uint8_t *)SDL_malloc((size_t)capacity * (size_t)channels * sizeof(float));
|
||||
if (samples != NULL) {
|
||||
produced = swr_convert(swr, &samples, capacity, NULL, 0);
|
||||
if (produced > 0) {
|
||||
ok = _appendAudio(&pcm, &used, &room, samples, (size_t)produced * (size_t)channels * sizeof(float));
|
||||
}
|
||||
SDL_free(samples);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ok && (used == 0)) {
|
||||
SDL_SetError("No audio decoded.");
|
||||
ok = false;
|
||||
}
|
||||
if (ok) {
|
||||
out->pcm = pcm;
|
||||
out->bytes = used;
|
||||
out->spec.format = SDL_AUDIO_F32;
|
||||
out->spec.channels = channels;
|
||||
out->spec.freq = source.codec->sample_rate;
|
||||
} else {
|
||||
SDL_free(pcm);
|
||||
}
|
||||
av_packet_free(&packet);
|
||||
av_frame_free(&frame);
|
||||
av_channel_layout_uninit(&layout);
|
||||
swr_free(&swr);
|
||||
_close(&source);
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
|
||||
MIX_Audio *decodeAudioIO(MIX_Mixer *mixer, SDL_IOStream *io, bool closeio) {
|
||||
DecodedAudioT decoded;
|
||||
MIX_Audio *audio = NULL;
|
||||
void *bytes = NULL;
|
||||
size_t size = 0;
|
||||
uint8_t header[MIDI_PEEK_BYTES];
|
||||
bool midi = false;
|
||||
|
||||
// A MIDI file is synthesised rather than decoded, and the mixer's own MIDI decoder wants a GUS
|
||||
// patch set almost nobody installs, so it is recognised before the mixer ever sees the stream.
|
||||
if (SDL_ReadIO(io, header, sizeof(header)) == sizeof(header)) {
|
||||
midi = midiIs(header, sizeof(header));
|
||||
}
|
||||
if (SDL_SeekIO(io, 0, SDL_IO_SEEK_SET) < 0) {
|
||||
if (closeio) {
|
||||
SDL_CloseIO(io);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
if (!midi) {
|
||||
audio = MIX_LoadAudio_IO(mixer, io, true, false);
|
||||
}
|
||||
if (audio == NULL) {
|
||||
// SDL_mixer reads a fixed set of formats. Anything else -- AAC, ALAC, AC-3, WMA and the
|
||||
// rest -- is decoded whole to float samples and handed over as raw PCM, which is what the
|
||||
// mixer would have made of it anyway.
|
||||
if (SDL_SeekIO(io, 0, SDL_IO_SEEK_SET) >= 0) {
|
||||
bytes = SDL_LoadFile_IO(io, &size, false);
|
||||
if (bytes != NULL) {
|
||||
if (midi ? midiRender(bytes, size, &decoded) : decodeAudio(bytes, size, &decoded)) {
|
||||
audio = MIX_LoadRawAudio(mixer, decoded.pcm, decoded.bytes, &decoded.spec);
|
||||
decodeFreeAudio(&decoded);
|
||||
}
|
||||
SDL_free(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (closeio) {
|
||||
SDL_CloseIO(io);
|
||||
}
|
||||
|
||||
return audio;
|
||||
}
|
||||
|
||||
|
||||
void decodeFreeAudio(DecodedAudioT *audio) {
|
||||
SDL_free(audio->pcm);
|
||||
memset(audio, 0, sizeof(*audio));
|
||||
}
|
||||
|
||||
|
||||
SDL_Surface *decodeImage(const void *bytes, size_t size) {
|
||||
SourceT source;
|
||||
SDL_Surface *surface = NULL;
|
||||
struct SwsContext *sws = NULL;
|
||||
AVFrame *frame = NULL;
|
||||
uint8_t *planes[4];
|
||||
int pitches[4];
|
||||
|
||||
if (!_open(&source, bytes, size, AVMEDIA_TYPE_VIDEO)) {
|
||||
return NULL;
|
||||
}
|
||||
frame = _firstFrame(&source);
|
||||
if (frame == NULL) {
|
||||
_close(&source);
|
||||
return NULL;
|
||||
}
|
||||
if ((frame->width <= 0) || (frame->height <= 0) || (frame->width > IMAGE_MAX_DIMENSION) || (frame->height > IMAGE_MAX_DIMENSION)) {
|
||||
SDL_SetError("The image is %dx%d, which is not a picture this engine will load.", frame->width, frame->height);
|
||||
av_frame_free(&frame);
|
||||
_close(&source);
|
||||
return NULL;
|
||||
}
|
||||
surface = SDL_CreateSurface(frame->width, frame->height, SDL_PIXELFORMAT_RGBA32);
|
||||
if (surface == NULL) {
|
||||
av_frame_free(&frame);
|
||||
_close(&source);
|
||||
return NULL;
|
||||
}
|
||||
sws = sws_getContext(frame->width, frame->height, (enum AVPixelFormat)frame->format, frame->width, frame->height, AV_PIX_FMT_RGBA, SWS_BILINEAR, NULL, NULL, NULL);
|
||||
if (sws == NULL) {
|
||||
SDL_SetError("The image's %s pixels cannot be converted.", av_get_pix_fmt_name((enum AVPixelFormat)frame->format));
|
||||
SDL_DestroySurface(surface);
|
||||
av_frame_free(&frame);
|
||||
_close(&source);
|
||||
return NULL;
|
||||
}
|
||||
memset(planes, 0, sizeof(planes));
|
||||
memset(pitches, 0, sizeof(pitches));
|
||||
planes[0] = (uint8_t *)surface->pixels;
|
||||
pitches[0] = surface->pitch;
|
||||
sws_scale(sws, (const uint8_t *const *)frame->data, frame->linesize, 0, frame->height, planes, pitches);
|
||||
sws_freeContext(sws);
|
||||
av_frame_free(&frame);
|
||||
_close(&source);
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
|
||||
SDL_Surface *decodeImageIO(SDL_IOStream *io, bool closeio) {
|
||||
SDL_Surface *surface = IMG_Load_IO(io, false);
|
||||
void *bytes = NULL;
|
||||
size_t size = 0;
|
||||
|
||||
if (surface == NULL) {
|
||||
// SDL_image could not read it. It may still be a TIFF, an OpenEXR, a JPEG 2000, an AVIF or
|
||||
// any of the other pictures the bundled FFmpeg decodes for video.
|
||||
if (SDL_SeekIO(io, 0, SDL_IO_SEEK_SET) >= 0) {
|
||||
bytes = SDL_LoadFile_IO(io, &size, false);
|
||||
if (bytes != NULL) {
|
||||
surface = decodeImage(bytes, size);
|
||||
SDL_free(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (closeio) {
|
||||
SDL_CloseIO(io);
|
||||
}
|
||||
|
||||
return surface;
|
||||
}
|
||||
|
||||
|
||||
float *decodeImageFloat(const void *bytes, size_t size, int32_t *width, int32_t *height) {
|
||||
const AVPixFmtDescriptor *description = NULL;
|
||||
SourceT source;
|
||||
struct SwsContext *sws = NULL;
|
||||
AVFrame *frame = NULL;
|
||||
float *out = NULL;
|
||||
float *planar = NULL;
|
||||
uint8_t *planes[4];
|
||||
int pitches[4];
|
||||
bool isFloat = false;
|
||||
size_t pixels = 0;
|
||||
size_t x = 0;
|
||||
|
||||
if (!_open(&source, bytes, size, AVMEDIA_TYPE_VIDEO)) {
|
||||
return NULL;
|
||||
}
|
||||
frame = _firstFrame(&source);
|
||||
if (frame == NULL) {
|
||||
_close(&source);
|
||||
return NULL;
|
||||
}
|
||||
if ((frame->width <= 0) || (frame->height <= 0) || (frame->width > IMAGE_MAX_DIMENSION) || (frame->height > IMAGE_MAX_DIMENSION)) {
|
||||
SDL_SetError("The image is %dx%d, which is not a picture this engine will load.", frame->width, frame->height);
|
||||
av_frame_free(&frame);
|
||||
_close(&source);
|
||||
return NULL;
|
||||
}
|
||||
// A floating-point source is already linear light; an integer one is sRGB and has to be brought
|
||||
// into linear before it can be used as radiance.
|
||||
description = av_pix_fmt_desc_get((enum AVPixelFormat)frame->format);
|
||||
isFloat = (description != NULL) && ((description->flags & AV_PIX_FMT_FLAG_FLOAT) != 0);
|
||||
pixels = (size_t)frame->width * (size_t)frame->height;
|
||||
planar = (float *)SDL_malloc(sizeof(float) * 3 * pixels);
|
||||
out = (float *)SDL_malloc(sizeof(float) * 3 * pixels);
|
||||
if ((planar == NULL) || (out == NULL)) {
|
||||
SDL_SetError("Out of memory decoding an image.");
|
||||
SDL_free(planar);
|
||||
SDL_free(out);
|
||||
av_frame_free(&frame);
|
||||
_close(&source);
|
||||
return NULL;
|
||||
}
|
||||
sws = sws_getContext(frame->width, frame->height, (enum AVPixelFormat)frame->format, frame->width, frame->height, AV_PIX_FMT_GBRPF32LE, SWS_BILINEAR, NULL, NULL, NULL);
|
||||
if (sws == NULL) {
|
||||
SDL_SetError("The image's %s pixels cannot be converted.", av_get_pix_fmt_name((enum AVPixelFormat)frame->format));
|
||||
SDL_free(planar);
|
||||
SDL_free(out);
|
||||
av_frame_free(&frame);
|
||||
_close(&source);
|
||||
return NULL;
|
||||
}
|
||||
// GBRP, so green, blue and red planes in that order, each one float per pixel.
|
||||
memset(planes, 0, sizeof(planes));
|
||||
memset(pitches, 0, sizeof(pitches));
|
||||
planes[0] = (uint8_t *)(planar + pixels);
|
||||
planes[1] = (uint8_t *)(planar + pixels * 2);
|
||||
planes[2] = (uint8_t *)planar;
|
||||
pitches[0] = frame->width * (int)sizeof(float);
|
||||
pitches[1] = pitches[0];
|
||||
pitches[2] = pitches[0];
|
||||
sws_scale(sws, (const uint8_t *const *)frame->data, frame->linesize, 0, frame->height, planes, pitches);
|
||||
sws_freeContext(sws);
|
||||
for (x = 0; x < pixels; x++) {
|
||||
out[x * 3] = isFloat ? planar[x] : _linear(planar[x]);
|
||||
out[x * 3 + 1] = isFloat ? planar[pixels + x] : _linear(planar[pixels + x]);
|
||||
out[x * 3 + 2] = isFloat ? planar[pixels * 2 + x] : _linear(planar[pixels * 2 + x]);
|
||||
}
|
||||
*width = frame->width;
|
||||
*height = frame->height;
|
||||
SDL_free(planar);
|
||||
av_frame_free(&frame);
|
||||
_close(&source);
|
||||
|
||||
return out;
|
||||
}
|
||||
68
src/decode.h
Normal file
68
src/decode.h
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
*
|
||||
* Singe 3
|
||||
* Copyright (C) 2006-2026 Scott Duensing <scott@kangaroopunch.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef DECODE_H
|
||||
#define DECODE_H
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3_mixer/SDL_mixer.h>
|
||||
|
||||
|
||||
// Decoding whatever the bundled FFmpeg understands, for the files SDL_image and SDL_mixer turn
|
||||
// down. Every entry point takes a whole file in memory, because every caller already read one
|
||||
// through the vfs, and reports failure through SDL_SetError.
|
||||
|
||||
typedef struct {
|
||||
uint8_t *pcm; // Interleaved samples in spec's format; decodeFreeAudio releases it
|
||||
size_t bytes;
|
||||
SDL_AudioSpec spec;
|
||||
} DecodedAudioT;
|
||||
|
||||
|
||||
// Whole file decoded to interleaved float samples. False when nothing in it decodes.
|
||||
bool decodeAudio(const void *bytes, size_t size, DecodedAudioT *out);
|
||||
|
||||
// Every sound and every piece of music the engine loads comes through here: SDL_mixer first, then
|
||||
// FFmpeg for the formats it does not read. NULL on failure with the reason in SDL_GetError.
|
||||
MIX_Audio *decodeAudioIO(MIX_Mixer *mixer, SDL_IOStream *io, bool closeio);
|
||||
|
||||
// Releases what decodeAudio filled in and zeroes it. Safe on a zeroed structure.
|
||||
void decodeFreeAudio(DecodedAudioT *audio);
|
||||
|
||||
// The first frame of a still image or an animation as an RGBA32 surface. NULL when the bytes are
|
||||
// not an image this build decodes.
|
||||
SDL_Surface *decodeImage(const void *bytes, size_t size);
|
||||
|
||||
// Every picture the engine loads comes through here: SDL_image first, then FFmpeg for the formats
|
||||
// it does not read. NULL on failure with the reason in SDL_GetError.
|
||||
SDL_Surface *decodeImageIO(SDL_IOStream *io, bool closeio);
|
||||
|
||||
// The same picture as linear RGB floats, three per pixel, for skies and other light data; SDL_free
|
||||
// it. Floating-point sources keep their range, integer ones are taken as sRGB and linearised.
|
||||
float *decodeImageFloat(const void *bytes, size_t size, int32_t *width, int32_t *height);
|
||||
|
||||
|
||||
#endif // DECODE_H
|
||||
121
src/frameFile.c
121
src/frameFile.c
|
|
@ -33,14 +33,17 @@
|
|||
|
||||
|
||||
#define REPORT_COLUMN_WIDTH 8
|
||||
#define SEGMENTS_OPEN_MAX 4 // The segment playing, the one after it, and room to bounce
|
||||
#define SEGMENT_CLOSED -1 // No player open for this line yet
|
||||
|
||||
|
||||
#define LEGACY_VIDEO_EXTENSION "m2v" // The one container whose audio is always a sidecar
|
||||
|
||||
|
||||
typedef struct FrameLineS {
|
||||
int32_t videoHandle;
|
||||
int64_t frame; // First laserdisc frame number in this segment
|
||||
int32_t videoHandle; // SEGMENT_CLOSED until the segment is opened
|
||||
int64_t frame; // First laserdisc frame number in this segment
|
||||
int64_t used; // When this segment was last wanted, for choosing what to close
|
||||
char *filename;
|
||||
} FrameLineT;
|
||||
|
||||
|
|
@ -48,7 +51,12 @@ typedef struct FrameFileS {
|
|||
int32_t id;
|
||||
int32_t count;
|
||||
int32_t currentIndex; // Segment currently playing
|
||||
int32_t openCount; // Segments with a player open
|
||||
int64_t lastObservedFrame; // Frame of that segment at the last update
|
||||
int64_t clock; // Counts up so "least recently wanted" has a meaning
|
||||
char *indexPath; // What videoLoad wants, kept because segments open later
|
||||
char *audioSuffix; // discAudioSuffix's choice, applied as segments open
|
||||
SDL_Renderer *renderer;
|
||||
FrameLineT *files;
|
||||
UT_hash_handle hh;
|
||||
} FrameFileT;
|
||||
|
|
@ -58,9 +66,21 @@ static FrameFileT *_frameFileHash = NULL;
|
|||
static int32_t _nextId = 0;
|
||||
|
||||
|
||||
static void _closeSegment(FrameFileT *f, int32_t index);
|
||||
static FrameFileT *_getFrameFile(int32_t frameFileHandle, const char *caller);
|
||||
static int32_t _openSegment(FrameFileT *f, int32_t index);
|
||||
static void _selectSegment(FrameFileT *f, int32_t index, int64_t frame, int32_t *videoHandle);
|
||||
static void _showCalculated(const FrameFileT *f);
|
||||
static void _showCalculated(FrameFileT *f);
|
||||
|
||||
|
||||
static void _closeSegment(FrameFileT *f, int32_t index) {
|
||||
if (f->files[index].videoHandle == SEGMENT_CLOSED) {
|
||||
return;
|
||||
}
|
||||
videoUnload(f->files[index].videoHandle);
|
||||
f->files[index].videoHandle = SEGMENT_CLOSED;
|
||||
f->openCount--;
|
||||
}
|
||||
|
||||
|
||||
static FrameFileT *_getFrameFile(int32_t frameFileHandle, const char *caller) {
|
||||
|
|
@ -75,20 +95,68 @@ static FrameFileT *_getFrameFile(int32_t frameFileHandle, const char *caller) {
|
|||
}
|
||||
|
||||
|
||||
// Make segment "index" the active video at "frame", carrying the previous video's state over.
|
||||
// The player for a segment, opened now if it is not open already. A disc plays one segment at a
|
||||
// time, and a long framefile has hundreds -- typing-md2 has 213 -- so holding a demuxer open for
|
||||
// every one of them costs hundreds of file descriptors for no gain, and puts a game within reach of
|
||||
// the 1024 that glibc's select refuses to look past. Only a handful stay open: the one playing,
|
||||
// the one after it, and enough room for a seek to move between two without reopening either.
|
||||
static int32_t _openSegment(FrameFileT *f, int32_t index) {
|
||||
char *audio = NULL;
|
||||
int32_t oldest = -1;
|
||||
int32_t x = 0;
|
||||
|
||||
f->files[index].used = ++f->clock;
|
||||
if (f->files[index].videoHandle != SEGMENT_CLOSED) {
|
||||
return f->files[index].videoHandle;
|
||||
}
|
||||
// Make room. Never the segment being asked for, and never the one playing: something outside
|
||||
// still holds that handle.
|
||||
while (f->openCount >= SEGMENTS_OPEN_MAX) {
|
||||
oldest = -1;
|
||||
for (x = 0; x < f->count; x++) {
|
||||
if ((f->files[x].videoHandle == SEGMENT_CLOSED) || (x == index) || (x == f->currentIndex)) {
|
||||
continue;
|
||||
}
|
||||
if ((oldest < 0) || (f->files[x].used < f->files[oldest].used)) {
|
||||
oldest = x;
|
||||
}
|
||||
}
|
||||
if (oldest < 0) {
|
||||
break;
|
||||
}
|
||||
_closeSegment(f, oldest);
|
||||
}
|
||||
audio = frameFileAudioName(f->files[index].filename, f->audioSuffix);
|
||||
f->files[index].videoHandle = videoLoad(f->files[index].filename, audio, f->indexPath, f->renderer, false);
|
||||
f->openCount++;
|
||||
free(audio);
|
||||
|
||||
return f->files[index].videoHandle;
|
||||
}
|
||||
|
||||
|
||||
// Make segment "index" the active video at "frame".
|
||||
//
|
||||
// A segment is a player of its own, so what the disc was doing has to be carried to it: the volume,
|
||||
// which track of the audio was chosen, and whether the disc was playing or paused. These live
|
||||
// nowhere but in the player, which is why they are copied here.
|
||||
//
|
||||
// The picture settings -- vldpSetMonochrome, vldpSetBlend and vldpSetLuma -- are deliberately not
|
||||
// among them. The engine holds those itself and puts them on whichever player is the disc, every
|
||||
// frame, so a player that has just been opened is told about them before anything is drawn from it.
|
||||
// Copying them here as well would make the player a second place to look for a setting the engine
|
||||
// already owns.
|
||||
static void _selectSegment(FrameFileT *f, int32_t index, int64_t frame, int32_t *videoHandle) {
|
||||
int32_t oldHandle = *videoHandle;
|
||||
int32_t newHandle = f->files[index].videoHandle;
|
||||
int32_t newHandle = _openSegment(f, index);
|
||||
int32_t left = 0;
|
||||
int32_t right = 0;
|
||||
int32_t track = 0;
|
||||
|
||||
videoSeek(newHandle, frame);
|
||||
if ((oldHandle >= 0) && (oldHandle != newHandle)) {
|
||||
// Transfer previous video's properties to this one
|
||||
videoGetVolume(oldHandle, &left, &right);
|
||||
videoSetVolume(newHandle, left, right);
|
||||
videoSetMonochrome(newHandle, videoGetMonochrome(oldHandle));
|
||||
track = videoGetAudioTrack(oldHandle);
|
||||
if ((track >= 0) && (track < videoGetAudioTracks(newHandle))) {
|
||||
videoSetAudioTrack(newHandle, track);
|
||||
|
|
@ -103,10 +171,15 @@ static void _selectSegment(FrameFileT *f, int32_t index, int64_t frame, int32_t
|
|||
f->currentIndex = index;
|
||||
f->lastObservedFrame = frame;
|
||||
*videoHandle = newHandle;
|
||||
// The next segment is opened now rather than when the picture reaches it, so the changeover
|
||||
// costs no more than it did when every segment was open from the start.
|
||||
if (f->count > 1) {
|
||||
_openSegment(f, (index + 1) % f->count);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void _showCalculated(const FrameFileT *f) {
|
||||
static void _showCalculated(FrameFileT *f) {
|
||||
int32_t x = 0;
|
||||
int64_t count = 0;
|
||||
int64_t next = 0;
|
||||
|
|
@ -117,7 +190,7 @@ static void _showCalculated(const FrameFileT *f) {
|
|||
utilSay(" Start Length End File");
|
||||
utilSay("-------- -------- -------- -------------------------------------------------");
|
||||
for (x = 0; x < f->count; x++) {
|
||||
count = videoGetFrameCount(f->files[x].videoHandle);
|
||||
count = videoGetFrameCount(_openSegment(f, x));
|
||||
utilSay("%*" PRId64 " %*" PRId64 " %*" PRId64 " %s", REPORT_COLUMN_WIDTH, f->files[x].frame, REPORT_COLUMN_WIDTH, count, REPORT_COLUMN_WIDTH, f->files[x].frame + count - 1, f->files[x].filename);
|
||||
}
|
||||
utilSay("\nIdeal Framefile:\n");
|
||||
|
|
@ -125,7 +198,7 @@ static void _showCalculated(const FrameFileT *f) {
|
|||
utilSay("-------- ---------------------------------------------------------------------");
|
||||
for (x = 0; x < f->count; x++) {
|
||||
utilSay("%*" PRId64 " %s", REPORT_COLUMN_WIDTH, next, f->files[x].filename);
|
||||
next += videoGetFrameCount(f->files[x].videoHandle);
|
||||
next += videoGetFrameCount(_openSegment(f, x));
|
||||
}
|
||||
utilNewline();
|
||||
}
|
||||
|
|
@ -178,7 +251,6 @@ int32_t frameFileLoad(const char *filename, const char *indexPath, SDL_Renderer
|
|||
int64_t frame = 0;
|
||||
size_t bytes = 0;
|
||||
size_t x = 0;
|
||||
char *audio = NULL;
|
||||
char *combined = NULL;
|
||||
char *data = NULL;
|
||||
char *path = NULL;
|
||||
|
|
@ -250,9 +322,14 @@ int32_t frameFileLoad(const char *filename, const char *indexPath, SDL_Renderer
|
|||
files = newFiles;
|
||||
files[count].frame = frame;
|
||||
files[count].filename = utilCreateString("%s%s", path, name);
|
||||
audio = frameFileAudioName(files[count].filename, "");
|
||||
files[count].videoHandle = videoLoad(files[count].filename, audio, indexPath, renderer, false);
|
||||
free(audio);
|
||||
files[count].videoHandle = SEGMENT_CLOSED;
|
||||
files[count].used = 0;
|
||||
// Segments open when the disc reaches them, but the names are checked now: a framefile
|
||||
// naming a file that is not there should say so at startup, as it always has, rather
|
||||
// than halfway through a game.
|
||||
if (!vfsExists(files[count].filename)) {
|
||||
utilDie("Framefile %s names %s, which does not exist.", filename, files[count].filename);
|
||||
}
|
||||
count++;
|
||||
}
|
||||
free(frameLine);
|
||||
|
|
@ -274,6 +351,9 @@ int32_t frameFileLoad(const char *filename, const char *indexPath, SDL_Renderer
|
|||
frameFile->count = count;
|
||||
frameFile->currentIndex = -1;
|
||||
frameFile->files = files;
|
||||
frameFile->indexPath = (indexPath != NULL) ? utilCreateString("%s", indexPath) : NULL;
|
||||
frameFile->audioSuffix = utilCreateString("%s", "");
|
||||
frameFile->renderer = renderer;
|
||||
HASH_ADD_INT(_frameFileHash, id, frameFile);
|
||||
|
||||
// Show debug output?
|
||||
|
|
@ -311,7 +391,7 @@ void frameFileSeek(int32_t frameFileHandle, int64_t seekFrame, int32_t *videoHan
|
|||
|
||||
// Clamp inside the segment instead of letting the player wrap it.
|
||||
*actualFrame = seekFrame - f->files[found].frame;
|
||||
last = videoGetFrameCount(f->files[found].videoHandle) - 1;
|
||||
last = videoGetFrameCount(_openSegment(f, found)) - 1;
|
||||
if (*actualFrame < 0) {
|
||||
*actualFrame = 0;
|
||||
}
|
||||
|
|
@ -339,7 +419,12 @@ bool frameFileSetAudioSuffix(int32_t frameFileHandle, const char *suffix) {
|
|||
free(audio);
|
||||
}
|
||||
}
|
||||
free(f->audioSuffix);
|
||||
f->audioSuffix = utilCreateString("%s", suffix);
|
||||
for (i = 0; i < f->count; i++) {
|
||||
if (f->files[i].videoHandle == SEGMENT_CLOSED) {
|
||||
continue;
|
||||
}
|
||||
audio = frameFileAudioName(f->files[i].filename, suffix);
|
||||
if (!videoReopenAudio(f->files[i].videoHandle, audio)) {
|
||||
utilDie("Unable to open %s for audio.", audio);
|
||||
|
|
@ -355,14 +440,16 @@ void frameFileUnload(int32_t frameFileHandle) {
|
|||
FrameFileT *f = _getFrameFile(frameFileHandle, "frameFileUnload");
|
||||
int32_t i = 0;
|
||||
|
||||
// Unload videos
|
||||
// Unload whichever segments are open
|
||||
for (i = 0; i < f->count; i++) {
|
||||
_closeSegment(f, i);
|
||||
free(f->files[i].filename);
|
||||
videoUnload(f->files[i].videoHandle);
|
||||
}
|
||||
|
||||
// Free memory
|
||||
free(f->files);
|
||||
free(f->indexPath);
|
||||
free(f->audioSuffix);
|
||||
|
||||
// Remove from hash
|
||||
HASH_DEL(_frameFileHash, f);
|
||||
|
|
|
|||
|
|
@ -65,6 +65,9 @@
|
|||
#include <RmlUi/Core/Log.h>
|
||||
#include <RmlUi/Core/Types.h>
|
||||
#include <SDL3_image/SDL_image.h>
|
||||
extern "C" {
|
||||
#include "decode.h"
|
||||
}
|
||||
#include "guiRender.h"
|
||||
#include "shaders/guiShaders.h"
|
||||
|
||||
|
|
@ -820,6 +823,10 @@ Rml::TextureHandle GuiRenderT::LoadTexture(Rml::Vector2i &textureDimensions, con
|
|||
files->Read(buffer.get(), fileSize, file);
|
||||
files->Close(file);
|
||||
surface = IMG_LoadTyped_IO(SDL_IOFromMem(buffer.get(), fileSize), true, extension.c_str());
|
||||
if (surface == nullptr) {
|
||||
// Whatever SDL_image turned down may still be a picture the bundled FFmpeg decodes.
|
||||
surface = decodeImage(buffer.get(), fileSize);
|
||||
}
|
||||
if (surface == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
#include <string.h>
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3_image/SDL_image.h>
|
||||
#include "decode.h"
|
||||
#include "hdr.h"
|
||||
#include "vfs.h"
|
||||
|
||||
|
|
@ -254,6 +255,10 @@ float *hdrLoad(const char *name, int32_t *width, int32_t *height) {
|
|||
if (surface != NULL) {
|
||||
out = _loadSurface(surface, width, height);
|
||||
SDL_DestroySurface(surface);
|
||||
} else {
|
||||
// OpenEXR is the other half of every HDRI download and SDL_image reads none of it, so
|
||||
// anything it turns down goes to FFmpeg, which keeps a floating-point source's range.
|
||||
out = decodeImageFloat(bytes, size, width, height);
|
||||
}
|
||||
}
|
||||
free(bytes);
|
||||
|
|
|
|||
47
src/main.c
47
src/main.c
|
|
@ -55,6 +55,8 @@
|
|||
#include "../thirdparty/uthash/src/utlist.h"
|
||||
|
||||
#include "main.h"
|
||||
#include "midi.h"
|
||||
#include "midiIo.h"
|
||||
#include "stddclmr.h"
|
||||
#include "util.h"
|
||||
#include "frameFile.h"
|
||||
|
|
@ -114,6 +116,7 @@ typedef enum LongOptionE {
|
|||
OPT_ABSOLUTES_ONLY = 256,
|
||||
OPT_ALTAUDIO,
|
||||
OPT_APIVERSION,
|
||||
OPT_DEINTERLACE,
|
||||
OPT_DETERMINISTIC,
|
||||
OPT_FVALUE,
|
||||
OPT_GAMEPAD_REORDER,
|
||||
|
|
@ -128,6 +131,7 @@ typedef enum LongOptionE {
|
|||
OPT_MONOCHROME,
|
||||
OPT_NOGAMEPAD,
|
||||
OPT_SCREEN,
|
||||
OPT_SOUNDFONT,
|
||||
OPT_STARTSILENT,
|
||||
OPT_TRIGGER_THRESHOLD,
|
||||
OPT_XRATIO,
|
||||
|
|
@ -198,6 +202,7 @@ static const OptionT _options[] = {
|
|||
{ OPT_ABSOLUTES_ONLY, "absolutes_only", ap_no, NULL, "keep only the mice that report absolute positions, which is what light guns do", false, true },
|
||||
{ OPT_ALTAUDIO, "altaudio", ap_yes, "SUFFIX", "play <base><SUFFIX>.ogg beside the disc video in place of its own audio", false, true },
|
||||
{ OPT_APIVERSION, "apiversion", ap_no, NULL, "print one machine readable version line and exit", false, false },
|
||||
{ OPT_DEINTERLACE, "deinterlace", ap_yes, "MODE", "what to do with an interlaced picture: off, auto or on", false, true },
|
||||
{ OPT_DETERMINISTIC, "deterministic", ap_maybe, "MS", "for testing: ignore real time and move the clock MS milliseconds each frame, seeding the random generators from the same number", false, true },
|
||||
{ OPT_FVALUE, "fvalue", ap_yes, "NUMBER", "one number handed to the game, which reads it with getFValue()", false, true },
|
||||
{ OPT_GAMEPAD_REORDER, "gamepad_reorder", ap_yes, "DIGITS", "which pad fills which slot, as enumeration positions from 0", false, true },
|
||||
|
|
@ -212,6 +217,7 @@ static const OptionT _options[] = {
|
|||
{ OPT_MONOCHROME, "monochrome", ap_no, NULL, "start with the disc picture in grey", false, true },
|
||||
{ OPT_NOGAMEPAD, "nogamepad", ap_no, NULL, "ignore every gamepad, as --nomouse ignores the mice", false, true },
|
||||
{ OPT_SCREEN, "screen", ap_yes, "N", "open the window on display N, counting from 1", false, true },
|
||||
{ OPT_SOUNDFONT, "soundfont", ap_yes, "FILE", "synthesise MIDI files with this SoundFont (.sf2)", false, true },
|
||||
{ OPT_STARTSILENT, "startsilent", ap_no, NULL, "start muted until the first input of any kind", false, true },
|
||||
{ OPT_TRIGGER_THRESHOLD, "trigger_threshold", ap_yes, "PERCENT", "how far a trigger travels before it counts as a button; 0 uses DEAD_ZONE", false, true },
|
||||
{ OPT_XRATIO, "xratio", ap_yes, "FACTOR", "horizontal gun coordinate scale a game reads with ratioGetX()", false, true },
|
||||
|
|
@ -314,6 +320,7 @@ static void _applyOptions(const char *exeName, ConfigT *conf, int32_t argc, cons
|
|||
char *aspectString = NULL;
|
||||
char *canvasString = NULL;
|
||||
char *sindenString = NULL;
|
||||
char *deinterlaceString = NULL;
|
||||
char *edgeString = NULL;
|
||||
char *temp = NULL;
|
||||
const char *arg = NULL;
|
||||
|
|
@ -570,6 +577,19 @@ static void _applyOptions(const char *exeName, ConfigT *conf, int32_t argc, cons
|
|||
conf->absolutesOnly = true;
|
||||
break;
|
||||
|
||||
// Deinterlacing
|
||||
case OPT_DEINTERLACE:
|
||||
free(deinterlaceString);
|
||||
deinterlaceString = strdup(arg);
|
||||
break;
|
||||
|
||||
// MIDI Sound Bank
|
||||
case OPT_SOUNDFONT:
|
||||
free(conf->soundfont);
|
||||
conf->soundfont = strdup(arg);
|
||||
utilFixPathSeparators(&conf->soundfont, false);
|
||||
break;
|
||||
|
||||
// Alternate Disc Audio
|
||||
case OPT_ALTAUDIO:
|
||||
conf->given |= GIVEN_AUDIO_SUFFIX;
|
||||
|
|
@ -755,6 +775,22 @@ static void _applyOptions(const char *exeName, ConfigT *conf, int32_t argc, cons
|
|||
_optionFail(exeName, source, "--rotate takes 0, 90, 180 or 270 degrees.");
|
||||
}
|
||||
|
||||
// A laserdisc rip that kept its interlaced fields combs on a progressive display. Automatic
|
||||
// deinterlacing touches only the frames the file says are interlaced, which is why it is the
|
||||
// default; "on" is for a file whose flags are wrong.
|
||||
if (deinterlaceString) {
|
||||
if (utilStricmp(deinterlaceString, "off") == 0) {
|
||||
conf->deinterlace = DEINTERLACE_OFF;
|
||||
} else if (utilStricmp(deinterlaceString, "auto") == 0) {
|
||||
conf->deinterlace = DEINTERLACE_AUTO;
|
||||
} else if (utilStricmp(deinterlaceString, "on") == 0) {
|
||||
conf->deinterlace = DEINTERLACE_ON;
|
||||
} else {
|
||||
_optionFail(exeName, source, "--deinterlace takes off, auto or on.");
|
||||
}
|
||||
free(deinterlaceString);
|
||||
}
|
||||
|
||||
// Where does the Sinden border go? Without the option a bezel decides: the gun's camera
|
||||
// sees the whole screen, so artwork around the picture pushes the border out to the window.
|
||||
if (edgeString) {
|
||||
|
|
@ -1364,6 +1400,7 @@ static ConfigT *_parseArguments(const char *exeName, int32_t argc, char *argv[])
|
|||
conf->linearScale = true;
|
||||
conf->mapJoysticks = true;
|
||||
conf->joyMouseRange = JOY_MOUSE_RANGE_DEFAULT;
|
||||
conf->deinterlace = DEINTERLACE_AUTO;
|
||||
conf->deterministicStep = FRAME_TICK_MS;
|
||||
|
||||
_applyOptions(exeName, conf, argc, (const char **)argv, NULL);
|
||||
|
|
@ -1481,6 +1518,9 @@ static void _resolveFiles(const char *exeName, ConfigT *conf) {
|
|||
|
||||
// Exists? A packed game answers through its database.
|
||||
vfsInit(conf->container, conf->dataDirBase, conf->dataDir);
|
||||
midiInit(conf->soundfont);
|
||||
midiIoInit();
|
||||
videoSetDeinterlace(conf->deinterlace);
|
||||
utilFixPathSeparators(&conf->scriptFile, false);
|
||||
if (!vfsExists(conf->scriptFile)) {
|
||||
// Missing. Is a path?
|
||||
|
|
@ -1723,6 +1763,8 @@ static void _traceHeader(const ConfigT *conf, SDL_Renderer *renderer, SDL_GPUDev
|
|||
utilTrace("Renderer: %s%s%s", SDL_GetRendererName(renderer), (device != NULL) ? ", GPU driver " : " (no GPU device; 3D is unavailable)", (device != NULL) ? SDL_GetGPUDeviceDriver(device) : "");
|
||||
utilTrace("Decoder: %s", videoGetDecoderDescription());
|
||||
utilTrace("Audio: %s", audio);
|
||||
utilTrace("SoundFont: %s", midiSoundfont());
|
||||
utilTrace("MIDI: %s", midiIoDescription());
|
||||
utilTrace("SDL: built %d.%d.%d, linked %d.%d.%d", SDL_VERSIONNUM_MAJOR(built), SDL_VERSIONNUM_MINOR(built), SDL_VERSIONNUM_MICRO(built), SDL_VERSIONNUM_MAJOR(linked), SDL_VERSIONNUM_MINOR(linked), SDL_VERSIONNUM_MICRO(linked));
|
||||
utilTrace("Settings: %s", (_settingsSummary != NULL) ? _settingsSummary : "none");
|
||||
utilTrace("Game: %s%s%s", conf->scriptFile, (conf->container != NULL) ? " in " : "", (conf->container != NULL) ? conf->container : "");
|
||||
|
|
@ -1824,6 +1866,8 @@ ConfigT *cloneConf(const ConfigT *conf) {
|
|||
c->bezelFile = _cloneString(conf->bezelFile);
|
||||
c->bezelDir = _cloneString(conf->bezelDir);
|
||||
c->keymapFile = _cloneString(conf->keymapFile);
|
||||
c->soundfont = _cloneString(conf->soundfont);
|
||||
c->deinterlace = conf->deinterlace;
|
||||
c->audioSuffix = _cloneString(conf->audioSuffix);
|
||||
c->gamepadOrder = _cloneString(conf->gamepadOrder);
|
||||
c->videoFile = _cloneString(conf->videoFile);
|
||||
|
|
@ -1907,6 +1951,7 @@ void destroyConf(ConfigT **confPointer) {
|
|||
free(conf->bezelFile);
|
||||
free(conf->bezelDir);
|
||||
free(conf->keymapFile);
|
||||
free(conf->soundfont);
|
||||
free(conf->audioSuffix);
|
||||
free(conf->gamepadOrder);
|
||||
free(conf);
|
||||
|
|
@ -2090,6 +2135,8 @@ int main(int argc, char *argv[]) {
|
|||
}
|
||||
|
||||
_stopSDL();
|
||||
midiIoQuit();
|
||||
midiQuit();
|
||||
vfsQuit();
|
||||
free(_commandLine);
|
||||
free(_settingsSummary);
|
||||
|
|
|
|||
227
src/midi.c
Normal file
227
src/midi.c
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
/*
|
||||
*
|
||||
* Singe 3
|
||||
* Copyright (C) 2006-2026 Scott Duensing <scott@kangaroopunch.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
// Singe: MIDI through TinySoundFont. SDL_mixer's own MIDI decoder is TiMidity, which wants a GUS
|
||||
// patch set nobody installs any more; this reads a SoundFont instead, which is what a person who
|
||||
// wants MIDI to sound like something will already have. There is no MIDI hardware here in either
|
||||
// direction: a file is synthesised to samples at load, and that is all.
|
||||
|
||||
#include <string.h>
|
||||
#include <SDL3/SDL.h>
|
||||
#include "common.h"
|
||||
#include "decode.h"
|
||||
#include "midi.h"
|
||||
#include "util.h"
|
||||
#include "vfs.h"
|
||||
|
||||
#define TSF_IMPLEMENTATION
|
||||
#include "../thirdparty/tinysoundfont/tsf.h"
|
||||
#define TML_IMPLEMENTATION
|
||||
#include "../thirdparty/tinysoundfont/tml.h"
|
||||
|
||||
#define MIDI_SAMPLE_RATE 44100
|
||||
#define MS_PER_SECOND 1000
|
||||
#define MIDI_CHANNELS 2
|
||||
#define MIDI_BLOCK_SAMPLES 512 // Messages land on a block boundary, 11.6 ms apart
|
||||
#define MIDI_TAIL_MS 3000 // Rendered past the last message so releases finish
|
||||
#define MIDI_MAX_MS (20 * 60 * 1000) // A MIDI longer than this is a mistake, not music
|
||||
#define MIDI_MAX_VOICES 64
|
||||
#define MIDI_HEADER_BYTES 4
|
||||
#define MIDI_DRUM_CHANNEL 9
|
||||
#define MIDI_VELOCITY_MAX 127.0f
|
||||
#define MIDI_GAIN_DB (-3.0f) // A little headroom: many banks clip at full scale
|
||||
|
||||
|
||||
static const char *_paths[] = {
|
||||
// The support folder the engine extracts, so a game can carry its own bank, and a bare file
|
||||
// beside the game for someone who dropped one in.
|
||||
"Singe/soundfont.sf2",
|
||||
"soundfont.sf2",
|
||||
// Where the distributions put the banks their own MIDI players use.
|
||||
"/usr/share/soundfonts/default.sf2",
|
||||
"/usr/share/soundfonts/FluidR3_GM.sf2",
|
||||
"/usr/share/sounds/sf2/default-GM.sf2",
|
||||
"/usr/share/sounds/sf2/FluidR3_GM.sf2",
|
||||
"/usr/share/sounds/sf2/TimGM6mb.sf2"
|
||||
};
|
||||
|
||||
static tsf *_soundfont = NULL;
|
||||
static char *_found = NULL;
|
||||
|
||||
|
||||
static void _apply(tml_message *message);
|
||||
static bool _load(const char *path);
|
||||
|
||||
|
||||
// ===== Internal helpers =====
|
||||
|
||||
// One MIDI message into the synthesiser. Anything else in the file is nothing a synthesiser acts on.
|
||||
static void _apply(tml_message *message) {
|
||||
switch (message->type) {
|
||||
case TML_PROGRAM_CHANGE:
|
||||
tsf_channel_set_presetnumber(_soundfont, message->channel, message->program, (message->channel == MIDI_DRUM_CHANNEL));
|
||||
break;
|
||||
case TML_NOTE_ON:
|
||||
tsf_channel_note_on(_soundfont, message->channel, message->key, (float)message->velocity / MIDI_VELOCITY_MAX);
|
||||
break;
|
||||
case TML_NOTE_OFF:
|
||||
tsf_channel_note_off(_soundfont, message->channel, message->key);
|
||||
break;
|
||||
case TML_PITCH_BEND:
|
||||
tsf_channel_set_pitchwheel(_soundfont, message->channel, message->pitch_bend);
|
||||
break;
|
||||
case TML_CONTROL_CHANGE:
|
||||
tsf_channel_midi_control(_soundfont, message->channel, message->control, message->control_value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Reads one candidate. The vfs is asked first, so a packed game's own bank is found and a name is
|
||||
// resolved the way every other name in a game is; the file system is the fallback, which is what
|
||||
// finds the bank a distribution installed somewhere absolute. Asking in that order rather than
|
||||
// looking at the shape of the path keeps it working on Windows, where an absolute path does not
|
||||
// begin with a slash.
|
||||
static bool _load(const char *path) {
|
||||
size_t size = 0;
|
||||
char *bytes = vfsRead(path, &size);
|
||||
|
||||
if (bytes != NULL) {
|
||||
if (size <= INT32_MAX) {
|
||||
_soundfont = tsf_load_memory(bytes, (int32_t)size);
|
||||
}
|
||||
free(bytes);
|
||||
} else {
|
||||
_soundfont = tsf_load_filename(path);
|
||||
}
|
||||
if (_soundfont == NULL) {
|
||||
return false;
|
||||
}
|
||||
tsf_set_max_voices(_soundfont, MIDI_MAX_VOICES);
|
||||
tsf_set_output(_soundfont, TSF_STEREO_INTERLEAVED, MIDI_SAMPLE_RATE, MIDI_GAIN_DB);
|
||||
_found = utilCreateString("%s", path);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ===== Public =====
|
||||
|
||||
void midiInit(const char *wanted) {
|
||||
int32_t x;
|
||||
|
||||
if (wanted != NULL) {
|
||||
if (!_load(wanted)) {
|
||||
utilTrace("No MIDI sound bank: %s could not be read as a SoundFont.", wanted);
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (x = 0; x < (int32_t)(sizeof(_paths) / sizeof(_paths[0])); x++) {
|
||||
if (_load(_paths[x])) {
|
||||
utilTrace("MIDI sound bank: %s, %d presets.", _found, tsf_get_presetcount(_soundfont));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool midiIs(const void *bytes, size_t size) {
|
||||
return (size > MIDI_HEADER_BYTES) && (memcmp(bytes, "MThd", MIDI_HEADER_BYTES) == 0);
|
||||
}
|
||||
|
||||
|
||||
bool midiRender(const void *bytes, size_t size, DecodedAudioT *out) {
|
||||
tml_message *messages = NULL;
|
||||
tml_message *message = NULL;
|
||||
float *pcm = NULL;
|
||||
uint32_t length = 0;
|
||||
int64_t total = 0;
|
||||
int64_t done = 0;
|
||||
int32_t block = 0;
|
||||
double played = 0.0;
|
||||
double perBlock = (double)MIDI_BLOCK_SAMPLES * MS_PER_SECOND / MIDI_SAMPLE_RATE;
|
||||
|
||||
memset(out, 0, sizeof(*out));
|
||||
if (_soundfont == NULL) {
|
||||
SDL_SetError("This is a MIDI file, and no SoundFont was found to play it with. Name one with --soundfont.");
|
||||
return false;
|
||||
}
|
||||
if (size > INT32_MAX) {
|
||||
SDL_SetError("The MIDI file is too large.");
|
||||
return false;
|
||||
}
|
||||
messages = tml_load_memory(bytes, (int32_t)size);
|
||||
if (messages == NULL) {
|
||||
SDL_SetError("The MIDI file would not parse.");
|
||||
return false;
|
||||
}
|
||||
tml_get_info(messages, NULL, NULL, NULL, NULL, &length);
|
||||
if ((length + MIDI_TAIL_MS) > MIDI_MAX_MS) {
|
||||
length = MIDI_MAX_MS - MIDI_TAIL_MS;
|
||||
}
|
||||
// Whole blocks, so the render loop never has a partial one to think about.
|
||||
total = (((int64_t)length + MIDI_TAIL_MS) * MIDI_SAMPLE_RATE / MS_PER_SECOND + MIDI_BLOCK_SAMPLES - 1) / MIDI_BLOCK_SAMPLES * MIDI_BLOCK_SAMPLES;
|
||||
pcm = (float *)SDL_malloc((size_t)total * MIDI_CHANNELS * sizeof(float));
|
||||
if (pcm == NULL) {
|
||||
SDL_SetError("Out of memory rendering a MIDI file.");
|
||||
tml_free(messages);
|
||||
return false;
|
||||
}
|
||||
tsf_reset(_soundfont);
|
||||
message = messages;
|
||||
while (done < total) {
|
||||
block = (int32_t)((total - done > MIDI_BLOCK_SAMPLES) ? MIDI_BLOCK_SAMPLES : (total - done));
|
||||
played += perBlock;
|
||||
while ((message != NULL) && (message->time <= (uint32_t)played)) {
|
||||
_apply(message);
|
||||
message = message->next;
|
||||
}
|
||||
tsf_render_float(_soundfont, pcm + done * MIDI_CHANNELS, block, 0);
|
||||
done += block;
|
||||
}
|
||||
tml_free(messages);
|
||||
out->pcm = (uint8_t *)pcm;
|
||||
out->bytes = (size_t)total * MIDI_CHANNELS * sizeof(float);
|
||||
out->spec.format = SDL_AUDIO_F32;
|
||||
out->spec.channels = MIDI_CHANNELS;
|
||||
out->spec.freq = MIDI_SAMPLE_RATE;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void midiQuit(void) {
|
||||
if (_soundfont != NULL) {
|
||||
tsf_close(_soundfont);
|
||||
_soundfont = NULL;
|
||||
}
|
||||
free(_found);
|
||||
_found = NULL;
|
||||
}
|
||||
|
||||
|
||||
const char *midiSoundfont(void) {
|
||||
return (_found != NULL) ? _found : "none found (MIDI will not play; name one with --soundfont)";
|
||||
}
|
||||
56
src/midi.h
Normal file
56
src/midi.h
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
*
|
||||
* Singe 3
|
||||
* Copyright (C) 2006-2026 Scott Duensing <scott@kangaroopunch.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef MIDI_H
|
||||
#define MIDI_H
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "decode.h"
|
||||
|
||||
|
||||
// Playing a MIDI file means synthesising it, and synthesising it means a sound bank. Singe ships
|
||||
// none: a good one is tens of megabytes and would be carried by every platform for a format almost
|
||||
// no game uses. Point --soundfont at a .sf2, or put one where midiInit looks, and MIDI plays.
|
||||
|
||||
// Finds and loads the sound bank. Called once at start up; quiet when there is none to find.
|
||||
void midiInit(const char *wanted);
|
||||
|
||||
// Whether the bytes begin a standard MIDI file. True regardless of whether a bank is loaded, so
|
||||
// the caller can tell "this is a MIDI" from "this will not play".
|
||||
bool midiIs(const void *bytes, size_t size);
|
||||
|
||||
// Renders a whole MIDI file to interleaved float samples. False when there is no sound bank or the
|
||||
// file will not parse, with the reason in SDL_GetError.
|
||||
bool midiRender(const void *bytes, size_t size, DecodedAudioT *out);
|
||||
|
||||
// Releases the sound bank.
|
||||
void midiQuit(void);
|
||||
|
||||
// The bank in use for the trace header, or a line saying why there is none. Never NULL.
|
||||
const char *midiSoundfont(void);
|
||||
|
||||
|
||||
#endif // MIDI_H
|
||||
951
src/midiIo.c
Normal file
951
src/midiIo.c
Normal file
|
|
@ -0,0 +1,951 @@
|
|||
/*
|
||||
*
|
||||
* Singe 3
|
||||
* Copyright (C) 2006-2026 Scott Duensing <scott@kangaroopunch.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
// Singe: MIDI ports, in and out. Three platforms, three system interfaces, one shape: a list of
|
||||
// ports, one open in each direction, raw messages both ways. Received messages queue on whatever
|
||||
// thread the driver uses and are handed over on the engine's thread by midiIoPoll.
|
||||
//
|
||||
// On Linux the ALSA library is opened at run time rather than linked, which is what SDL does with
|
||||
// the same library for audio, so one binary runs on a machine that has it and on one that does not;
|
||||
// on the second, MIDI ports simply do not exist. The ports come from ALSA's sequencer, so they are
|
||||
// both the hardware interfaces and any synthesiser or other program that has registered one.
|
||||
//
|
||||
// Nothing here is opened until a script asks about a port. A game that never mentions MIDI pays
|
||||
// neither the file descriptor nor the driver configuration behind it.
|
||||
|
||||
#include <string.h>
|
||||
#include <SDL3/SDL.h>
|
||||
#include "common.h"
|
||||
#include "midiIo.h"
|
||||
#include "util.h"
|
||||
|
||||
#define PORT_NAME_MAX 128
|
||||
#define PORTS_MAX 32
|
||||
#define MESSAGE_MAX 256 // A message longer than this is a sysex dump, which no game wants
|
||||
#define MESSAGE_SHORT 3 // A status byte and up to two data bytes, which is most messages
|
||||
#define QUEUE_BYTES 8192
|
||||
#define DESCRIPTION_MAX 160
|
||||
|
||||
|
||||
typedef struct {
|
||||
char name[PORT_NAME_MAX];
|
||||
char address[PORT_NAME_MAX]; // How the platform names it, which is not always how a person does
|
||||
} MidiPortT;
|
||||
|
||||
|
||||
static bool _started = false; // Whether the platform's MIDI system has been opened yet
|
||||
static MidiPortT _inputs[PORTS_MAX];
|
||||
static MidiPortT _outputs[PORTS_MAX];
|
||||
static int32_t _inputCount = 0;
|
||||
static int32_t _outputCount = 0;
|
||||
static bool _available = false;
|
||||
static bool _inputOpen = false;
|
||||
static bool _outputOpen = false;
|
||||
static char _description[DESCRIPTION_MAX] = "not started";
|
||||
static SDL_Mutex *_lock = NULL;
|
||||
static uint8_t _queue[QUEUE_BYTES];
|
||||
static size_t _queueHead = 0;
|
||||
static size_t _queueTail = 0;
|
||||
static size_t _queueUsed = 0;
|
||||
static bool _queueLost = false;
|
||||
|
||||
|
||||
static void _describe(void);
|
||||
static bool _start(void);
|
||||
static bool _openBackend(void);
|
||||
static bool _openPort(bool input, int32_t index);
|
||||
static void _push(const uint8_t *bytes, size_t size);
|
||||
static void _scan(void);
|
||||
static bool _send(const uint8_t *bytes, size_t size);
|
||||
static void _closeBackend(void);
|
||||
static void _closePort(bool input);
|
||||
static void _receive(void);
|
||||
|
||||
|
||||
// ===== Linux: the ALSA sequencer, opened at run time =====
|
||||
|
||||
#if defined(__linux__) && !defined(__APPLE__)
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <alsa/asoundlib.h>
|
||||
|
||||
// The library is opened rather than linked, which is what SDL does with the same library for audio,
|
||||
// so the binary still starts on a machine without it. The declarations come from alsa's own
|
||||
// headers so the compiler checks them; only the symbols are late bound.
|
||||
#define ALSA_LIBRARY "libasound.so.2"
|
||||
#define ALSA_CLIENT "Singe"
|
||||
#define ALSA_PORT "Singe MIDI"
|
||||
#define ALSA_ADDRESS "%d:%d"
|
||||
|
||||
static void *_alsa = NULL;
|
||||
static snd_seq_t *_seq = NULL;
|
||||
static snd_midi_event_t *_codec = NULL;
|
||||
static int32_t _seqPort = -1;
|
||||
static int32_t _inClient = -1;
|
||||
static int32_t _inPort = -1;
|
||||
static int32_t _outClient = -1;
|
||||
static int32_t _outPort = -1;
|
||||
|
||||
static int (*_seqOpen)(snd_seq_t **, const char *, int, int);
|
||||
static int (*_seqClose)(snd_seq_t *);
|
||||
static int (*_seqSetClientName)(snd_seq_t *, const char *);
|
||||
static int (*_seqClientId)(snd_seq_t *);
|
||||
static int (*_seqCreatePort)(snd_seq_t *, const char *, unsigned int, unsigned int);
|
||||
static int (*_seqDeletePort)(snd_seq_t *, int);
|
||||
static int (*_seqConnectTo)(snd_seq_t *, int, int, int);
|
||||
static int (*_seqConnectFrom)(snd_seq_t *, int, int, int);
|
||||
static int (*_seqDisconnectTo)(snd_seq_t *, int, int, int);
|
||||
static int (*_seqDisconnectFrom)(snd_seq_t *, int, int, int);
|
||||
static int (*_seqEventOutputDirect)(snd_seq_t *, snd_seq_event_t *);
|
||||
static int (*_seqEventInput)(snd_seq_t *, snd_seq_event_t **);
|
||||
static int (*_seqClientInfoMalloc)(snd_seq_client_info_t **);
|
||||
static void (*_seqClientInfoFree)(snd_seq_client_info_t *);
|
||||
static void (*_seqClientInfoSetClient)(snd_seq_client_info_t *, int);
|
||||
static int (*_seqQueryNextClient)(snd_seq_t *, snd_seq_client_info_t *);
|
||||
static int (*_seqClientInfoGetClient)(const snd_seq_client_info_t *);
|
||||
static const char *(*_seqClientInfoGetName)(snd_seq_client_info_t *);
|
||||
static int (*_seqPortInfoMalloc)(snd_seq_port_info_t **);
|
||||
static void (*_seqPortInfoFree)(snd_seq_port_info_t *);
|
||||
static void (*_seqPortInfoSetClient)(snd_seq_port_info_t *, int);
|
||||
static void (*_seqPortInfoSetPort)(snd_seq_port_info_t *, int);
|
||||
static int (*_seqQueryNextPort)(snd_seq_t *, snd_seq_port_info_t *);
|
||||
static int (*_seqPortInfoGetPort)(const snd_seq_port_info_t *);
|
||||
static const char *(*_seqPortInfoGetName)(const snd_seq_port_info_t *);
|
||||
static unsigned int (*_seqPortInfoGetCapability)(const snd_seq_port_info_t *);
|
||||
static int (*_midiEventNew)(size_t, snd_midi_event_t **);
|
||||
static void (*_midiEventFree)(snd_midi_event_t *);
|
||||
static void (*_midiEventInit)(snd_midi_event_t *);
|
||||
static long (*_midiEventEncode)(snd_midi_event_t *, const unsigned char *, long, snd_seq_event_t *);
|
||||
static long (*_midiEventDecode)(snd_midi_event_t *, unsigned char *, long, const snd_seq_event_t *);
|
||||
|
||||
// One table rather than thirty dlsym lines, so a missing symbol is found by the loop that loads it.
|
||||
static const struct {
|
||||
const char *name;
|
||||
void **slot;
|
||||
} _symbols[] = {
|
||||
{ "snd_seq_open", (void **)&_seqOpen },
|
||||
{ "snd_seq_close", (void **)&_seqClose },
|
||||
{ "snd_seq_set_client_name", (void **)&_seqSetClientName },
|
||||
{ "snd_seq_client_id", (void **)&_seqClientId },
|
||||
{ "snd_seq_create_simple_port", (void **)&_seqCreatePort },
|
||||
{ "snd_seq_delete_simple_port", (void **)&_seqDeletePort },
|
||||
{ "snd_seq_connect_to", (void **)&_seqConnectTo },
|
||||
{ "snd_seq_connect_from", (void **)&_seqConnectFrom },
|
||||
{ "snd_seq_disconnect_to", (void **)&_seqDisconnectTo },
|
||||
{ "snd_seq_disconnect_from", (void **)&_seqDisconnectFrom },
|
||||
{ "snd_seq_event_output_direct", (void **)&_seqEventOutputDirect },
|
||||
{ "snd_seq_event_input", (void **)&_seqEventInput },
|
||||
{ "snd_seq_client_info_malloc", (void **)&_seqClientInfoMalloc },
|
||||
{ "snd_seq_client_info_free", (void **)&_seqClientInfoFree },
|
||||
{ "snd_seq_client_info_set_client", (void **)&_seqClientInfoSetClient },
|
||||
{ "snd_seq_query_next_client", (void **)&_seqQueryNextClient },
|
||||
{ "snd_seq_client_info_get_client", (void **)&_seqClientInfoGetClient },
|
||||
{ "snd_seq_client_info_get_name", (void **)&_seqClientInfoGetName },
|
||||
{ "snd_seq_port_info_malloc", (void **)&_seqPortInfoMalloc },
|
||||
{ "snd_seq_port_info_free", (void **)&_seqPortInfoFree },
|
||||
{ "snd_seq_port_info_set_client", (void **)&_seqPortInfoSetClient },
|
||||
{ "snd_seq_port_info_set_port", (void **)&_seqPortInfoSetPort },
|
||||
{ "snd_seq_query_next_port", (void **)&_seqQueryNextPort },
|
||||
{ "snd_seq_port_info_get_port", (void **)&_seqPortInfoGetPort },
|
||||
{ "snd_seq_port_info_get_name", (void **)&_seqPortInfoGetName },
|
||||
{ "snd_seq_port_info_get_capability",(void **)&_seqPortInfoGetCapability },
|
||||
{ "snd_midi_event_new", (void **)&_midiEventNew },
|
||||
{ "snd_midi_event_free", (void **)&_midiEventFree },
|
||||
{ "snd_midi_event_init", (void **)&_midiEventInit },
|
||||
{ "snd_midi_event_encode", (void **)&_midiEventEncode },
|
||||
{ "snd_midi_event_decode", (void **)&_midiEventDecode }
|
||||
};
|
||||
|
||||
#define ALSA_INPUT_CAPS (SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_SUBS_READ)
|
||||
#define ALSA_OUTPUT_CAPS (SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE)
|
||||
|
||||
|
||||
static void _describe(void) {
|
||||
if (!_available) {
|
||||
SDL_strlcpy(_description, "no MIDI ports (" ALSA_LIBRARY " is not installed)", sizeof(_description));
|
||||
return;
|
||||
}
|
||||
SDL_snprintf(_description, sizeof(_description), "ALSA sequencer, %d in, %d out", _inputCount, _outputCount);
|
||||
}
|
||||
|
||||
|
||||
static bool _openBackend(void) {
|
||||
size_t x;
|
||||
|
||||
_alsa = dlopen(ALSA_LIBRARY, RTLD_LAZY | RTLD_LOCAL);
|
||||
if (_alsa == NULL) {
|
||||
return false;
|
||||
}
|
||||
for (x = 0; x < sizeof(_symbols) / sizeof(_symbols[0]); x++) {
|
||||
*_symbols[x].slot = dlsym(_alsa, _symbols[x].name);
|
||||
if (*_symbols[x].slot == NULL) {
|
||||
utilTrace("MIDI: %s has no %s; MIDI ports are unavailable.", ALSA_LIBRARY, _symbols[x].name);
|
||||
dlclose(_alsa);
|
||||
_alsa = NULL;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Non-blocking, because the engine asks for messages once a frame and must never wait for one.
|
||||
if (_seqOpen(&_seq, "default", SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK) < 0) {
|
||||
_seq = NULL;
|
||||
dlclose(_alsa);
|
||||
_alsa = NULL;
|
||||
return false;
|
||||
}
|
||||
_seqSetClientName(_seq, ALSA_CLIENT);
|
||||
_seqPort = _seqCreatePort(_seq, ALSA_PORT, ALSA_INPUT_CAPS | ALSA_OUTPUT_CAPS, SND_SEQ_PORT_TYPE_APPLICATION | SND_SEQ_PORT_TYPE_MIDI_GENERIC);
|
||||
if ((_seqPort < 0) || (_midiEventNew(MESSAGE_MAX, &_codec) < 0)) {
|
||||
_seqClose(_seq);
|
||||
_seq = NULL;
|
||||
dlclose(_alsa);
|
||||
_alsa = NULL;
|
||||
return false;
|
||||
}
|
||||
_midiEventInit(_codec);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void _closeBackend(void) {
|
||||
if (_codec != NULL) {
|
||||
_midiEventFree(_codec);
|
||||
_codec = NULL;
|
||||
}
|
||||
if (_seq != NULL) {
|
||||
if (_seqPort >= 0) {
|
||||
_seqDeletePort(_seq, _seqPort);
|
||||
_seqPort = -1;
|
||||
}
|
||||
_seqClose(_seq);
|
||||
_seq = NULL;
|
||||
}
|
||||
if (_alsa != NULL) {
|
||||
dlclose(_alsa);
|
||||
_alsa = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void _closePort(bool input) {
|
||||
if (input) {
|
||||
if (_inClient >= 0) {
|
||||
_seqDisconnectFrom(_seq, _seqPort, _inClient, _inPort);
|
||||
_inClient = -1;
|
||||
_inPort = -1;
|
||||
}
|
||||
} else if (_outClient >= 0) {
|
||||
_seqDisconnectTo(_seq, _seqPort, _outClient, _outPort);
|
||||
_outClient = -1;
|
||||
_outPort = -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool _openPort(bool input, int32_t index) {
|
||||
const MidiPortT *port = input ? &_inputs[index] : &_outputs[index];
|
||||
int32_t client = -1;
|
||||
int32_t number = -1;
|
||||
|
||||
if (SDL_sscanf(port->address, ALSA_ADDRESS, &client, &number) != 2) {
|
||||
return false;
|
||||
}
|
||||
if (input) {
|
||||
if (_seqConnectFrom(_seq, _seqPort, client, number) < 0) {
|
||||
return false;
|
||||
}
|
||||
_inClient = client;
|
||||
_inPort = number;
|
||||
return true;
|
||||
}
|
||||
if (_seqConnectTo(_seq, _seqPort, client, number) < 0) {
|
||||
return false;
|
||||
}
|
||||
_outClient = client;
|
||||
_outPort = number;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void _receive(void) {
|
||||
snd_seq_event_t *event = NULL;
|
||||
uint8_t bytes[MESSAGE_MAX];
|
||||
long size;
|
||||
|
||||
if (_seq == NULL) {
|
||||
return;
|
||||
}
|
||||
// Non-blocking, so this drains what has arrived and then returns an error meaning "no more".
|
||||
while (_seqEventInput(_seq, &event) >= 0) {
|
||||
if (event == NULL) {
|
||||
break;
|
||||
}
|
||||
size = _midiEventDecode(_codec, bytes, (long)sizeof(bytes), event);
|
||||
if (size > 0) {
|
||||
_push(bytes, (size_t)size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Every port of every client on the sequencer, which is both the hardware interfaces and any
|
||||
// synthesiser or other program that has registered one. Our own client is skipped.
|
||||
static void _scan(void) {
|
||||
snd_seq_client_info_t *client = NULL;
|
||||
snd_seq_port_info_t *port = NULL;
|
||||
unsigned int caps = 0;
|
||||
int mine = _seqClientId(_seq);
|
||||
int number = 0;
|
||||
|
||||
_inputCount = 0;
|
||||
_outputCount = 0;
|
||||
if ((_seqClientInfoMalloc(&client) < 0) || (_seqPortInfoMalloc(&port) < 0)) {
|
||||
_seqClientInfoFree(client);
|
||||
return;
|
||||
}
|
||||
_seqClientInfoSetClient(client, -1);
|
||||
while (_seqQueryNextClient(_seq, client) >= 0) {
|
||||
number = _seqClientInfoGetClient(client);
|
||||
// Our own ports, and the sequencer's Timer and Announce, which carry no music.
|
||||
if ((number == mine) || (number == SND_SEQ_CLIENT_SYSTEM)) {
|
||||
continue;
|
||||
}
|
||||
_seqPortInfoSetClient(port, number);
|
||||
_seqPortInfoSetPort(port, -1);
|
||||
while (_seqQueryNextPort(_seq, port) >= 0) {
|
||||
caps = _seqPortInfoGetCapability(port);
|
||||
if (((caps & ALSA_INPUT_CAPS) == ALSA_INPUT_CAPS) && (_inputCount < PORTS_MAX)) {
|
||||
SDL_snprintf(_inputs[_inputCount].name, PORT_NAME_MAX, "%s: %s", _seqClientInfoGetName(client), _seqPortInfoGetName(port));
|
||||
SDL_snprintf(_inputs[_inputCount].address, PORT_NAME_MAX, ALSA_ADDRESS, number, _seqPortInfoGetPort(port));
|
||||
_inputCount++;
|
||||
}
|
||||
if (((caps & ALSA_OUTPUT_CAPS) == ALSA_OUTPUT_CAPS) && (_outputCount < PORTS_MAX)) {
|
||||
SDL_snprintf(_outputs[_outputCount].name, PORT_NAME_MAX, "%s: %s", _seqClientInfoGetName(client), _seqPortInfoGetName(port));
|
||||
SDL_snprintf(_outputs[_outputCount].address, PORT_NAME_MAX, ALSA_ADDRESS, number, _seqPortInfoGetPort(port));
|
||||
_outputCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
_seqPortInfoFree(port);
|
||||
_seqClientInfoFree(client);
|
||||
}
|
||||
|
||||
|
||||
static bool _send(const uint8_t *bytes, size_t size) {
|
||||
snd_seq_event_t event;
|
||||
|
||||
if (_outClient < 0) {
|
||||
return false;
|
||||
}
|
||||
snd_seq_ev_clear(&event);
|
||||
// Encoding first: it fills the event in, and the addressing has to survive that.
|
||||
if (_midiEventEncode(_codec, bytes, (long)size, &event) <= 0) {
|
||||
return false;
|
||||
}
|
||||
snd_seq_ev_set_source(&event, _seqPort);
|
||||
snd_seq_ev_set_subs(&event);
|
||||
snd_seq_ev_set_direct(&event);
|
||||
|
||||
return (_seqEventOutputDirect(_seq, &event) >= 0);
|
||||
}
|
||||
|
||||
|
||||
// ===== macOS: CoreMIDI =====
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
|
||||
#include <CoreMIDI/CoreMIDI.h>
|
||||
|
||||
#define COREMIDI_PACKET_BYTES 1024
|
||||
|
||||
static MIDIClientRef _client = 0;
|
||||
static MIDIPortRef _inPort = 0;
|
||||
static MIDIPortRef _outPort = 0;
|
||||
static MIDIEndpointRef _inSource = 0;
|
||||
static MIDIEndpointRef _outDest = 0;
|
||||
|
||||
|
||||
static void _coreMidiRead(const MIDIPacketList *packets, void *context, void *source);
|
||||
static void _endpointName(MIDIEndpointRef endpoint, char *out, size_t size);
|
||||
|
||||
|
||||
static void _coreMidiRead(const MIDIPacketList *packets, void *context, void *source) {
|
||||
const MIDIPacket *packet = &packets->packet[0];
|
||||
uint32_t x;
|
||||
|
||||
(void)context;
|
||||
(void)source;
|
||||
for (x = 0; x < packets->numPackets; x++) {
|
||||
_push(packet->data, packet->length);
|
||||
packet = MIDIPacketNext(packet);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void _endpointName(MIDIEndpointRef endpoint, char *out, size_t size) {
|
||||
CFStringRef name = NULL;
|
||||
|
||||
out[0] = '\0';
|
||||
if (MIDIObjectGetStringProperty(endpoint, kMIDIPropertyDisplayName, &name) == noErr) {
|
||||
CFStringGetCString(name, out, (CFIndex)size, kCFStringEncodingUTF8);
|
||||
CFRelease(name);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void _describe(void) {
|
||||
if (!_available) {
|
||||
SDL_strlcpy(_description, "no MIDI ports (CoreMIDI would not start)", sizeof(_description));
|
||||
return;
|
||||
}
|
||||
SDL_snprintf(_description, sizeof(_description), "CoreMIDI, %d in, %d out", _inputCount, _outputCount);
|
||||
}
|
||||
|
||||
|
||||
static bool _openBackend(void) {
|
||||
CFStringRef name = CFSTR("Singe");
|
||||
|
||||
if (MIDIClientCreate(name, NULL, NULL, &_client) != noErr) {
|
||||
return false;
|
||||
}
|
||||
if (MIDIInputPortCreate(_client, CFSTR("Singe In"), _coreMidiRead, NULL, &_inPort) != noErr) {
|
||||
MIDIClientDispose(_client);
|
||||
_client = 0;
|
||||
return false;
|
||||
}
|
||||
if (MIDIOutputPortCreate(_client, CFSTR("Singe Out"), &_outPort) != noErr) {
|
||||
MIDIPortDispose(_inPort);
|
||||
MIDIClientDispose(_client);
|
||||
_client = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void _closeBackend(void) {
|
||||
if (_outPort != 0) {
|
||||
MIDIPortDispose(_outPort);
|
||||
_outPort = 0;
|
||||
}
|
||||
if (_inPort != 0) {
|
||||
MIDIPortDispose(_inPort);
|
||||
_inPort = 0;
|
||||
}
|
||||
if (_client != 0) {
|
||||
MIDIClientDispose(_client);
|
||||
_client = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void _closePort(bool input) {
|
||||
if (input) {
|
||||
if (_inSource != 0) {
|
||||
MIDIPortDisconnectSource(_inPort, _inSource);
|
||||
_inSource = 0;
|
||||
}
|
||||
} else {
|
||||
_outDest = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool _openPort(bool input, int32_t index) {
|
||||
if (input) {
|
||||
_inSource = MIDIGetSource((ItemCount)index);
|
||||
if (_inSource == 0) {
|
||||
return false;
|
||||
}
|
||||
if (MIDIPortConnectSource(_inPort, _inSource, NULL) != noErr) {
|
||||
_inSource = 0;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
_outDest = MIDIGetDestination((ItemCount)index);
|
||||
|
||||
return (_outDest != 0);
|
||||
}
|
||||
|
||||
|
||||
// CoreMIDI delivers on its own thread through _coreMidiRead, so there is nothing to pull.
|
||||
static void _receive(void) {
|
||||
}
|
||||
|
||||
|
||||
static void _scan(void) {
|
||||
ItemCount count;
|
||||
ItemCount x;
|
||||
|
||||
_inputCount = 0;
|
||||
_outputCount = 0;
|
||||
count = MIDIGetNumberOfSources();
|
||||
for (x = 0; (x < count) && (_inputCount < PORTS_MAX); x++) {
|
||||
_endpointName(MIDIGetSource(x), _inputs[_inputCount].name, PORT_NAME_MAX);
|
||||
SDL_snprintf(_inputs[_inputCount].address, PORT_NAME_MAX, "%u", (unsigned)x);
|
||||
_inputCount++;
|
||||
}
|
||||
count = MIDIGetNumberOfDestinations();
|
||||
for (x = 0; (x < count) && (_outputCount < PORTS_MAX); x++) {
|
||||
_endpointName(MIDIGetDestination(x), _outputs[_outputCount].name, PORT_NAME_MAX);
|
||||
SDL_snprintf(_outputs[_outputCount].address, PORT_NAME_MAX, "%u", (unsigned)x);
|
||||
_outputCount++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool _send(const uint8_t *bytes, size_t size) {
|
||||
uint8_t storage[COREMIDI_PACKET_BYTES];
|
||||
MIDIPacketList *list = (MIDIPacketList *)storage;
|
||||
MIDIPacket *packet = NULL;
|
||||
|
||||
if (_outDest == 0) {
|
||||
return false;
|
||||
}
|
||||
packet = MIDIPacketListInit(list);
|
||||
packet = MIDIPacketListAdd(list, sizeof(storage), packet, 0, size, bytes);
|
||||
if (packet == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (MIDISend(_outPort, _outDest, list) == noErr);
|
||||
}
|
||||
|
||||
|
||||
// ===== Windows: the multimedia MIDI calls =====
|
||||
|
||||
#elif defined(_WIN32)
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
|
||||
#define WINDOWS_SYSEX_BYTES MESSAGE_MAX
|
||||
|
||||
static HMIDIOUT _windowsOut = NULL;
|
||||
static HMIDIIN _windowsIn = NULL;
|
||||
|
||||
|
||||
static void CALLBACK _windowsRead(HMIDIIN handle, UINT message, DWORD_PTR instance, DWORD_PTR first, DWORD_PTR second);
|
||||
|
||||
|
||||
static void CALLBACK _windowsRead(HMIDIIN handle, UINT message, DWORD_PTR instance, DWORD_PTR first, DWORD_PTR second) {
|
||||
uint8_t bytes[3];
|
||||
|
||||
(void)handle;
|
||||
(void)instance;
|
||||
(void)second;
|
||||
if (message != MIM_DATA) {
|
||||
return;
|
||||
}
|
||||
bytes[0] = (uint8_t)(first & 0xFF);
|
||||
bytes[1] = (uint8_t)((first >> 8) & 0xFF);
|
||||
bytes[2] = (uint8_t)((first >> 16) & 0xFF);
|
||||
_push(bytes, sizeof(bytes));
|
||||
}
|
||||
|
||||
|
||||
static void _describe(void) {
|
||||
if (!_available) {
|
||||
SDL_strlcpy(_description, "no MIDI ports", sizeof(_description));
|
||||
return;
|
||||
}
|
||||
SDL_snprintf(_description, sizeof(_description), "Windows multimedia MIDI, %d in, %d out", _inputCount, _outputCount);
|
||||
}
|
||||
|
||||
|
||||
static bool _openBackend(void) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static void _closeBackend(void) {
|
||||
}
|
||||
|
||||
|
||||
static void _closePort(bool input) {
|
||||
if (input) {
|
||||
if (_windowsIn != NULL) {
|
||||
midiInStop(_windowsIn);
|
||||
midiInClose(_windowsIn);
|
||||
_windowsIn = NULL;
|
||||
}
|
||||
} else if (_windowsOut != NULL) {
|
||||
midiOutClose(_windowsOut);
|
||||
_windowsOut = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool _openPort(bool input, int32_t index) {
|
||||
if (input) {
|
||||
if (midiInOpen(&_windowsIn, (UINT)index, (DWORD_PTR)_windowsRead, 0, CALLBACK_FUNCTION) != MMSYSERR_NOERROR) {
|
||||
_windowsIn = NULL;
|
||||
return false;
|
||||
}
|
||||
midiInStart(_windowsIn);
|
||||
return true;
|
||||
}
|
||||
if (midiOutOpen(&_windowsOut, (UINT)index, 0, 0, CALLBACK_NULL) != MMSYSERR_NOERROR) {
|
||||
_windowsOut = NULL;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// The driver calls _windowsRead on its own thread, so there is nothing to pull.
|
||||
static void _receive(void) {
|
||||
}
|
||||
|
||||
|
||||
static void _scan(void) {
|
||||
MIDIOUTCAPSA outCaps;
|
||||
MIDIINCAPSA inCaps;
|
||||
UINT count;
|
||||
UINT x;
|
||||
|
||||
_inputCount = 0;
|
||||
_outputCount = 0;
|
||||
count = midiInGetNumDevs();
|
||||
for (x = 0; (x < count) && (_inputCount < PORTS_MAX); x++) {
|
||||
if (midiInGetDevCapsA(x, &inCaps, sizeof(inCaps)) == MMSYSERR_NOERROR) {
|
||||
SDL_strlcpy(_inputs[_inputCount].name, inCaps.szPname, PORT_NAME_MAX);
|
||||
SDL_snprintf(_inputs[_inputCount].address, PORT_NAME_MAX, "%u", x);
|
||||
_inputCount++;
|
||||
}
|
||||
}
|
||||
// Device zero is the wave table synthesiser Windows itself provides, and it is a real port.
|
||||
count = midiOutGetNumDevs();
|
||||
for (x = 0; (x < count) && (_outputCount < PORTS_MAX); x++) {
|
||||
if (midiOutGetDevCapsA(x, &outCaps, sizeof(outCaps)) == MMSYSERR_NOERROR) {
|
||||
SDL_strlcpy(_outputs[_outputCount].name, outCaps.szPname, PORT_NAME_MAX);
|
||||
SDL_snprintf(_outputs[_outputCount].address, PORT_NAME_MAX, "%u", x);
|
||||
_outputCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static bool _send(const uint8_t *bytes, size_t size) {
|
||||
MIDIHDR header;
|
||||
DWORD packed = 0;
|
||||
size_t x;
|
||||
|
||||
if (_windowsOut == NULL) {
|
||||
return false;
|
||||
}
|
||||
// Three bytes or fewer go in a word; anything longer is a sysex and needs a buffer the driver
|
||||
// borrows until it says it is finished with it.
|
||||
if (size <= MESSAGE_SHORT) {
|
||||
for (x = 0; x < size; x++) {
|
||||
packed |= (DWORD)bytes[x] << (8 * x);
|
||||
}
|
||||
return (midiOutShortMsg(_windowsOut, packed) == MMSYSERR_NOERROR);
|
||||
}
|
||||
memset(&header, 0, sizeof(header));
|
||||
header.lpData = (LPSTR)bytes;
|
||||
header.dwBufferLength = (DWORD)size;
|
||||
if (midiOutPrepareHeader(_windowsOut, &header, sizeof(header)) != MMSYSERR_NOERROR) {
|
||||
return false;
|
||||
}
|
||||
if (midiOutLongMsg(_windowsOut, &header, sizeof(header)) != MMSYSERR_NOERROR) {
|
||||
midiOutUnprepareHeader(_windowsOut, &header, sizeof(header));
|
||||
return false;
|
||||
}
|
||||
while (midiOutUnprepareHeader(_windowsOut, &header, sizeof(header)) == MIDIERR_STILLPLAYING) {
|
||||
SDL_Delay(1);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// ===== Anywhere else: no ports =====
|
||||
|
||||
#else
|
||||
|
||||
static void _describe(void) {
|
||||
SDL_strlcpy(_description, "no MIDI ports (this platform has no interface)", sizeof(_description));
|
||||
}
|
||||
|
||||
|
||||
static bool _openBackend(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static void _closeBackend(void) {
|
||||
}
|
||||
|
||||
|
||||
static void _closePort(bool input) {
|
||||
(void)input;
|
||||
}
|
||||
|
||||
|
||||
static bool _openPort(bool input, int32_t index) {
|
||||
(void)input;
|
||||
(void)index;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static void _receive(void) {
|
||||
}
|
||||
|
||||
|
||||
static void _scan(void) {
|
||||
_inputCount = 0;
|
||||
_outputCount = 0;
|
||||
}
|
||||
|
||||
|
||||
static bool _send(const uint8_t *bytes, size_t size) {
|
||||
(void)bytes;
|
||||
(void)size;
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
// ===== The part every platform shares =====
|
||||
|
||||
// Queues one received message, dropping it when the queue is full rather than blocking a driver's
|
||||
// thread. Each entry is a length byte and then the message.
|
||||
static void _push(const uint8_t *bytes, size_t size) {
|
||||
size_t x;
|
||||
|
||||
if ((size == 0) || (size > MESSAGE_MAX)) {
|
||||
return;
|
||||
}
|
||||
SDL_LockMutex(_lock);
|
||||
if ((_queueUsed + size + 1) > QUEUE_BYTES) {
|
||||
_queueLost = true;
|
||||
} else {
|
||||
_queue[_queueTail] = (uint8_t)size;
|
||||
_queueTail = (_queueTail + 1) % QUEUE_BYTES;
|
||||
for (x = 0; x < size; x++) {
|
||||
_queue[_queueTail] = bytes[x];
|
||||
_queueTail = (_queueTail + 1) % QUEUE_BYTES;
|
||||
}
|
||||
_queueUsed += size + 1;
|
||||
}
|
||||
SDL_UnlockMutex(_lock);
|
||||
}
|
||||
|
||||
|
||||
// Opens the platform's MIDI system the first time anything asks, and remembers that it tried: a
|
||||
// machine with no MIDI must not pay for the attempt on every call.
|
||||
static bool _start(void) {
|
||||
if (_started) {
|
||||
return _available;
|
||||
}
|
||||
_started = true;
|
||||
_available = _openBackend();
|
||||
if (_available) {
|
||||
_scan();
|
||||
}
|
||||
_describe();
|
||||
utilTrace("MIDI: %s", _description);
|
||||
|
||||
return _available;
|
||||
}
|
||||
|
||||
|
||||
// ===== Public =====
|
||||
|
||||
bool midiIoAvailable(void) {
|
||||
return _start();
|
||||
}
|
||||
|
||||
|
||||
void midiIoCloseInput(void) {
|
||||
if (_inputOpen) {
|
||||
_closePort(true);
|
||||
_inputOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void midiIoCloseOutput(void) {
|
||||
if (_outputOpen) {
|
||||
_closePort(false);
|
||||
_outputOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char *midiIoDescription(void) {
|
||||
return _description;
|
||||
}
|
||||
|
||||
|
||||
void midiIoInit(void) {
|
||||
_lock = SDL_CreateMutex();
|
||||
if (_lock == NULL) {
|
||||
utilDie("Unable to create the MIDI queue lock.");
|
||||
}
|
||||
// Nothing is opened here. A MIDI port costs a file descriptor and a round of driver
|
||||
// configuration, and almost no game wants one, so the platform's MIDI system waits until a
|
||||
// script asks about it. Games that never mention MIDI never pay for it.
|
||||
SDL_strlcpy(_description, "not opened (no game has asked for a port)", sizeof(_description));
|
||||
}
|
||||
|
||||
|
||||
int32_t midiIoInputCount(void) {
|
||||
_start();
|
||||
|
||||
return _inputCount;
|
||||
}
|
||||
|
||||
|
||||
const char *midiIoInputName(int32_t index) {
|
||||
_start();
|
||||
if ((index < 0) || (index >= _inputCount)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return _inputs[index].name;
|
||||
}
|
||||
|
||||
|
||||
bool midiIoInputOpen(void) {
|
||||
return _inputOpen;
|
||||
}
|
||||
|
||||
|
||||
bool midiIoOpenInput(int32_t index) {
|
||||
if (!_start() || (index < 0) || (index >= _inputCount)) {
|
||||
return false;
|
||||
}
|
||||
midiIoCloseInput();
|
||||
_inputOpen = _openPort(true, index);
|
||||
|
||||
return _inputOpen;
|
||||
}
|
||||
|
||||
|
||||
bool midiIoOpenOutput(int32_t index) {
|
||||
if (!_start() || (index < 0) || (index >= _outputCount)) {
|
||||
return false;
|
||||
}
|
||||
midiIoCloseOutput();
|
||||
_outputOpen = _openPort(false, index);
|
||||
|
||||
return _outputOpen;
|
||||
}
|
||||
|
||||
|
||||
int32_t midiIoOutputCount(void) {
|
||||
_start();
|
||||
|
||||
return _outputCount;
|
||||
}
|
||||
|
||||
|
||||
const char *midiIoOutputName(int32_t index) {
|
||||
_start();
|
||||
if ((index < 0) || (index >= _outputCount)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return _outputs[index].name;
|
||||
}
|
||||
|
||||
|
||||
bool midiIoOutputOpen(void) {
|
||||
return _outputOpen;
|
||||
}
|
||||
|
||||
|
||||
void midiIoPoll(MidiIoReceiverT receiver, void *context) {
|
||||
uint8_t message[MESSAGE_MAX];
|
||||
size_t size = 0;
|
||||
size_t x = 0;
|
||||
bool lost = false;
|
||||
|
||||
if (!_available) {
|
||||
return;
|
||||
}
|
||||
// Backends that have to be asked rather than call in.
|
||||
_receive();
|
||||
for (;;) {
|
||||
SDL_LockMutex(_lock);
|
||||
if (_queueUsed == 0) {
|
||||
lost = _queueLost;
|
||||
_queueLost = false;
|
||||
SDL_UnlockMutex(_lock);
|
||||
break;
|
||||
}
|
||||
size = _queue[_queueHead];
|
||||
_queueHead = (_queueHead + 1) % QUEUE_BYTES;
|
||||
for (x = 0; x < size; x++) {
|
||||
message[x] = _queue[_queueHead];
|
||||
_queueHead = (_queueHead + 1) % QUEUE_BYTES;
|
||||
}
|
||||
_queueUsed -= size + 1;
|
||||
SDL_UnlockMutex(_lock);
|
||||
if (receiver != NULL) {
|
||||
receiver(context, message, size);
|
||||
}
|
||||
}
|
||||
if (lost) {
|
||||
utilTrace("MIDI input overflowed; messages were dropped. Is the game reading them?");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void midiIoQuit(void) {
|
||||
midiIoCloseInput();
|
||||
midiIoCloseOutput();
|
||||
if (_available) {
|
||||
_closeBackend();
|
||||
_available = false;
|
||||
}
|
||||
if (_lock != NULL) {
|
||||
SDL_DestroyMutex(_lock);
|
||||
_lock = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void midiIoRescan(void) {
|
||||
if (_start()) {
|
||||
_scan();
|
||||
_describe();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool midiIoSend(const uint8_t *bytes, size_t size) {
|
||||
if (!_outputOpen || (size == 0) || (size > MESSAGE_MAX)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return _send(bytes, size);
|
||||
}
|
||||
87
src/midiIo.h
Normal file
87
src/midiIo.h
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
*
|
||||
* Singe 3
|
||||
* Copyright (C) 2006-2026 Scott Duensing <scott@kangaroopunch.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 3
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
* 02110-1301, USA.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef MIDIIO_H
|
||||
#define MIDIIO_H
|
||||
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
|
||||
// Talking to MIDI ports, which is a different thing from playing a MIDI file: midi.c synthesises a
|
||||
// file into samples, this sends and receives live messages on a real port. A game drives a sound
|
||||
// module with it, or reads a keyboard or a control surface as another input device.
|
||||
|
||||
// What a received message is handed to. Called from midiIoPoll, on the thread that calls it, never
|
||||
// from a driver's own thread.
|
||||
typedef void (*MidiIoReceiverT)(void *context, const uint8_t *bytes, size_t size);
|
||||
|
||||
|
||||
// Prepares the queue the received messages land in. It opens nothing: the platform's MIDI system
|
||||
// is opened, and the ports counted, by whichever of the calls below is asked first.
|
||||
void midiIoInit(void);
|
||||
|
||||
// Whether the platform has a MIDI system at all. This is one of the calls that opens it, so asking
|
||||
// costs the open on a machine that has one.
|
||||
bool midiIoAvailable(void);
|
||||
|
||||
void midiIoCloseInput(void);
|
||||
void midiIoCloseOutput(void);
|
||||
|
||||
// One line for the trace header. Says so when nothing has asked for a port yet, because that is
|
||||
// the answer at start up and a reader should not take it for "this machine has no MIDI". Never
|
||||
// NULL, and never opens anything itself.
|
||||
const char *midiIoDescription(void);
|
||||
|
||||
int32_t midiIoInputCount(void);
|
||||
|
||||
// The name of an input port, or NULL when the index names none.
|
||||
const char *midiIoInputName(int32_t index);
|
||||
|
||||
// Whether an input port is open.
|
||||
bool midiIoInputOpen(void);
|
||||
|
||||
// Opens one port, closing whichever was open. False when the index names none or the port refuses.
|
||||
bool midiIoOpenInput(int32_t index);
|
||||
bool midiIoOpenOutput(int32_t index);
|
||||
|
||||
int32_t midiIoOutputCount(void);
|
||||
const char *midiIoOutputName(int32_t index);
|
||||
bool midiIoOutputOpen(void);
|
||||
|
||||
// Hands every message that arrived since the last call to the receiver. Call it once a frame.
|
||||
void midiIoPoll(MidiIoReceiverT receiver, void *context);
|
||||
|
||||
void midiIoQuit(void);
|
||||
|
||||
// Looks for ports again, so a device plugged in while the game runs can be found. Open ports stay
|
||||
// open and keep their index only if the list did not move underneath them.
|
||||
void midiIoRescan(void);
|
||||
|
||||
// Sends one message. False when no output port is open or the port refused it.
|
||||
bool midiIoSend(const uint8_t *bytes, size_t size);
|
||||
|
||||
|
||||
#endif // MIDIIO_H
|
||||
|
|
@ -35,6 +35,7 @@
|
|||
#include <SDL3_image/SDL_image.h>
|
||||
#define CGLTF_IMPLEMENTATION
|
||||
#include "../thirdparty/cgltf/cgltf.h"
|
||||
#include "decode.h"
|
||||
#include "util.h"
|
||||
#include "vfs.h"
|
||||
#include "scene.h"
|
||||
|
|
@ -505,7 +506,7 @@ static SDL_Surface *_cachedSurface(ImageCacheT *cache, const cgltf_image *image)
|
|||
if (!cached->surfaceTried) {
|
||||
cached->surfaceTried = true;
|
||||
if (_imageBytes(image, &bytes, &size, &decoded)) {
|
||||
cached->surface = IMG_Load_IO(SDL_IOFromConstMem(bytes, size), true);
|
||||
cached->surface = decodeImageIO(SDL_IOFromConstMem(bytes, size), true);
|
||||
if (cached->surface == NULL) {
|
||||
_warn("Unable to decode an embedded image: %s", SDL_GetError());
|
||||
}
|
||||
|
|
|
|||
437
src/singe.c
437
src/singe.c
|
|
@ -58,7 +58,9 @@ int luaopen_lpeg(lua_State *L);
|
|||
// There is no header for ssl.config binding. Make our own.
|
||||
LSEC_API int luaopen_ssl_config(lua_State *L);
|
||||
|
||||
#include "decode.h"
|
||||
#include "main.h"
|
||||
#include "midiIo.h"
|
||||
#include "util.h"
|
||||
#include "frameFile.h"
|
||||
#include "vfs.h"
|
||||
|
|
@ -154,6 +156,20 @@ SDL_COMPILE_TIME_ASSERT(codeGamepadBase, CODE_GAMEPAD_BASE >= SDL_SCANCODE_RESER
|
|||
#define SCORE_CODE_BLANK -3
|
||||
#define SCORE_CODE_H -4
|
||||
#define SCORE_CODE_A -5
|
||||
// The MIDI wire format, for the midi* sending calls. Status bytes carry the channel in their low
|
||||
// four bits; every data byte after one is seven bits.
|
||||
#define MIDI_NOTE_OFF 0x80
|
||||
#define MIDI_NOTE_ON 0x90
|
||||
#define MIDI_CONTROL_CHANGE 0xB0
|
||||
#define MIDI_PROGRAM_CHANGE 0xC0
|
||||
#define MIDI_PITCH_BEND 0xE0
|
||||
#define MIDI_CHANNEL_MAX 15
|
||||
#define MIDI_DATA_MAX 127
|
||||
#define MIDI_DATA_BITS 7
|
||||
#define MIDI_BEND_MAX 16383 // Two seven bit halves, 8192 being no bend at all
|
||||
#define MIDI_BYTE_MAX 255
|
||||
#define MIDI_MESSAGE_BYTES 3 // The longest message the numeric form of midiSend takes
|
||||
#define SPRITE_VECTOR_MAX 8192 // Pixels a side an SVG may be asked for
|
||||
#define SUBTITLE_RML "Singe/subtitle.rml"
|
||||
#define SUBTITLE_SLOT "slot" // The element the engine writes a cue or a banner into
|
||||
#define SUBTITLE_WIDTH 960 // The subtitle document's own pixels; the draw scales it over the picture
|
||||
|
|
@ -779,6 +795,9 @@ static int32_t _materialSetMap(lua_State *L, const char *method, MaterialMa
|
|||
static float _mixerGain(int32_t volume, int32_t maximum);
|
||||
static int32_t _mouseCode(int32_t device, int32_t button);
|
||||
static void _musicDestroy(MusicT *music);
|
||||
static uint8_t _midiChannel(lua_State *L, const char *method, int32_t index);
|
||||
static uint8_t _midiData(lua_State *L, const char *method, int32_t index);
|
||||
static void _midiReceived(void *context, const uint8_t *bytes, size_t size);
|
||||
static void _navCallbacks(void);
|
||||
static void _noteInput(void);
|
||||
static void _overlayApplyOpacity(void);
|
||||
|
|
@ -835,7 +854,7 @@ static int32_t _soundQueueDrain(int32_t *finished);
|
|||
static void _spriteAdoptAnimation(SpriteT *sprite);
|
||||
static void _spriteDestroy(SpriteT *sprite);
|
||||
static void _spriteFreeSurface(SpriteT *sprite);
|
||||
static SpriteT *_spriteLoadFromIO(lua_State *L, const char *method, SDL_IOStream *io);
|
||||
static SpriteT *_spriteLoadFromIO(lua_State *L, const char *method, SDL_IOStream *io, int32_t width, int32_t height);
|
||||
static SpriteT *_spriteNew(lua_State *L, const char *method);
|
||||
static void _spriteRebuildSurface(SpriteT *sprite);
|
||||
static void _spriteRegister(SpriteT *sprite);
|
||||
|
|
@ -916,6 +935,8 @@ static int32_t apiDiscAudioSuffix(lua_State *L);
|
|||
static int32_t apiDiscChangeSpeed(lua_State *L);
|
||||
static int32_t apiDiscGetAudioTrack(lua_State *L);
|
||||
static int32_t apiDiscGetAudioTracks(lua_State *L);
|
||||
static int32_t apiDiscGetSubtitleLanguage(lua_State *L);
|
||||
static int32_t apiDiscGetSubtitleTracks(lua_State *L);
|
||||
static int32_t apiDiscGetFrame(lua_State *L);
|
||||
static int32_t apiDiscGetHeight(lua_State *L);
|
||||
static int32_t apiDiscGetLanguage(lua_State *L);
|
||||
|
|
@ -1041,6 +1062,23 @@ static int32_t apiMeshNew(lua_State *L);
|
|||
static int32_t apiMeshPlane(lua_State *L);
|
||||
static int32_t apiMeshSphere(lua_State *L);
|
||||
static int32_t apiMeshTorus(lua_State *L);
|
||||
static int32_t apiMidiCloseInput(lua_State *L);
|
||||
static int32_t apiMidiCloseOutput(lua_State *L);
|
||||
static int32_t apiMidiControlChange(lua_State *L);
|
||||
static int32_t apiMidiInputCount(lua_State *L);
|
||||
static int32_t apiMidiInputName(lua_State *L);
|
||||
static int32_t apiMidiIsInputOpen(lua_State *L);
|
||||
static int32_t apiMidiIsOutputOpen(lua_State *L);
|
||||
static int32_t apiMidiNoteOff(lua_State *L);
|
||||
static int32_t apiMidiNoteOn(lua_State *L);
|
||||
static int32_t apiMidiOpenInput(lua_State *L);
|
||||
static int32_t apiMidiOpenOutput(lua_State *L);
|
||||
static int32_t apiMidiOutputCount(lua_State *L);
|
||||
static int32_t apiMidiOutputName(lua_State *L);
|
||||
static int32_t apiMidiPitchBend(lua_State *L);
|
||||
static int32_t apiMidiProgramChange(lua_State *L);
|
||||
static int32_t apiMidiRescan(lua_State *L);
|
||||
static int32_t apiMidiSend(lua_State *L);
|
||||
static int32_t apiModelDelete(lua_State *L);
|
||||
static int32_t apiModelGetAnimations(lua_State *L);
|
||||
static int32_t apiModelInstance(lua_State *L);
|
||||
|
|
@ -1261,6 +1299,7 @@ static int32_t apiSpriteUnload(lua_State *L);
|
|||
static int32_t apiSrtClear(lua_State *L);
|
||||
static int32_t apiSrtEnable(lua_State *L);
|
||||
static int32_t apiSrtLoad(lua_State *L);
|
||||
static int32_t apiSrtLoadTrack(lua_State *L);
|
||||
static int32_t apiSrtPosition(lua_State *L);
|
||||
static int32_t apiTerrainGetHeight(lua_State *L);
|
||||
static int32_t apiVehicleAddWheel(lua_State *L);
|
||||
|
|
@ -1845,7 +1884,7 @@ static void _bezelLoad(void) {
|
|||
if (data == NULL) {
|
||||
utilDie("Unable to read the bezel %s.", candidate[found]);
|
||||
}
|
||||
artwork = IMG_Load_IO(SDL_IOFromConstMem(data, bytes), true);
|
||||
artwork = decodeImageIO(SDL_IOFromConstMem(data, bytes), true);
|
||||
if (artwork == NULL) {
|
||||
utilDie("%s: %s", candidate[found], SDL_GetError());
|
||||
}
|
||||
|
|
@ -2126,6 +2165,13 @@ static void _callLua(const char *func, const char *sig, ...) {
|
|||
lua_pushstring(_global.luaContext, va_arg(vl, char *));
|
||||
break;
|
||||
|
||||
case 'S': { // Counted string, for bytes that may hold a zero
|
||||
const char *bytes = va_arg(vl, const char *);
|
||||
|
||||
lua_pushlstring(_global.luaContext, bytes, va_arg(vl, size_t));
|
||||
break;
|
||||
}
|
||||
|
||||
case '>':
|
||||
done = true;
|
||||
break;
|
||||
|
|
@ -4923,6 +4969,8 @@ static void _registerApi(lua_State *L) {
|
|||
lua_register(L, "discChangeSpeed", apiDiscChangeSpeed); // 1.xx
|
||||
lua_register(L, "discGetAudioTrack", apiDiscGetAudioTrack); // 2.10
|
||||
lua_register(L, "discGetAudioTracks", apiDiscGetAudioTracks); // 2.10
|
||||
lua_register(L, "discGetSubtitleLanguage", apiDiscGetSubtitleLanguage); // 3.00
|
||||
lua_register(L, "discGetSubtitleTracks", apiDiscGetSubtitleTracks); // 3.00
|
||||
lua_register(L, "discGetFrame", apiDiscGetFrame); // 1.xx
|
||||
lua_register(L, "discGetHeight", apiDiscGetHeight); // 2.00
|
||||
lua_register(L, "discGetLanguage", apiDiscGetLanguage); // 2.10
|
||||
|
|
@ -5051,6 +5099,23 @@ static void _registerApi(lua_State *L) {
|
|||
lua_register(L, "meshPlane", apiMeshPlane); // 3.00
|
||||
lua_register(L, "meshSphere", apiMeshSphere); // 3.00
|
||||
lua_register(L, "meshTorus", apiMeshTorus); // 3.00
|
||||
lua_register(L, "midiCloseInput", apiMidiCloseInput); // 3.00
|
||||
lua_register(L, "midiCloseOutput", apiMidiCloseOutput); // 3.00
|
||||
lua_register(L, "midiControlChange", apiMidiControlChange); // 3.00
|
||||
lua_register(L, "midiInputCount", apiMidiInputCount); // 3.00
|
||||
lua_register(L, "midiInputName", apiMidiInputName); // 3.00
|
||||
lua_register(L, "midiIsInputOpen", apiMidiIsInputOpen); // 3.00
|
||||
lua_register(L, "midiIsOutputOpen", apiMidiIsOutputOpen); // 3.00
|
||||
lua_register(L, "midiNoteOff", apiMidiNoteOff); // 3.00
|
||||
lua_register(L, "midiNoteOn", apiMidiNoteOn); // 3.00
|
||||
lua_register(L, "midiOpenInput", apiMidiOpenInput); // 3.00
|
||||
lua_register(L, "midiOpenOutput", apiMidiOpenOutput); // 3.00
|
||||
lua_register(L, "midiOutputCount", apiMidiOutputCount); // 3.00
|
||||
lua_register(L, "midiOutputName", apiMidiOutputName); // 3.00
|
||||
lua_register(L, "midiPitchBend", apiMidiPitchBend); // 3.00
|
||||
lua_register(L, "midiProgramChange", apiMidiProgramChange); // 3.00
|
||||
lua_register(L, "midiRescan", apiMidiRescan); // 3.00
|
||||
lua_register(L, "midiSend", apiMidiSend); // 3.00
|
||||
lua_register(L, "modelDelete", apiModelDelete); // 3.00
|
||||
lua_register(L, "modelGetAnimations", apiModelGetAnimations); // 3.00
|
||||
lua_register(L, "modelInstance", apiModelInstance); // 3.00
|
||||
|
|
@ -5277,6 +5342,7 @@ static void _registerApi(lua_State *L) {
|
|||
lua_register(L, "srtClear", apiSrtClear); // Hypseus
|
||||
lua_register(L, "srtEnable", apiSrtEnable); // Hypseus
|
||||
lua_register(L, "srtLoad", apiSrtLoad); // Hypseus
|
||||
lua_register(L, "srtLoadTrack", apiSrtLoadTrack); // 3.00
|
||||
lua_register(L, "srtPosition", apiSrtPosition); // Hypseus
|
||||
|
||||
lua_register(L, "terrainGetHeight", apiTerrainGetHeight); // 3.00
|
||||
|
|
@ -6020,7 +6086,7 @@ static SoundT *_soundLoadFromIO(lua_State *L, const char *method, SDL_IOStream *
|
|||
SDL_CloseIO(io);
|
||||
_luaDie(L, method, "Unable to allocate new sound.");
|
||||
}
|
||||
sound->audio = MIX_LoadAudio_IO(videoGetMixer(), io, true, true);
|
||||
sound->audio = decodeAudioIO(videoGetMixer(), io, true);
|
||||
if (!sound->audio) {
|
||||
free(sound);
|
||||
_luaDie(L, method, "%s", SDL_GetError());
|
||||
|
|
@ -6087,9 +6153,22 @@ static void _spriteFreeSurface(SpriteT *sprite) {
|
|||
// The sprite both spriteLoad and spriteLoadData end up making, from a stream either of them opened.
|
||||
// An animated GIF or WEBP becomes an animation; anything else becomes a still. The stream is closed
|
||||
// whatever happens.
|
||||
static SpriteT *_spriteLoadFromIO(lua_State *L, const char *method, SDL_IOStream *io) {
|
||||
static SpriteT *_spriteLoadFromIO(lua_State *L, const char *method, SDL_IOStream *io, int32_t width, int32_t height) {
|
||||
SpriteT *sprite = _spriteNew(L, method);
|
||||
|
||||
// A wanted size only means anything to a vector picture, and only spriteLoad offers one.
|
||||
if ((width > 0) && (height > 0)) {
|
||||
sprite->originalSurface = IMG_LoadSizedSVG_IO(io, width, height);
|
||||
if (sprite->originalSurface == NULL) {
|
||||
SDL_SeekIO(io, 0, SDL_IO_SEEK_SET);
|
||||
} else {
|
||||
_surfaceUnpack(&sprite->originalSurface);
|
||||
SDL_CloseIO(io);
|
||||
SDL_SetSurfaceColorKey(sprite->originalSurface, true, COLOR_KEY_VALUE);
|
||||
_spriteRegister(sprite);
|
||||
return sprite;
|
||||
}
|
||||
}
|
||||
sprite->animation = IMG_LoadAnimation_IO(io, false);
|
||||
if ((sprite->animation != NULL) && (sprite->animation->count < 2)) {
|
||||
// Only one frame: take it over as the still image (no copy, so packed 1-bit PNGs survive).
|
||||
|
|
@ -6103,7 +6182,7 @@ static SpriteT *_spriteLoadFromIO(lua_State *L, const char *method, SDL_IOStream
|
|||
_spriteAdoptAnimation(sprite);
|
||||
} else {
|
||||
SDL_SeekIO(io, 0, SDL_IO_SEEK_SET);
|
||||
sprite->originalSurface = IMG_Load_IO(io, false);
|
||||
sprite->originalSurface = decodeImageIO(io, false);
|
||||
_surfaceUnpack(&sprite->originalSurface);
|
||||
}
|
||||
}
|
||||
|
|
@ -6121,6 +6200,39 @@ static SpriteT *_spriteLoadFromIO(lua_State *L, const char *method, SDL_IOStream
|
|||
}
|
||||
|
||||
|
||||
// A MIDI channel argument, which a person counts from 1 and the wire counts from 0.
|
||||
static uint8_t _midiChannel(lua_State *L, const char *method, int32_t index) {
|
||||
int32_t channel = _argInteger(L, method, index) - 1;
|
||||
|
||||
if ((channel < 0) || (channel > MIDI_CHANNEL_MAX)) {
|
||||
_luaDie(L, method, "A MIDI channel is 1 to %d, not %d.", MIDI_CHANNEL_MAX + 1, channel + 1);
|
||||
}
|
||||
|
||||
return (uint8_t)channel;
|
||||
}
|
||||
|
||||
|
||||
// A seven bit MIDI data argument: a key, a velocity, a controller or its value.
|
||||
static uint8_t _midiData(lua_State *L, const char *method, int32_t index) {
|
||||
int32_t value = _argInteger(L, method, index);
|
||||
|
||||
if ((value < 0) || (value > MIDI_DATA_MAX)) {
|
||||
_luaDie(L, method, "A MIDI value is 0 to %d, not %d.", MIDI_DATA_MAX, value);
|
||||
}
|
||||
|
||||
return (uint8_t)value;
|
||||
}
|
||||
|
||||
|
||||
// onMidiMessage(status, data1, data2, bytes) for one message that arrived on the input port. The
|
||||
// three numbers are the usual message; the string is the whole thing, which is what a system
|
||||
// exclusive message needs.
|
||||
static void _midiReceived(void *context, const uint8_t *bytes, size_t size) {
|
||||
(void)context;
|
||||
_callLua("onMidiMessage", "iiiS", (int32_t)bytes[0], (size > 1) ? (int32_t)bytes[1] : 0, (size > 2) ? (int32_t)bytes[2] : 0, (const char *)bytes, size);
|
||||
}
|
||||
|
||||
|
||||
// An empty sprite record for a loader to fill.
|
||||
static SpriteT *_spriteNew(lua_State *L, const char *method) {
|
||||
SpriteT *sprite = (SpriteT *)calloc(1, sizeof(SpriteT));
|
||||
|
|
@ -7678,6 +7790,36 @@ static int32_t apiDiscGetAudioTracks(lua_State *L) {
|
|||
}
|
||||
|
||||
|
||||
// language = discGetSubtitleLanguage(track) The language a subtitle track inside the disc's own
|
||||
// container is labelled with, or "" when it is labelled with nothing.
|
||||
static int32_t apiDiscGetSubtitleLanguage(lua_State *L) {
|
||||
const char *language = "";
|
||||
|
||||
_argCheck(L, "discGetSubtitleLanguage", 1, 1);
|
||||
if (_global.videoHandle >= 0) {
|
||||
language = videoGetSubtitleLanguage(_global.videoHandle, _argInteger(L, "discGetSubtitleLanguage", 1));
|
||||
}
|
||||
lua_pushstring(L, language);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// count = discGetSubtitleTracks() How many subtitle tracks the disc's own container holds.
|
||||
static int32_t apiDiscGetSubtitleTracks(lua_State *L) {
|
||||
int32_t count = 0;
|
||||
|
||||
_argCheck(L, "discGetSubtitleTracks", 0, 0);
|
||||
if (_global.videoHandle >= 0) {
|
||||
count = videoGetSubtitleTracks(_global.videoHandle);
|
||||
}
|
||||
_luaTrace(L, "discGetSubtitleTracks", "%d", count);
|
||||
lua_pushinteger(L, count);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// frame = discGetFrame()
|
||||
static int32_t apiDiscGetFrame(lua_State *L) {
|
||||
int64_t frame = 0;
|
||||
|
|
@ -9227,7 +9369,7 @@ static int32_t apiMeshHeightmap(lua_State *L) {
|
|||
if (io == NULL) {
|
||||
_luaDie(L, "meshHeightmap", "Unable to open %s", name);
|
||||
}
|
||||
image = IMG_Load_IO(io, true);
|
||||
image = decodeImageIO(io, true);
|
||||
if (image == NULL) {
|
||||
_luaDie(L, "meshHeightmap", "%s: %s", name, SDL_GetError());
|
||||
}
|
||||
|
|
@ -9385,6 +9527,231 @@ static int32_t apiMeshTorus(lua_State *L) {
|
|||
|
||||
|
||||
// modelDelete(model): frees its meshes and materials; instances keep their nodes, bare
|
||||
// midiCloseInput() Stops listening to the MIDI input port.
|
||||
static int32_t apiMidiCloseInput(lua_State *L) {
|
||||
_argCheck(L, "midiCloseInput", 0, 0);
|
||||
midiIoCloseInput();
|
||||
_luaTrace(L, "midiCloseInput", "Closed.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// midiCloseOutput() Releases the MIDI output port.
|
||||
static int32_t apiMidiCloseOutput(lua_State *L) {
|
||||
_argCheck(L, "midiCloseOutput", 0, 0);
|
||||
midiIoCloseOutput();
|
||||
_luaTrace(L, "midiCloseOutput", "Closed.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// sent = midiControlChange(channel, control, value) Moves a controller on the open output port.
|
||||
static int32_t apiMidiControlChange(lua_State *L) {
|
||||
uint8_t message[3];
|
||||
|
||||
_argCheck(L, "midiControlChange", 3, 3);
|
||||
message[0] = (uint8_t)(MIDI_CONTROL_CHANGE | _midiChannel(L, "midiControlChange", 1));
|
||||
message[1] = _midiData(L, "midiControlChange", 2);
|
||||
message[2] = _midiData(L, "midiControlChange", 3);
|
||||
lua_pushboolean(L, midiIoSend(message, sizeof(message)));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// count = midiInputCount() How many MIDI input ports this machine has.
|
||||
static int32_t apiMidiInputCount(lua_State *L) {
|
||||
_argCheck(L, "midiInputCount", 0, 0);
|
||||
lua_pushinteger(L, midiIoInputCount());
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// name = midiInputName(index) The name of an input port, or nil when the index names none.
|
||||
static int32_t apiMidiInputName(lua_State *L) {
|
||||
const char *name = NULL;
|
||||
|
||||
_argCheck(L, "midiInputName", 1, 1);
|
||||
name = midiIoInputName(_argInteger(L, "midiInputName", 1));
|
||||
if (name == NULL) {
|
||||
lua_pushnil(L);
|
||||
} else {
|
||||
lua_pushstring(L, name);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// open = midiIsInputOpen() Whether a MIDI input port is open.
|
||||
static int32_t apiMidiIsInputOpen(lua_State *L) {
|
||||
_argCheck(L, "midiIsInputOpen", 0, 0);
|
||||
lua_pushboolean(L, midiIoInputOpen());
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// open = midiIsOutputOpen() Whether a MIDI output port is open.
|
||||
static int32_t apiMidiIsOutputOpen(lua_State *L) {
|
||||
_argCheck(L, "midiIsOutputOpen", 0, 0);
|
||||
lua_pushboolean(L, midiIoOutputOpen());
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// sent = midiNoteOff(channel, key) Releases a note on the open output port.
|
||||
static int32_t apiMidiNoteOff(lua_State *L) {
|
||||
uint8_t message[3];
|
||||
|
||||
_argCheck(L, "midiNoteOff", 2, 2);
|
||||
message[0] = (uint8_t)(MIDI_NOTE_OFF | _midiChannel(L, "midiNoteOff", 1));
|
||||
message[1] = _midiData(L, "midiNoteOff", 2);
|
||||
message[2] = 0;
|
||||
lua_pushboolean(L, midiIoSend(message, sizeof(message)));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// sent = midiNoteOn(channel, key, velocity) Sounds a note on the open output port.
|
||||
static int32_t apiMidiNoteOn(lua_State *L) {
|
||||
uint8_t message[3];
|
||||
|
||||
_argCheck(L, "midiNoteOn", 3, 3);
|
||||
message[0] = (uint8_t)(MIDI_NOTE_ON | _midiChannel(L, "midiNoteOn", 1));
|
||||
message[1] = _midiData(L, "midiNoteOn", 2);
|
||||
message[2] = _midiData(L, "midiNoteOn", 3);
|
||||
lua_pushboolean(L, midiIoSend(message, sizeof(message)));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// opened = midiOpenInput(index) Listens to one input port; onMidiMessage then fires for what arrives.
|
||||
static int32_t apiMidiOpenInput(lua_State *L) {
|
||||
bool opened = false;
|
||||
|
||||
_argCheck(L, "midiOpenInput", 1, 1);
|
||||
opened = midiIoOpenInput(_argInteger(L, "midiOpenInput", 1));
|
||||
_luaTrace(L, "midiOpenInput", "%s", opened ? "opened" : "failed");
|
||||
lua_pushboolean(L, opened);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// opened = midiOpenOutput(index) Takes one output port for the midi* sending calls.
|
||||
static int32_t apiMidiOpenOutput(lua_State *L) {
|
||||
bool opened = false;
|
||||
|
||||
_argCheck(L, "midiOpenOutput", 1, 1);
|
||||
opened = midiIoOpenOutput(_argInteger(L, "midiOpenOutput", 1));
|
||||
_luaTrace(L, "midiOpenOutput", "%s", opened ? "opened" : "failed");
|
||||
lua_pushboolean(L, opened);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// count = midiOutputCount() How many MIDI output ports this machine has.
|
||||
static int32_t apiMidiOutputCount(lua_State *L) {
|
||||
_argCheck(L, "midiOutputCount", 0, 0);
|
||||
lua_pushinteger(L, midiIoOutputCount());
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// name = midiOutputName(index) The name of an output port, or nil when the index names none.
|
||||
static int32_t apiMidiOutputName(lua_State *L) {
|
||||
const char *name = NULL;
|
||||
|
||||
_argCheck(L, "midiOutputName", 1, 1);
|
||||
name = midiIoOutputName(_argInteger(L, "midiOutputName", 1));
|
||||
if (name == NULL) {
|
||||
lua_pushnil(L);
|
||||
} else {
|
||||
lua_pushstring(L, name);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// sent = midiPitchBend(channel, value) Bends a channel, 0 to 16383 with 8192 in the middle.
|
||||
static int32_t apiMidiPitchBend(lua_State *L) {
|
||||
uint8_t message[3];
|
||||
int32_t value = 0;
|
||||
|
||||
_argCheck(L, "midiPitchBend", 2, 2);
|
||||
value = _argInteger(L, "midiPitchBend", 2);
|
||||
if ((value < 0) || (value > MIDI_BEND_MAX)) {
|
||||
_luaDie(L, "midiPitchBend", "A pitch bend is 0 to %d, not %d.", MIDI_BEND_MAX, value);
|
||||
}
|
||||
message[0] = (uint8_t)(MIDI_PITCH_BEND | _midiChannel(L, "midiPitchBend", 1));
|
||||
message[1] = (uint8_t)(value & MIDI_DATA_MAX);
|
||||
message[2] = (uint8_t)((value >> MIDI_DATA_BITS) & MIDI_DATA_MAX);
|
||||
lua_pushboolean(L, midiIoSend(message, sizeof(message)));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// sent = midiProgramChange(channel, program) Chooses the sound a channel plays.
|
||||
static int32_t apiMidiProgramChange(lua_State *L) {
|
||||
uint8_t message[2];
|
||||
|
||||
_argCheck(L, "midiProgramChange", 2, 2);
|
||||
message[0] = (uint8_t)(MIDI_PROGRAM_CHANGE | _midiChannel(L, "midiProgramChange", 1));
|
||||
message[1] = _midiData(L, "midiProgramChange", 2);
|
||||
lua_pushboolean(L, midiIoSend(message, sizeof(message)));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// midiRescan() Looks for ports again, for a device plugged in while the game is running.
|
||||
static int32_t apiMidiRescan(lua_State *L) {
|
||||
_argCheck(L, "midiRescan", 0, 0);
|
||||
midiIoRescan();
|
||||
_luaTrace(L, "midiRescan", "%d in, %d out", midiIoInputCount(), midiIoOutputCount());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// sent = midiSend(byte [, byte [, byte]]) or midiSend(string)
|
||||
// Any message at all, for the ones the named calls do not cover: a string carries a whole system
|
||||
// exclusive message, and the numbers carry an ordinary one.
|
||||
static int32_t apiMidiSend(lua_State *L) {
|
||||
uint8_t message[MIDI_MESSAGE_BYTES];
|
||||
const char *bytes = NULL;
|
||||
size_t size = 0;
|
||||
int32_t count = 0;
|
||||
int32_t x = 0;
|
||||
|
||||
_argCheck(L, "midiSend", 1, MIDI_MESSAGE_BYTES);
|
||||
if (lua_type(L, 1) == LUA_TSTRING) {
|
||||
bytes = lua_tolstring(L, 1, &size);
|
||||
lua_pushboolean(L, midiIoSend((const uint8_t *)bytes, size));
|
||||
|
||||
return 1;
|
||||
}
|
||||
count = lua_gettop(L);
|
||||
for (x = 0; x < count; x++) {
|
||||
message[x] = (uint8_t)(_argInteger(L, "midiSend", x + 1) & MIDI_BYTE_MAX);
|
||||
}
|
||||
lua_pushboolean(L, midiIoSend(message, (size_t)count));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int32_t apiModelDelete(lua_State *L) {
|
||||
int32_t model;
|
||||
|
||||
|
|
@ -9498,7 +9865,7 @@ static int32_t apiMusicLoad(lua_State *L) {
|
|||
SDL_CloseIO(io);
|
||||
_luaDie(L, "musicLoad", "Unable to allocate new music.");
|
||||
}
|
||||
music->audio = MIX_LoadAudio_IO(videoGetMixer(), io, true, true);
|
||||
music->audio = decodeAudioIO(videoGetMixer(), io, true);
|
||||
if (!music->audio) {
|
||||
free(music);
|
||||
_luaDie(L, "musicLoad", "%s", SDL_GetError());
|
||||
|
|
@ -12413,18 +12780,32 @@ static int32_t apiSpriteIsPlaying(lua_State *L) {
|
|||
}
|
||||
|
||||
|
||||
// spriteLoad(name [, width, height])
|
||||
// The size is for a vector picture, an SVG, which has no size of its own worth having: it is
|
||||
// rasterised to fit that box, keeping its own proportions, rather than drawn small and scaled up.
|
||||
// Ask spriteGetWidth and spriteGetHeight what it came out as. Other formats ignore the size.
|
||||
static int32_t apiSpriteLoad(lua_State *L) {
|
||||
const char *name = NULL;
|
||||
SpriteT *sprite = NULL;
|
||||
SDL_IOStream *io = NULL;
|
||||
int32_t width = 0;
|
||||
int32_t height = 0;
|
||||
|
||||
_argCheck(L, "spriteLoad", 1, 1);
|
||||
_argCheck(L, "spriteLoad", 1, 3);
|
||||
name = _argString(L, "spriteLoad", 1);
|
||||
io = vfsOpenIO(name);
|
||||
if (lua_gettop(L) > 1) {
|
||||
_argCheck(L, "spriteLoad", 3, 3);
|
||||
width = _argInteger(L, "spriteLoad", 2);
|
||||
height = _argInteger(L, "spriteLoad", 3);
|
||||
if ((width < 1) || (height < 1) || (width > SPRITE_VECTOR_MAX) || (height > SPRITE_VECTOR_MAX)) {
|
||||
_luaDie(L, "spriteLoad", "A vector size is 1 to %d a side, not %dx%d.", SPRITE_VECTOR_MAX, width, height);
|
||||
}
|
||||
}
|
||||
io = vfsOpenIO(name);
|
||||
if (io == NULL) {
|
||||
_luaDie(L, "spriteLoad", "%s", SDL_GetError());
|
||||
}
|
||||
sprite = _spriteLoadFromIO(L, "spriteLoad", io);
|
||||
sprite = _spriteLoadFromIO(L, "spriteLoad", io, width, height);
|
||||
_luaTrace(L, "spriteLoad", "%d %s", sprite->id, name);
|
||||
lua_pushinteger(L, sprite->id);
|
||||
|
||||
|
|
@ -12442,7 +12823,7 @@ static int32_t apiSpriteLoadData(lua_State *L) {
|
|||
|
||||
_argCheck(L, "spriteLoadData", 1, 1);
|
||||
data = _argData(L, "spriteLoadData", 1, &length);
|
||||
sprite = _spriteLoadFromIO(L, "spriteLoadData", SDL_IOFromConstMem(data, length));
|
||||
sprite = _spriteLoadFromIO(L, "spriteLoadData", SDL_IOFromConstMem(data, length), 0, 0);
|
||||
_luaTrace(L, "spriteLoadData", "%d %zu", sprite->id, length);
|
||||
lua_pushinteger(L, sprite->id);
|
||||
|
||||
|
|
@ -12471,7 +12852,7 @@ static int32_t apiSpriteLoadFrames(lua_State *L) {
|
|||
if (io == NULL) {
|
||||
_luaDie(L, "spriteLoadFrames", "%s", SDL_GetError());
|
||||
}
|
||||
sheet = IMG_Load_IO(io, true);
|
||||
sheet = decodeImageIO(io, true);
|
||||
if (sheet == NULL) {
|
||||
_luaDie(L, "spriteLoadFrames", "%s", SDL_GetError());
|
||||
}
|
||||
|
|
@ -12783,6 +13164,34 @@ static int32_t apiSrtLoad(lua_State *L) {
|
|||
}
|
||||
|
||||
|
||||
// loaded = srtLoadTrack(track) The same as srtLoad, but the subtitles come from inside the disc's
|
||||
// own container instead of from a .srt beside it.
|
||||
static int32_t apiSrtLoadTrack(lua_State *L) {
|
||||
char *text = NULL;
|
||||
int32_t track = 0;
|
||||
bool loaded = false;
|
||||
|
||||
_argCheck(L, "srtLoadTrack", 1, 1);
|
||||
track = _argInteger(L, "srtLoadTrack", 1);
|
||||
if (_global.videoHandle >= 0) {
|
||||
text = videoReadSubtitles(_global.videoHandle, track);
|
||||
if (text != NULL) {
|
||||
loaded = _subtitleParse(text);
|
||||
free(text);
|
||||
}
|
||||
}
|
||||
if (!loaded) {
|
||||
_subtitleClearCues();
|
||||
_subtitleShow(NULL, 0);
|
||||
utilSay("Warning: No subtitles were loaded from track %d of the disc.", track);
|
||||
}
|
||||
_luaTrace(L, "srtLoadTrack", "%d %d %d", track, loaded, _global.subtitleCueCount);
|
||||
lua_pushboolean(L, loaded);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
// srtPosition(percent) Hypseus extension. Where a subtitle sits, as a per cent down the picture,
|
||||
// 1 to 95. Anything outside that is ignored, as Hypseus ignores it.
|
||||
static int32_t apiSrtPosition(lua_State *L) {
|
||||
|
|
@ -14500,6 +14909,10 @@ void singe(SDL_Window *window, SDL_Renderer *renderer, SDL_GPUDevice *device, Co
|
|||
// --joymouse: the stick has had a frame to move the cursor in.
|
||||
_joyMouseUpdate();
|
||||
|
||||
// Whatever arrived on the MIDI input port. This belongs with the other input, not with the
|
||||
// drawing: a game that never redraws still has to be given its messages.
|
||||
midiIoPoll(_midiReceived, NULL);
|
||||
|
||||
// --idleexit: nothing has been touched for that long, so an attract cabinet lets go.
|
||||
if ((_global.conf->idleExitSeconds > 0) && ((utilTicks() - _global.idleClock) >= ((uint64_t)_global.conf->idleExitSeconds * MS_PER_SECOND))) {
|
||||
_progTrace("Idle for %d seconds; quitting", _global.conf->idleExitSeconds);
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
|
||||
#include "common.h"
|
||||
#include "generated/version.h"
|
||||
#include "videoPlayer.h"
|
||||
|
||||
|
||||
#define SINDEN_ARG_MAX 8
|
||||
|
|
@ -87,6 +88,8 @@ typedef struct ConfigS {
|
|||
char *dataDirBase;
|
||||
char *keymapFile; // --keymapfile: the controls.cfg to read in place of the four place search
|
||||
char *audioSuffix; // --altaudio: the disc audio suffix discAudioSuffix takes, applied at startup
|
||||
char *soundfont; // --soundfont: the .sf2 MIDI files are synthesised with, in place of the search
|
||||
DeinterlaceE deinterlace; // --deinterlace: what an interlaced picture gets, automatic by default
|
||||
char *gamepadOrder; // --gamepad_reorder: enumeration positions, one per gamepad slot, as Hypseus writes them
|
||||
bool resolutionWasCalculated;
|
||||
bool isFrameFile;
|
||||
|
|
|
|||
|
|
@ -40,6 +40,9 @@
|
|||
#include <SDL3/SDL.h>
|
||||
#include <SDL3_mixer/SDL_mixer.h>
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavfilter/avfilter.h>
|
||||
#include <libavfilter/buffersink.h>
|
||||
#include <libavfilter/buffersrc.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/channel_layout.h>
|
||||
#include <libavutil/hwcontext.h>
|
||||
|
|
@ -70,6 +73,14 @@ typedef struct iso639_lang_t iso639_lang_t;
|
|||
#define AUDIO_MEASURE_MAX_BUFFERS 64
|
||||
#define AUDIO_MEASURE_TIMEOUT_MS 2000
|
||||
#define AUDIO_STREAM_LOW_WATERMARK (24 * 1024) // Bytes queued for the mixer before we stop decoding ahead
|
||||
#define FILTER_ARGUMENTS_MAX 256 // Longest "video_size=..." the buffer filter is given
|
||||
#define LANGUAGE_CODE_BYTES 8 // An ISO 639 code and room for the odd long one
|
||||
#define SRT_TIME_MAX 16 // "01:23:45,678" and its terminator
|
||||
#define SRT_CUE_MAX 32 // Longest cue number and the lines around it
|
||||
#define SRT_ASS_FIELDS 8 // Commas before the text in FFmpeg's ASS dialogue form
|
||||
#define SRT_BYTES_MAX (16 * 1024 * 1024)
|
||||
#define MS_PER_HOUR 3600000
|
||||
#define MS_PER_MINUTE 60000
|
||||
#define AVIO_BUFFER_BYTES (64 * 1024) // libavformat's read buffer over a vfs stream
|
||||
#define BYTES_PER_PIXEL 4
|
||||
#define BYTES_PER_SAMPLE 4 // Float samples
|
||||
|
|
@ -191,6 +202,16 @@ typedef struct VideoPlayerS {
|
|||
AVPacket *videoPacket;
|
||||
AVFrame *videoFrame;
|
||||
struct SwsContext *sws;
|
||||
// Deinterlacing, built the first time an interlaced frame turns up and left in place after.
|
||||
AVFilterGraph *filterGraph;
|
||||
AVFilterContext *filterSource;
|
||||
AVFilterContext *filterSink;
|
||||
AVFrame *filterFrame;
|
||||
enum AVPixelFormat filterFormat; // What the graph was built for
|
||||
int32_t filterWidth;
|
||||
int32_t filterHeight;
|
||||
bool filterReported;
|
||||
DeinterlaceE deinterlace;
|
||||
AVBufferRef *hwDevice; // Hardware decoder context, NULL when decoding in software
|
||||
AVFrame *hwFrame; // Decoded hardware frame transferred to system memory
|
||||
enum AVPixelFormat hwPixelFormat; // What the hardware decoder hands back
|
||||
|
|
@ -266,6 +287,12 @@ static int64_t _avioSeek(void *opaque, int64_t offset, int whence);
|
|||
static void _buildFrameTable(VideoPlayerT *v, const char *filename, const char *indexPath);
|
||||
static int _compareFrames(const void *a, const void *b); // qsort callback. Not changing int.
|
||||
static void _convertFrame(VideoPlayerT *v, FrameBufferT *buffer, const AVFrame *frame);
|
||||
static const AVFrame *_deinterlace(VideoPlayerT *v, AVFrame *frame);
|
||||
static void _deinterlaceClose(VideoPlayerT *v);
|
||||
static bool _srtAppend(char **text, size_t *used, size_t *room, const char *addition);
|
||||
static char *_srtFromRect(const AVSubtitleRect *rect);
|
||||
static void _srtTime(int64_t milliseconds, char *out, size_t size);
|
||||
static bool _deinterlaceOpen(VideoPlayerT *v, const AVFrame *frame);
|
||||
static DecodeResultE _decodeFrame(VideoPlayerT *v, int64_t want);
|
||||
static void _decodeHere(VideoPlayerT *v);
|
||||
static int _decoderThread(void *data); // SDL thread entry. Not changing int.
|
||||
|
|
@ -311,6 +338,7 @@ static void *_alsaLibrary = NULL;
|
|||
static VideoPlayerT *_videoPlayerHash = NULL;
|
||||
static int32_t _nextId = 0;
|
||||
static MIX_Mixer *_mixer = NULL;
|
||||
static DeinterlaceE _deinterlaceMode = DEINTERLACE_AUTO; // --deinterlace, taken by every video opened after it is set
|
||||
static SDL_AudioSpec _mixSpec; // What the mixer feeds the device
|
||||
static int64_t _mixLatencyMs = 0; // Time between handing audio to the device and hearing it
|
||||
static int32_t _audioDelayMs = 0; // Per-game correction on top of the measured latency
|
||||
|
|
@ -807,6 +835,91 @@ static void _convertFrame(VideoPlayerT *v, FrameBufferT *buffer, const AVFrame *
|
|||
}
|
||||
|
||||
|
||||
// A frame ready to convert: the deinterlaced one when the picture is interlaced and the option
|
||||
// allows it, and the frame itself otherwise. Laserdisc rips are the reason this exists -- a disc
|
||||
// held interlaced fields, and a rip that kept them combs on every progressive display.
|
||||
static const AVFrame *_deinterlace(VideoPlayerT *v, AVFrame *frame) {
|
||||
if ((v->deinterlace == DEINTERLACE_OFF) || (frame->format == AV_PIX_FMT_NONE)) {
|
||||
return frame;
|
||||
}
|
||||
// bwdif is told to leave progressive frames alone, so in automatic mode the graph is built only
|
||||
// once something interlaced actually turns up and costs nothing on a progressive disc.
|
||||
if ((v->deinterlace == DEINTERLACE_AUTO) && ((frame->flags & AV_FRAME_FLAG_INTERLACED) == 0) && (v->filterGraph == NULL)) {
|
||||
return frame;
|
||||
}
|
||||
if ((v->filterGraph != NULL) && ((frame->format != v->filterFormat) || (frame->width != v->filterWidth) || (frame->height != v->filterHeight))) {
|
||||
_deinterlaceClose(v);
|
||||
}
|
||||
if ((v->filterGraph == NULL) && !_deinterlaceOpen(v, frame)) {
|
||||
return frame;
|
||||
}
|
||||
av_frame_unref(v->filterFrame);
|
||||
if (av_buffersrc_add_frame_flags(v->filterSource, frame, AV_BUFFERSRC_FLAG_KEEP_REF) < 0) {
|
||||
return frame;
|
||||
}
|
||||
if (av_buffersink_get_frame(v->filterSink, v->filterFrame) < 0) {
|
||||
return frame;
|
||||
}
|
||||
|
||||
return v->filterFrame;
|
||||
}
|
||||
|
||||
|
||||
static void _deinterlaceClose(VideoPlayerT *v) {
|
||||
if (v->filterGraph != NULL) {
|
||||
avfilter_graph_free(&v->filterGraph);
|
||||
v->filterSource = NULL;
|
||||
v->filterSink = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// buffer -> bwdif -> buffersink. bwdif runs in send_frame mode, one picture out for one in: a
|
||||
// disc is addressed by frame number, and a filter that turned each frame into two fields would
|
||||
// move every frame in the index.
|
||||
static bool _deinterlaceOpen(VideoPlayerT *v, const AVFrame *frame) {
|
||||
char arguments[FILTER_ARGUMENTS_MAX];
|
||||
const AVFilter *source = avfilter_get_by_name("buffer");
|
||||
const AVFilter *sink = avfilter_get_by_name("buffersink");
|
||||
const AVFilter *bwdif = avfilter_get_by_name("bwdif");
|
||||
AVFilterContext *filter = NULL;
|
||||
AVRational ratio = (frame->sample_aspect_ratio.num > 0) ? frame->sample_aspect_ratio : (AVRational){ 1, 1 };
|
||||
|
||||
if ((source == NULL) || (sink == NULL) || (bwdif == NULL)) {
|
||||
utilTrace("Video %d: this build has no deinterlacer.", v->id);
|
||||
v->deinterlace = DEINTERLACE_OFF;
|
||||
return false;
|
||||
}
|
||||
v->filterGraph = avfilter_graph_alloc();
|
||||
if (v->filterGraph == NULL) {
|
||||
utilDie("Unable to allocate the deinterlacing graph.");
|
||||
}
|
||||
snprintf(arguments, sizeof(arguments), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
|
||||
frame->width, frame->height, frame->format, v->videoTimeBase.num, v->videoTimeBase.den, ratio.num, ratio.den);
|
||||
if ((avfilter_graph_create_filter(&v->filterSource, source, "in", arguments, NULL, v->filterGraph) < 0) ||
|
||||
(avfilter_graph_create_filter(&filter, bwdif, "bwdif", (v->deinterlace == DEINTERLACE_ON) ? "mode=send_frame:parity=auto:deint=all" : "mode=send_frame:parity=auto:deint=interlaced", NULL, v->filterGraph) < 0) ||
|
||||
(avfilter_graph_create_filter(&v->filterSink, sink, "out", NULL, NULL, v->filterGraph) < 0) ||
|
||||
(avfilter_link(v->filterSource, 0, filter, 0) < 0) ||
|
||||
(avfilter_link(filter, 0, v->filterSink, 0) < 0) ||
|
||||
(avfilter_graph_config(v->filterGraph, NULL) < 0)) {
|
||||
utilTrace("Video %d: the deinterlacing graph would not build; leaving the picture alone.", v->id);
|
||||
_deinterlaceClose(v);
|
||||
v->deinterlace = DEINTERLACE_OFF;
|
||||
return false;
|
||||
}
|
||||
v->filterFormat = (enum AVPixelFormat)frame->format;
|
||||
v->filterWidth = frame->width;
|
||||
v->filterHeight = frame->height;
|
||||
if (!v->filterReported) {
|
||||
v->filterReported = true;
|
||||
utilTrace("Video %d: deinterlacing with bwdif.", v->id);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Decodes frame "want" into the back buffer. Runs on the decoder thread. On DECODE_ERROR the
|
||||
// message is in threadErrMsg; the caller raises threadError under the lock, which publishes both.
|
||||
static DecodeResultE _decodeFrame(VideoPlayerT *v, int64_t want) {
|
||||
|
|
@ -846,13 +959,13 @@ static DecodeResultE _decodeFrame(VideoPlayerT *v, int64_t want) {
|
|||
v->hwReported = true;
|
||||
utilTrace("Video %d: first hardware frame read back as %s %dx%d", v->id, av_get_pix_fmt_name((enum AVPixelFormat)v->hwFrame->format), v->hwFrame->width, v->hwFrame->height);
|
||||
}
|
||||
_convertFrame(v, &v->back, v->hwFrame);
|
||||
_convertFrame(v, &v->back, _deinterlace(v, v->hwFrame));
|
||||
} else {
|
||||
if (v->v4l2 && !v->hwReported) {
|
||||
v->hwReported = true;
|
||||
utilTrace("Video %d: first V4L2 frame arrived as %s %dx%d", v->id, av_get_pix_fmt_name((enum AVPixelFormat)v->videoFrame->format), v->videoFrame->width, v->videoFrame->height);
|
||||
}
|
||||
_convertFrame(v, &v->back, v->videoFrame);
|
||||
_convertFrame(v, &v->back, _deinterlace(v, v->videoFrame));
|
||||
}
|
||||
av_frame_unref(v->videoFrame);
|
||||
v->nextDecodeFrame = want + 1;
|
||||
|
|
@ -1296,11 +1409,13 @@ static void _openVideo(VideoPlayerT *v, const char *filename) {
|
|||
utilDie("Unable to open the video decoder for %s.", filename);
|
||||
}
|
||||
}
|
||||
v->videoPacket = av_packet_alloc();
|
||||
v->videoFrame = av_frame_alloc();
|
||||
if (!v->videoPacket || !v->videoFrame) {
|
||||
v->videoPacket = av_packet_alloc();
|
||||
v->videoFrame = av_frame_alloc();
|
||||
v->filterFrame = av_frame_alloc();
|
||||
if (!v->videoPacket || !v->videoFrame || !v->filterFrame) {
|
||||
utilDie("Unable to allocate video decoding buffers.");
|
||||
}
|
||||
v->deinterlace = _deinterlaceMode;
|
||||
v->nextDecodeFrame = -1;
|
||||
v->seekKeyframe = 0;
|
||||
}
|
||||
|
|
@ -1424,6 +1539,111 @@ static void _resetClock(VideoPlayerT *v, uint64_t now) {
|
|||
|
||||
// Positions the video demuxer at a keyframe and resets the decoder. Only ever called by whichever
|
||||
// thread is decoding: the decoder thread, or the frame loop in deterministic mode.
|
||||
// Appends to a growing string, doubling it as it goes. False when it will not grow.
|
||||
static bool _srtAppend(char **text, size_t *used, size_t *room, const char *addition) {
|
||||
size_t length = strlen(addition);
|
||||
size_t want = *room;
|
||||
char *grown = NULL;
|
||||
|
||||
if ((*used + length + 1) > SRT_BYTES_MAX) {
|
||||
return false;
|
||||
}
|
||||
while ((*used + length + 1) > want) {
|
||||
want = (want == 0) ? AVIO_BUFFER_BYTES : (want * 2);
|
||||
}
|
||||
if (want != *room) {
|
||||
grown = (char *)realloc(*text, want);
|
||||
if (grown == NULL) {
|
||||
return false;
|
||||
}
|
||||
*text = grown;
|
||||
*room = want;
|
||||
}
|
||||
memcpy(*text + *used, addition, length + 1);
|
||||
*used += length;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// The words out of one subtitle rectangle. A text subtitle carries them plainly; an ASS one wraps
|
||||
// them in a dialogue line whose last field is the text, with formatting in braces and its own
|
||||
// newline escape. A bitmap subtitle has no words at all and comes back NULL.
|
||||
static char *_srtFromRect(const AVSubtitleRect *rect) {
|
||||
const char *source = NULL;
|
||||
char *out = NULL;
|
||||
size_t length = 0;
|
||||
size_t in = 0;
|
||||
size_t commas = 0;
|
||||
bool brace = false;
|
||||
|
||||
if (rect->type == SUBTITLE_TEXT) {
|
||||
source = rect->text;
|
||||
} else if (rect->type == SUBTITLE_ASS) {
|
||||
source = rect->ass;
|
||||
// FFmpeg hands over the dialogue fields alone: read order, layer, style, name, three
|
||||
// margins and an effect, and then the words.
|
||||
while ((*source != '\0') && (commas < SRT_ASS_FIELDS)) {
|
||||
if (*source == ',') {
|
||||
commas++;
|
||||
}
|
||||
source++;
|
||||
}
|
||||
if (commas < SRT_ASS_FIELDS) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if ((source == NULL) || (source[0] == '\0')) {
|
||||
return NULL;
|
||||
}
|
||||
out = (char *)malloc(strlen(source) + 1);
|
||||
if (out == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
for (in = 0; source[in] != '\0'; in++) {
|
||||
if (brace) {
|
||||
brace = (source[in] != '}');
|
||||
continue;
|
||||
}
|
||||
if (source[in] == '{') {
|
||||
brace = true;
|
||||
continue;
|
||||
}
|
||||
// ASS writes a line break as \N, and both cases turn up.
|
||||
if ((source[in] == '\\') && ((source[in + 1] == 'N') || (source[in + 1] == 'n'))) {
|
||||
out[length++] = '\n';
|
||||
in++;
|
||||
continue;
|
||||
}
|
||||
if ((source[in] == '\r') || (source[in] == '\n')) {
|
||||
out[length++] = '\n';
|
||||
continue;
|
||||
}
|
||||
out[length++] = source[in];
|
||||
}
|
||||
out[length] = '\0';
|
||||
if (length == 0) {
|
||||
free(out);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
// Milliseconds as SubRip writes a time.
|
||||
static void _srtTime(int64_t milliseconds, char *out, size_t size) {
|
||||
if (milliseconds < 0) {
|
||||
milliseconds = 0;
|
||||
}
|
||||
snprintf(out, size, "%02d:%02d:%02d,%03d",
|
||||
(int32_t)(milliseconds / MS_PER_HOUR),
|
||||
(int32_t)((milliseconds % MS_PER_HOUR) / MS_PER_MINUTE),
|
||||
(int32_t)((milliseconds % MS_PER_MINUTE) / (int64_t)MS_PER_SECOND),
|
||||
(int32_t)(milliseconds % (int64_t)MS_PER_SECOND));
|
||||
}
|
||||
|
||||
|
||||
static void _seekVideo(VideoPlayerT *v, int64_t keyframe) {
|
||||
if (av_seek_frame(v->videoFormat, v->videoStream, v->frames[keyframe].pts, AVSEEK_FLAG_BACKWARD) < 0) {
|
||||
avformat_seek_file(v->videoFormat, v->videoStream, INT64_MIN, 0, INT64_MAX, 0);
|
||||
|
|
@ -1670,6 +1890,178 @@ double videoGetFps(int32_t playerHandle) {
|
|||
}
|
||||
|
||||
|
||||
// How many subtitle tracks the file holds, bitmap ones included: they are counted so that the
|
||||
// numbering matches what a player or ffprobe shows, and videoReadSubtitles says which have words.
|
||||
int32_t videoGetSubtitleTracks(int32_t playerHandle) {
|
||||
VideoPlayerT *v = _getPlayer(playerHandle, "videoGetSubtitleTracks");
|
||||
AVFormatContext *format = _formatOpen(v->filename);
|
||||
int32_t count = 0;
|
||||
uint32_t x = 0;
|
||||
|
||||
if (format == NULL) {
|
||||
return 0;
|
||||
}
|
||||
if (avformat_find_stream_info(format, NULL) >= 0) {
|
||||
for (x = 0; x < format->nb_streams; x++) {
|
||||
if (format->streams[x]->codecpar->codec_type == AVMEDIA_TYPE_SUBTITLE) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
_formatClose(&format);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
|
||||
// The language of a subtitle track as the file labels it, "" when it says nothing. The pointer is
|
||||
// good until the next call.
|
||||
const char *videoGetSubtitleLanguage(int32_t playerHandle, int32_t track) {
|
||||
static char language[LANGUAGE_CODE_BYTES];
|
||||
VideoPlayerT *v = _getPlayer(playerHandle, "videoGetSubtitleLanguage");
|
||||
AVFormatContext *format = _formatOpen(v->filename);
|
||||
AVDictionaryEntry *entry = NULL;
|
||||
int32_t seen = 0;
|
||||
uint32_t x = 0;
|
||||
|
||||
language[0] = '\0';
|
||||
if (format == NULL) {
|
||||
return language;
|
||||
}
|
||||
if (avformat_find_stream_info(format, NULL) >= 0) {
|
||||
for (x = 0; x < format->nb_streams; x++) {
|
||||
if (format->streams[x]->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
|
||||
continue;
|
||||
}
|
||||
if (seen == track) {
|
||||
entry = av_dict_get(format->streams[x]->metadata, "language", NULL, 0);
|
||||
if ((entry != NULL) && (entry->value != NULL)) {
|
||||
SDL_strlcpy(language, entry->value, sizeof(language));
|
||||
}
|
||||
break;
|
||||
}
|
||||
seen++;
|
||||
}
|
||||
}
|
||||
_formatClose(&format);
|
||||
|
||||
return language;
|
||||
}
|
||||
|
||||
|
||||
// One subtitle track read out of the container as SubRip text, which is the form the engine's own
|
||||
// subtitle loader already takes. The whole file is walked once, decoding nothing but the subtitle
|
||||
// packets, so this costs a pass over the file and no picture decoding at all. NULL when the track
|
||||
// does not exist, holds pictures rather than words, or yields nothing; free() what comes back.
|
||||
char *videoReadSubtitles(int32_t playerHandle, int32_t track) {
|
||||
VideoPlayerT *v = _getPlayer(playerHandle, "videoReadSubtitles");
|
||||
AVFormatContext *format = _formatOpen(v->filename);
|
||||
AVCodecContext *codec = NULL;
|
||||
const AVCodec *decoder = NULL;
|
||||
AVPacket *packet = NULL;
|
||||
AVSubtitle subtitle;
|
||||
AVRational timeBase;
|
||||
char *text = NULL;
|
||||
char *words = NULL;
|
||||
char line[SRT_CUE_MAX];
|
||||
char from[SRT_TIME_MAX];
|
||||
char to[SRT_TIME_MAX];
|
||||
size_t used = 0;
|
||||
size_t room = 0;
|
||||
int64_t start = 0;
|
||||
int64_t end = 0;
|
||||
int32_t stream = -1;
|
||||
int32_t seen = 0;
|
||||
int32_t cues = 0;
|
||||
int got = 0;
|
||||
uint32_t x = 0;
|
||||
bool ok = true;
|
||||
|
||||
if (format == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
if (avformat_find_stream_info(format, NULL) < 0) {
|
||||
_formatClose(&format);
|
||||
return NULL;
|
||||
}
|
||||
for (x = 0; x < format->nb_streams; x++) {
|
||||
if (format->streams[x]->codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
|
||||
continue;
|
||||
}
|
||||
if (seen == track) {
|
||||
stream = (int32_t)x;
|
||||
break;
|
||||
}
|
||||
seen++;
|
||||
}
|
||||
if (stream < 0) {
|
||||
_formatClose(&format);
|
||||
return NULL;
|
||||
}
|
||||
timeBase = format->streams[stream]->time_base;
|
||||
decoder = avcodec_find_decoder(format->streams[stream]->codecpar->codec_id);
|
||||
codec = (decoder != NULL) ? avcodec_alloc_context3(decoder) : NULL;
|
||||
packet = av_packet_alloc();
|
||||
if ((codec == NULL) || (packet == NULL) ||
|
||||
(avcodec_parameters_to_context(codec, format->streams[stream]->codecpar) < 0) ||
|
||||
(avcodec_open2(codec, decoder, NULL) < 0)) {
|
||||
utilTrace("Video %d: subtitle track %d has no decoder in this build.", v->id, track);
|
||||
av_packet_free(&packet);
|
||||
avcodec_free_context(&codec);
|
||||
_formatClose(&format);
|
||||
return NULL;
|
||||
}
|
||||
while (ok && (av_read_frame(format, packet) >= 0)) {
|
||||
if (packet->stream_index != stream) {
|
||||
av_packet_unref(packet);
|
||||
continue;
|
||||
}
|
||||
memset(&subtitle, 0, sizeof(subtitle));
|
||||
got = 0;
|
||||
if (avcodec_decode_subtitle2(codec, &subtitle, &got, packet) >= 0) {
|
||||
if (got != 0) {
|
||||
start = (int64_t)((double)packet->pts * av_q2d(timeBase) * MS_PER_SECOND) + (int64_t)subtitle.start_display_time;
|
||||
if (subtitle.end_display_time > subtitle.start_display_time) {
|
||||
end = start + (int64_t)(subtitle.end_display_time - subtitle.start_display_time);
|
||||
} else {
|
||||
end = start + (int64_t)((double)packet->duration * av_q2d(timeBase) * MS_PER_SECOND);
|
||||
}
|
||||
for (x = 0; (x < subtitle.num_rects) && ok; x++) {
|
||||
words = _srtFromRect(subtitle.rects[x]);
|
||||
if (words == NULL) {
|
||||
continue;
|
||||
}
|
||||
cues++;
|
||||
_srtTime(start, from, sizeof(from));
|
||||
_srtTime(end, to, sizeof(to));
|
||||
snprintf(line, sizeof(line), "%d\n", cues);
|
||||
ok = _srtAppend(&text, &used, &room, line) &&
|
||||
_srtAppend(&text, &used, &room, from) &&
|
||||
_srtAppend(&text, &used, &room, " --> ") &&
|
||||
_srtAppend(&text, &used, &room, to) &&
|
||||
_srtAppend(&text, &used, &room, "\n") &&
|
||||
_srtAppend(&text, &used, &room, words) &&
|
||||
_srtAppend(&text, &used, &room, "\n\n");
|
||||
free(words);
|
||||
}
|
||||
}
|
||||
avsubtitle_free(&subtitle);
|
||||
}
|
||||
av_packet_unref(packet);
|
||||
}
|
||||
av_packet_free(&packet);
|
||||
avcodec_free_context(&codec);
|
||||
_formatClose(&format);
|
||||
if (!ok || (cues == 0)) {
|
||||
free(text);
|
||||
return NULL;
|
||||
}
|
||||
utilTrace("Video %d: subtitle track %d read as %d cues.", v->id, track, cues);
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
|
||||
int64_t videoGetFrame(int32_t playerHandle) {
|
||||
return _getPlayer(playerHandle, "videoGetFrame")->frame;
|
||||
}
|
||||
|
|
@ -1754,11 +2146,6 @@ const char *videoGetDecoderDescription(void) {
|
|||
}
|
||||
|
||||
|
||||
bool videoGetMonochrome(int32_t playerHandle) {
|
||||
return _getPlayer(playerHandle, "videoGetMonochrome")->monochrome;
|
||||
}
|
||||
|
||||
|
||||
// Reads one pixel of the frame being shown. Returns false if there is no frame yet.
|
||||
bool videoGetPixel(int32_t playerHandle, int32_t x, int32_t y, uint8_t *r, uint8_t *g, uint8_t *b) {
|
||||
VideoPlayerT *v = _getPlayer(playerHandle, "videoGetPixel");
|
||||
|
|
@ -2108,6 +2495,11 @@ void videoSetHardwareDecoding(bool enabled) {
|
|||
// changing nothing, and the distance from neutral is applied to every luma sample in eighths, so 0
|
||||
// halves the brightness and 8 raises it by half. Turning it off, or asking for the neutral level,
|
||||
// puts the picture back.
|
||||
void videoSetDeinterlace(DeinterlaceE mode) {
|
||||
_deinterlaceMode = mode;
|
||||
}
|
||||
|
||||
|
||||
void videoSetLuma(int32_t playerHandle, bool enabled, int32_t level) {
|
||||
VideoPlayerT *v = _getPlayer(playerHandle, "videoSetLuma");
|
||||
|
||||
|
|
@ -2167,6 +2559,8 @@ void videoUnload(int32_t playerHandle) {
|
|||
av_free(v->back.data[x]);
|
||||
}
|
||||
sws_freeContext(v->sws);
|
||||
_deinterlaceClose(v);
|
||||
av_frame_free(&v->filterFrame);
|
||||
av_frame_free(&v->hwFrame);
|
||||
av_frame_free(&v->videoFrame);
|
||||
av_packet_free(&v->videoPacket);
|
||||
|
|
|
|||
|
|
@ -37,6 +37,15 @@
|
|||
#define LUMA_LEVEL_NEUTRAL 4 // ... where this level changes nothing, 0 halves the luma and 8 raises it by half
|
||||
|
||||
|
||||
// What --deinterlace asks for. A laserdisc held interlaced fields, and a rip that kept them combs
|
||||
// on every progressive display; a rip that was deinterlaced when it was made needs nothing here.
|
||||
typedef enum {
|
||||
DEINTERLACE_OFF, // Never, whatever the picture says
|
||||
DEINTERLACE_AUTO, // Only the frames the file marks interlaced. The default
|
||||
DEINTERLACE_ON // Every frame, for a file whose flags lie
|
||||
} DeinterlaceE;
|
||||
|
||||
|
||||
void videoFlash(int32_t playerHandle);
|
||||
int32_t videoGetAudioCalibration(void);
|
||||
int32_t videoGetAudioDelay(void);
|
||||
|
|
@ -48,16 +57,26 @@ int64_t videoGetFrame(int32_t playerHandle);
|
|||
int64_t videoGetFrameCount(int32_t playerHandle);
|
||||
int32_t videoGetHeight(int32_t playerHandle);
|
||||
const char *videoGetLanguage(int32_t playerHandle, int32_t audioTrack);
|
||||
|
||||
// Subtitle tracks carried inside the container. Bitmap tracks are counted so the numbering matches
|
||||
// what any other player shows; videoReadSubtitles tells you which of them hold words.
|
||||
int32_t videoGetSubtitleTracks(int32_t playerHandle);
|
||||
const char *videoGetSubtitleLanguage(int32_t playerHandle, int32_t track);
|
||||
|
||||
// One track as SubRip text, or NULL. free() it.
|
||||
char *videoReadSubtitles(int32_t playerHandle, int32_t track);
|
||||
const char *videoGetLanguageDescription(const char *languageCode);
|
||||
MIX_Mixer *videoGetMixer(void);
|
||||
const char *videoGetDecoderDescription(void);
|
||||
bool videoGetMonochrome(int32_t playerHandle);
|
||||
bool videoGetPixel(int32_t playerHandle, int32_t x, int32_t y, uint8_t *r, uint8_t *g, uint8_t *b);
|
||||
bool videoGetPixels(int32_t playerHandle, const uint8_t **pixels, int32_t *pitch);
|
||||
void videoGetVolume(int32_t playerHandle, int32_t *leftPercent, int32_t *rightPercent);
|
||||
int32_t videoGetWidth(int32_t playerHandle);
|
||||
bool videoGetYUVPixel(int32_t playerHandle, int32_t x, int32_t y, uint8_t *luma, uint8_t *cb, uint8_t *cr);
|
||||
void videoInit(MIX_Mixer *mixer);
|
||||
|
||||
// What every video opened from now on does about interlaced pictures.
|
||||
void videoSetDeinterlace(DeinterlaceE mode);
|
||||
bool videoIsPlaying(int32_t playerHandle);
|
||||
int32_t videoLoad(const char *videoFilename, const char *audioFilename, const char *indexPath, SDL_Renderer *renderer, bool rgb);
|
||||
void videoLockAudio(void);
|
||||
|
|
|
|||
6
thirdparty/SDL3_image/CMakeLists.txt
vendored
6
thirdparty/SDL3_image/CMakeLists.txt
vendored
|
|
@ -763,7 +763,11 @@ if(SDLIMAGE_JXL)
|
|||
# JPEGXL_ENABLE_PLUGINS variable is used by libjxl
|
||||
set(JPEGXL_ENABLE_PLUGINS OFF CACHE BOOL "libjxl manpage option" FORCE)
|
||||
# JPEGXL_ENABLE_SKCMS variable is used by libjxl
|
||||
set(JPEGXL_ENABLE_SKCMS OFF CACHE BOOL "libjxl skcms option" FORCE)
|
||||
# Singe: upstream forces lcms2 here, which is libjxl's own fallback for big-endian
|
||||
# machines. Every target Singe builds is little-endian, where libjxl defaults to skcms --
|
||||
# far smaller, and one less library to vendor. Left as a default rather than forced, so a
|
||||
# caller can still ask for lcms2.
|
||||
set(JPEGXL_ENABLE_SKCMS ON CACHE BOOL "libjxl skcms option")
|
||||
# JPEGXL_FORCE_SYSTEM_HWY variable is used by libjxl
|
||||
set(JPEGXL_FORCE_SYSTEM_HWY OFF CACHE BOOL "libjxl highway option" FORCE)
|
||||
sdl_check_project_in_subfolder(external/libjxl libjxl SDLIMAGE_VENDORED)
|
||||
|
|
|
|||
4
thirdparty/SDL3_image/external/libjxl/.clang-format
vendored
Normal file
4
thirdparty/SDL3_image/external/libjxl/.clang-format
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
BasedOnStyle: Google
|
||||
IncludeCategories:
|
||||
- Regex: '^<hwy/'
|
||||
Priority: 2
|
||||
70
thirdparty/SDL3_image/external/libjxl/.clang-tidy
vendored
Normal file
70
thirdparty/SDL3_image/external/libjxl/.clang-tidy
vendored
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# Disabled checks:
|
||||
# - google-readability-todo: We don't use the google TODO format.
|
||||
#
|
||||
# - modernize-deprecated-headers: We don't use std:: versions of the standard
|
||||
# types and functions like size_t or printf, so we should include <stdio.h>
|
||||
# instead <cstdio>.
|
||||
# - modernize-return-braced-init-list: this often doesn't improve readability.
|
||||
# - modernize-use-auto: is too aggressive towards using auto.
|
||||
# - modernize-use-default-member-init: with a mix of constructors and default
|
||||
# member initialization this can be confusing if enforced.
|
||||
# - modernize-use-trailing-return-type: does not improve readability when used
|
||||
# systematically.
|
||||
# - modernize-use-using: typedefs are ok.
|
||||
#
|
||||
# - readability-else-after-return: It doesn't always improve readability.
|
||||
# - readability-static-accessed-through-instance
|
||||
# It is often more useful and readable to access a constant of a passed
|
||||
# variable (like d.N) instead of using the type of the variable that could be
|
||||
# long and complex.
|
||||
# - readability-uppercase-literal-suffix: we write 1.0f, not 1.0F.
|
||||
|
||||
Checks: >-
|
||||
bugprone-*,
|
||||
clang-*,
|
||||
-clang-diagnostic-unused-command-line-argument,
|
||||
google-*,
|
||||
modernize-*,
|
||||
performance-*,
|
||||
readability-*,
|
||||
-google-readability-todo,
|
||||
-modernize-deprecated-headers,
|
||||
-modernize-return-braced-init-list,
|
||||
-modernize-use-auto,
|
||||
-modernize-use-default-member-init,
|
||||
-modernize-use-trailing-return-type,
|
||||
-modernize-use-using,
|
||||
-readability-else-after-return,
|
||||
-readability-function-cognitive-complexity,
|
||||
-readability-static-accessed-through-instance,
|
||||
-readability-uppercase-literal-suffix,
|
||||
|
||||
|
||||
WarningsAsErrors: >-
|
||||
bugprone-argument-comment,
|
||||
bugprone-macro-parentheses,
|
||||
bugprone-suspicious-string-compare,
|
||||
bugprone-use-after-move,
|
||||
clang-*,
|
||||
clang-analyzer-*,
|
||||
-clang-diagnostic-unused-command-line-argument,
|
||||
google-build-using-namespace,
|
||||
google-explicit-constructor,
|
||||
google-readability-braces-around-statements,
|
||||
google-readability-namespace-comments,
|
||||
modernize-use-override,
|
||||
readability-inconsistent-declaration-parameter-name
|
||||
|
||||
# We are only interested in the headers from this projects, excluding
|
||||
# third_party/ and build/.
|
||||
HeaderFilterRegex: '^.*/(lib|tools)/.*\.h$'
|
||||
|
||||
CheckOptions:
|
||||
- key: readability-braces-around-statements.ShortStatementLines
|
||||
value: '2'
|
||||
- key: google-readability-braces-around-statements.ShortStatementLines
|
||||
value: '2'
|
||||
- key: readability-implicit-bool-conversion.AllowPointerConditions
|
||||
value: '1'
|
||||
- key: readability-implicit-bool-conversion.AllowIntegerConditions
|
||||
value: '1'
|
||||
17
thirdparty/SDL3_image/external/libjxl/.gitignore
vendored
Normal file
17
thirdparty/SDL3_image/external/libjxl/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Build output directories
|
||||
/build
|
||||
/build*
|
||||
/docker/*.log
|
||||
|
||||
# The downloaded corpora files for benchmark.
|
||||
/third_party/corpora
|
||||
|
||||
# hdrvdp source code
|
||||
third_party/hdrvdp-2.2.2
|
||||
third_party/hdrvdp-2.2.2.zip
|
||||
third_party/hdrvdp-2.2.2.zip.tmp
|
||||
|
||||
# Output plots
|
||||
tools/benchmark/metrics/plots
|
||||
tools/benchmark/metrics/results.csv
|
||||
tools/conformance/__pycache__
|
||||
29
thirdparty/SDL3_image/external/libjxl/.gitmodules
vendored
Normal file
29
thirdparty/SDL3_image/external/libjxl/.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
[submodule "third_party/brotli"]
|
||||
path = third_party/brotli
|
||||
url = https://github.com/libsdl-org/brotli.git
|
||||
branch = v1.1.0-SDL
|
||||
[submodule "third_party/lcms"]
|
||||
path = third_party/lcms
|
||||
url = https://github.com/mm2/Little-CMS
|
||||
[submodule "third_party/googletest"]
|
||||
path = third_party/googletest
|
||||
url = https://github.com/google/googletest
|
||||
[submodule "third_party/sjpeg"]
|
||||
path = third_party/sjpeg
|
||||
url = https://github.com/webmproject/sjpeg.git
|
||||
[submodule "third_party/skcms"]
|
||||
path = third_party/skcms
|
||||
url = https://skia.googlesource.com/skcms
|
||||
[submodule "third_party/highway"]
|
||||
path = third_party/highway
|
||||
url = https://github.com/libsdl-org/highway.git
|
||||
branch = 1.2.0-SDL
|
||||
[submodule "third_party/libpng"]
|
||||
path = third_party/libpng
|
||||
url = https://github.com/glennrp/libpng.git
|
||||
[submodule "third_party/zlib"]
|
||||
path = third_party/zlib
|
||||
url = https://github.com/madler/zlib.git
|
||||
[submodule "third_party/testdata"]
|
||||
path = testdata
|
||||
url = https://github.com/libjxl/testdata
|
||||
17
thirdparty/SDL3_image/external/libjxl/.readthedocs.yaml
vendored
Normal file
17
thirdparty/SDL3_image/external/libjxl/.readthedocs.yaml
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
#
|
||||
# readthedocs.io configuration file. See:
|
||||
# https://docs.readthedocs.io/en/stable/config-file/v2.html
|
||||
|
||||
version: 2
|
||||
|
||||
sphinx:
|
||||
configuration: doc/sphinx/conf.py
|
||||
|
||||
python:
|
||||
version: "3.7"
|
||||
install:
|
||||
- requirements: doc/sphinx/requirements.txt
|
||||
55
thirdparty/SDL3_image/external/libjxl/AUTHORS
vendored
Normal file
55
thirdparty/SDL3_image/external/libjxl/AUTHORS
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
# List of the project authors for copyright purposes. When contributing to the
|
||||
# project add your name or your organization's name to this list. See
|
||||
# CONTRIBUTING.md for details.
|
||||
#
|
||||
# For organizations:
|
||||
# Organization <email pattern: *@domain>
|
||||
#
|
||||
# For individuals:
|
||||
# Name <email address>
|
||||
#
|
||||
# Please keep each list sorted. If you wish to change your email address please
|
||||
# send a pull request.
|
||||
|
||||
# Organizations:
|
||||
Cloudinary Ltd. <*@cloudinary.com>
|
||||
Google LLC <*@google.com>
|
||||
|
||||
# Individuals:
|
||||
a-shvedov
|
||||
Alex Xu (Hello71) <alex_y_xu@yahoo.ca>
|
||||
Alexander Sago <cagelight@gmail.com>
|
||||
Andrius Lukas Narbutas <andrius4669@gmail.com>
|
||||
Aous Naman <aous@unsw.edu.au>
|
||||
Artem Selishchev
|
||||
Biswapriyo Nath <nathbappai@gmail.com>
|
||||
CanadianBaconBoi <beamconnor@gmail.com>
|
||||
Daniel Novomeský <dnovomesky@gmail.com>
|
||||
David Burnett <vargolsoft@gmail.com>
|
||||
Dirk Lemstra <dirk@lemstra.org>
|
||||
Don Olmstead <don.j.olmstead@gmail.com>
|
||||
Even Rouault <even.rouault@spatialys.com>
|
||||
Heiko Becker <heirecka@exherbo.org>
|
||||
Jon Sneyers <jon@cloudinary.com>
|
||||
Kai Hollberg <Schweinepriester@users.noreply.github.com>
|
||||
Kleis Auke Wolthuizen <github@kleisauke.nl>
|
||||
L. E. Segovia
|
||||
Leo Izen <leo.izen@gmail.com>
|
||||
Lovell Fuller
|
||||
Maarten DB <anonymous.maarten@gmail.com>
|
||||
Marcin Konicki <ahwayakchih@gmail.com>
|
||||
Martin Strunz
|
||||
Mathieu Malaterre <mathieu.malaterre@gmail.com>
|
||||
Mikk Leini <mikk.leini@krakul.eu>
|
||||
Misaki Kasumi <misakikasumi@outlook.com>
|
||||
Petr Diblík
|
||||
Pieter Wuille
|
||||
roland-rollo
|
||||
Samuel Leong <wvvwvvvvwvvw@gmail.com>
|
||||
Sandro <sandro.jaeckel@gmail.com>
|
||||
Stephan T. Lavavej <stl@nuwen.net>
|
||||
Thomas Bonfort <thomas.bonfort@airbus.com>
|
||||
Vincent Torri <vincent.torri@gmail.com>
|
||||
xiota
|
||||
Yonatan Nebenzhal <yonatan.nebenzhl@gmail.com>
|
||||
Ziemowit Zabawa <ziemek.zabawa@outlook.com>
|
||||
122
thirdparty/SDL3_image/external/libjxl/Android.mk
vendored
Normal file
122
thirdparty/SDL3_image/external/libjxl/Android.mk
vendored
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
LOCAL_PATH:= $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_C_INCLUDES := \
|
||||
$(LOCAL_PATH)/android \
|
||||
$(LOCAL_PATH)/lib/include \
|
||||
$(LOCAL_PATH)/third_party/highway \
|
||||
$(LOCAL_PATH)/third_party/brotli/c/include \
|
||||
$(LOCAL_PATH)/third_party/brotli/c/dec \
|
||||
|
||||
BROTLI_SRC_FILES := \
|
||||
third_party/brotli/c/common/constants.c \
|
||||
third_party/brotli/c/common/context.c \
|
||||
third_party/brotli/c/common/dictionary.c \
|
||||
third_party/brotli/c/common/platform.c \
|
||||
third_party/brotli/c/common/shared_dictionary.c \
|
||||
third_party/brotli/c/common/transform.c \
|
||||
third_party/brotli/c/dec/bit_reader.c \
|
||||
third_party/brotli/c/dec/decode.c \
|
||||
third_party/brotli/c/dec/huffman.c \
|
||||
third_party/brotli/c/dec/state.c \
|
||||
|
||||
HIGHWAY_SRC_FILES := \
|
||||
third_party/highway/hwy/aligned_allocator.cc \
|
||||
third_party/highway/hwy/per_target.cc \
|
||||
third_party/highway/hwy/targets.cc \
|
||||
|
||||
LOCAL_SRC_FILES := \
|
||||
$(BROTLI_SRC_FILES) \
|
||||
$(HIGHWAY_SRC_FILES) \
|
||||
lib/jxl/ac_strategy.cc \
|
||||
lib/jxl/alpha.cc \
|
||||
lib/jxl/ans_common.cc \
|
||||
lib/jxl/aux_out.cc \
|
||||
lib/jxl/base/cache_aligned.cc \
|
||||
lib/jxl/base/data_parallel.cc \
|
||||
lib/jxl/base/padded_bytes.cc \
|
||||
lib/jxl/base/random.cc \
|
||||
lib/jxl/blending.cc \
|
||||
lib/jxl/box_content_decoder.cc \
|
||||
lib/jxl/chroma_from_luma.cc \
|
||||
lib/jxl/coeff_order.cc \
|
||||
lib/jxl/color_encoding_internal.cc \
|
||||
lib/jxl/color_management.cc \
|
||||
lib/jxl/compressed_dc.cc \
|
||||
lib/jxl/convolve_separable5.cc \
|
||||
lib/jxl/convolve_separable7.cc \
|
||||
lib/jxl/convolve_slow.cc \
|
||||
lib/jxl/convolve_symmetric3.cc \
|
||||
lib/jxl/convolve_symmetric5.cc \
|
||||
lib/jxl/dct_scales.cc \
|
||||
lib/jxl/dec_ans.cc \
|
||||
lib/jxl/dec_cache.cc \
|
||||
lib/jxl/dec_context_map.cc \
|
||||
lib/jxl/dec_external_image.cc \
|
||||
lib/jxl/dec_frame.cc \
|
||||
lib/jxl/dec_group.cc \
|
||||
lib/jxl/dec_group_border.cc \
|
||||
lib/jxl/dec_huffman.cc \
|
||||
lib/jxl/dec_modular.cc \
|
||||
lib/jxl/dec_noise.cc \
|
||||
lib/jxl/dec_patch_dictionary.cc \
|
||||
lib/jxl/dec_xyb.cc \
|
||||
lib/jxl/decode.cc \
|
||||
lib/jxl/decode_to_jpeg.cc \
|
||||
lib/jxl/enc_bit_writer.cc \
|
||||
lib/jxl/entropy_coder.cc \
|
||||
lib/jxl/epf.cc \
|
||||
lib/jxl/fast_dct.cc \
|
||||
lib/jxl/fields.cc \
|
||||
lib/jxl/frame_header.cc \
|
||||
lib/jxl/gauss_blur.cc \
|
||||
lib/jxl/headers.cc \
|
||||
lib/jxl/huffman_table.cc \
|
||||
lib/jxl/icc_codec.cc \
|
||||
lib/jxl/icc_codec_common.cc \
|
||||
lib/jxl/image.cc \
|
||||
lib/jxl/image_bundle.cc \
|
||||
lib/jxl/image_metadata.cc \
|
||||
lib/jxl/jpeg/dec_jpeg_data.cc \
|
||||
lib/jxl/jpeg/dec_jpeg_data_writer.cc \
|
||||
lib/jxl/jpeg/jpeg_data.cc \
|
||||
lib/jxl/loop_filter.cc \
|
||||
lib/jxl/luminance.cc \
|
||||
lib/jxl/memory_manager_internal.cc \
|
||||
lib/jxl/modular/encoding/dec_ma.cc \
|
||||
lib/jxl/modular/encoding/encoding.cc \
|
||||
lib/jxl/modular/modular_image.cc \
|
||||
lib/jxl/modular/transform/rct.cc \
|
||||
lib/jxl/modular/transform/squeeze.cc \
|
||||
lib/jxl/modular/transform/transform.cc \
|
||||
lib/jxl/opsin_params.cc \
|
||||
lib/jxl/passes_state.cc \
|
||||
lib/jxl/quant_weights.cc \
|
||||
lib/jxl/quantizer.cc \
|
||||
lib/jxl/render_pipeline/low_memory_render_pipeline.cc \
|
||||
lib/jxl/render_pipeline/render_pipeline.cc \
|
||||
lib/jxl/render_pipeline/simple_render_pipeline.cc \
|
||||
lib/jxl/render_pipeline/stage_blending.cc \
|
||||
lib/jxl/render_pipeline/stage_chroma_upsampling.cc \
|
||||
lib/jxl/render_pipeline/stage_epf.cc \
|
||||
lib/jxl/render_pipeline/stage_from_linear.cc \
|
||||
lib/jxl/render_pipeline/stage_gaborish.cc \
|
||||
lib/jxl/render_pipeline/stage_noise.cc \
|
||||
lib/jxl/render_pipeline/stage_patches.cc \
|
||||
lib/jxl/render_pipeline/stage_splines.cc \
|
||||
lib/jxl/render_pipeline/stage_spot.cc \
|
||||
lib/jxl/render_pipeline/stage_to_linear.cc \
|
||||
lib/jxl/render_pipeline/stage_tone_mapping.cc \
|
||||
lib/jxl/render_pipeline/stage_upsampling.cc \
|
||||
lib/jxl/render_pipeline/stage_write.cc \
|
||||
lib/jxl/render_pipeline/stage_xyb.cc \
|
||||
lib/jxl/render_pipeline/stage_ycbcr.cc \
|
||||
lib/jxl/splines.cc \
|
||||
lib/jxl/toc.cc \
|
||||
|
||||
LOCAL_CFLAGS := -DJXL_INTERNAL_LIBRARY_BUILD -DJPEGXL_MAJOR_VERSION=0 -DJPEGXL_MINOR_VERSION=7 -DJPEGXL_PATCH_VERSION=2 -DJPEGXL_ENABLE_TRANSCODE_JPEG=1 -D__STDC_FORMAT_MACROS
|
||||
|
||||
LOCAL_MODULE := jxl
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
||||
278
thirdparty/SDL3_image/external/libjxl/CHANGELOG.md
vendored
Normal file
278
thirdparty/SDL3_image/external/libjxl/CHANGELOG.md
vendored
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.7.3] - 2026-02-10
|
||||
|
||||
### Fixed
|
||||
- fix tile dimension in low memory rendering pipeline (#4495 -
|
||||
[CVE-2025-12474](https://www.cve.org/cverecord?id=CVE-2025-12474))
|
||||
|
||||
## [0.7.2] - 2024-11-26
|
||||
|
||||
### Fixed
|
||||
- Huffman lookup table size fix (#3871 -
|
||||
[CVE-2024-11403](https://www.cve.org/cverecord?id=CVE-2024-11403))
|
||||
- Check height limit in modular trees. (#3943 -
|
||||
[CVE-2024-11498](https://www.cve.org/cverecord?id=CVE-2024-11498))
|
||||
|
||||
## [0.7.1] - 2024-06-28
|
||||
|
||||
### Fixed
|
||||
- decoding of some special images (#3662)
|
||||
|
||||
## [0.7] - 2022-07-21
|
||||
|
||||
### Added
|
||||
- Export version information in headers.
|
||||
- decoder API: Ability to decode the content of metadata boxes:
|
||||
`JXL_DEC_BOX`, `JXL_DEC_BOX_NEED_MORE_OUTPUT`, `JxlDecoderSetBoxBuffer`,
|
||||
`JxlDecoderGetBoxType`, `JxlDecoderGetBoxSizeRaw` and
|
||||
`JxlDecoderSetDecompressBoxes`.
|
||||
- decoder API: ability to mark the input is finished: `JxlDecoderCloseInput`.
|
||||
- decoder API: ability to request updates on different progressive events using
|
||||
`JxlDecoderSetProgressiveDetail`; currently supported events are
|
||||
`kDC`, `kLastPasses` and `kPasses`.
|
||||
- decoder API: ability to specify desired intensity target using
|
||||
`JxlDecoderSetDesiredIntensityTarget`
|
||||
- decoder API: new function `JxlDecoderSetCoalesced` to allow decoding
|
||||
non-coalesced (unblended) frames, e.g. layers of a composite still image
|
||||
or the cropped frames of a recompressed GIF/APNG.
|
||||
- decoder API: new function `JxlDecoderSetUnpremultiplyAlpha` to set
|
||||
preference for getting an associated alpha channel with premultiplied or
|
||||
unpremultiplied colors.
|
||||
- decoder API: field added to `JxlFrameHeader`: a `JxlLayerInfo` struct
|
||||
that contains crop dimensions and offsets and blending information for
|
||||
the non-coalesced case.
|
||||
- decoder API: new function `JxlDecoderGetExtraChannelBlendInfo` to get
|
||||
the blending information for extra channels in the non-coalesced case.
|
||||
- decoder API: new function `JxlDecoderSetMultithreadedImageOutCallback`,
|
||||
allowing output callbacks to receive more information about the number of
|
||||
threads on which they are running.
|
||||
- decoder API: new function `JxlDecoderSkipCurrentFrame` to skip processing
|
||||
the current frame after a progressive detail is reached.
|
||||
- decoder API: new function `JxlDecoderGetIntendedDownsamplingRatio` to get
|
||||
the intended downsampling ratio of progressive steps, based on the
|
||||
information in the frame header.
|
||||
- decoder API: new function `JxlDecoderSetRenderSpotcolors` to allow disabling
|
||||
rendering of spot colors.
|
||||
- decoder/encoder API: add two fields to `JXLBasicInfo`: `intrinsic_xsize`
|
||||
and `intrinsic_ysize` to signal the intrinsic size.
|
||||
- encoder API: ability to add metadata boxes, added new functions
|
||||
`JxlEncoderAddBox`, `JxlEncoderUseBoxes`, `JxlEncoderCloseBoxes` and
|
||||
`JxlEncoderCloseFrames`.
|
||||
- encoder API: added ability to set several encoder options / extra fields to
|
||||
frames using `JxlEncoderSetFrameName`, `JxlEncoderFrameSettingsSetOption`,
|
||||
`JxlEncoderFrameSettingsSetFloatOption`.
|
||||
- encoder API: added ability to check required codestream compatibility level
|
||||
and force specified using `JxlEncoderGetRequiredCodestreamLevel` and
|
||||
`JxlEncoderSetCodestreamLevel`.
|
||||
- encoder API: added ability to force emitting box-based container format
|
||||
using `JxlEncoderUseContainer`.
|
||||
- encoder API: added ability to store JPEG metadata for lossless reconstruction
|
||||
using `JxlEncoderStoreJPEGMetadata`
|
||||
- encoder API: new functions `JxlEncoderSetFrameHeader` and
|
||||
`JxlEncoderSetExtraChannelBlendInfo` to set animation
|
||||
and blending parameters of the frame, and `JxlEncoderInitFrameHeader` and
|
||||
`JxlEncoderInitBlendInfo` to initialize the structs to set.
|
||||
- encoder API: ability to encode arbitrary extra channels:
|
||||
`JxlEncoderInitExtraChannelInfo`, `JxlEncoderSetExtraChannelInfo`,
|
||||
`JxlEncoderSetExtraChannelName` and `JxlEncoderSetExtraChannelBuffer`.
|
||||
- encoder API: ability to plug custom CMS implementation using
|
||||
`JxlEncoderSetCms(JxlEncoder* enc, JxlCmsInterface cms)`
|
||||
- encoder API: added `JxlEncoderGetError` to retrieve last encoder error.
|
||||
|
||||
### Changed
|
||||
- decoder API: using `JxlDecoderCloseInput` at the end of all input is required
|
||||
when using JXL_DEC_BOX, and is now also encouraged in other cases, but not
|
||||
required in those other cases for backwards compatibility.
|
||||
- encoder API: `JxlEncoderCloseInput` now closes both frames and boxes input.
|
||||
- CLI: `cjxl` and `djxl` have been reimplemented on the base of public decoder
|
||||
and encoder API; dropped dependency on `gflags` for argument parsing.
|
||||
|
||||
### Deprecated
|
||||
- decoder API: `JXL_DEC_EXTENSIONS` event: use `JXL_DEC_BASIC_INFO`
|
||||
- decoder / encoder API: pixel types `JXL_TYPE_BOOLEAN` and `JXL_TYPE_UINT32`:
|
||||
consider using `JXL_TYPE_UINT8` and `JXL_TYPE_FLOAT` correspondingly.
|
||||
- decoder API: pixel format parameter for `JxlDecoderGetColorAsEncodedProfile`
|
||||
and `JxlDecoderGetICCProfileSize`: pass `NULL`.
|
||||
- decoder API: `JxlDecoderDefaultPixelFormat`
|
||||
- encoder API: `JxlEncoderOptions`: use `JxlEncoderFrameSettings` instead.
|
||||
- encoder API: `JxlEncoderOptionsCreate`: use `JxlEncoderFrameSettingsCreate`
|
||||
instead.
|
||||
- encoder API: `JxlEncoderOptionsSetDistance`: use `JxlEncoderSetFrameDistance`
|
||||
instead.
|
||||
- encoder API: `JxlEncoderOptionsSetLossless`: use `JxlEncoderSetFrameLossless`
|
||||
instead.
|
||||
- encoder API: `JxlEncoderOptionsSetEffort`: use
|
||||
`JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_EFFORT, effort)`
|
||||
instead.
|
||||
- encoder API: `JxlEncoderOptionsSetDecodingSpeed`: use
|
||||
`JxlEncoderFrameSettingsSetOption(frame_settings, JXL_ENC_FRAME_SETTING_DECODING_SPEED, tier)`
|
||||
instead.
|
||||
- encoder API: deprecated `JXL_ENC_NOT_SUPPORTED`, the encoder returns
|
||||
`JXL_ENC_ERROR` instead and there is no need to handle
|
||||
`JXL_ENC_NOT_SUPPORTED`.
|
||||
|
||||
## [0.6.1] - 2021-10-29
|
||||
### Changed
|
||||
- Security: Fix OOB read in splines rendering (#735 -
|
||||
[CVE-2021-22563](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-22563))
|
||||
- Security: Fix OOB copy (read/write) in out-of-order/multi-threaded decoding
|
||||
(#708 - [CVE-2021-22564](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-22564))
|
||||
- Fix segfault in `djxl` tool with `--allow_partial_files` flag (#781).
|
||||
- Fix border in extra channels when using upsampling (#796)
|
||||
|
||||
## [0.6] - 2021-10-04
|
||||
### Added
|
||||
- API: New functions to decode extra channels:
|
||||
`JxlDecoderExtraChannelBufferSize` and `JxlDecoderSetExtraChannelBuffer`.
|
||||
- API: New function `JxlEncoderInitBasicInfo` to initialize `JxlBasicInfo`
|
||||
(only needed when encoding). NOTE: it is now required to call this function
|
||||
when using the encoder. Padding was added to the struct for forward
|
||||
compatibility.
|
||||
- API: Support for encoding oriented images.
|
||||
- API: FLOAT16 support in the encoder API.
|
||||
- Rewrite of the GDK pixbuf loader plugin. Added proper color management and
|
||||
animation support.
|
||||
- Rewrite of GIMP plugin. Added compression parameters dialog and switched to
|
||||
using the public C API.
|
||||
- Debian packages for GDK pixbuf loader (`libjxl-gdk-pixbuf`) and GIMP
|
||||
(`libjxl-gimp-plugin`) plugins.
|
||||
- `cjxl`/`djxl` support for `stdin` and `stdout`.
|
||||
|
||||
### Changed
|
||||
- API: Renamed the field `alpha_associated` in `JxlExtraChannelInfo` to
|
||||
`alpha_premultiplied`, to match the corresponding name in `JxlBasicInfo`.
|
||||
- Improved the 2x2 downscaling method in the encoder for the optional color
|
||||
channel resampling for low bit rates.
|
||||
- Fixed: the combination of floating point original data, XYB color encoding,
|
||||
and Modular mode was broken (in both encoder and decoder). It now works.
|
||||
NOTE: this can cause the current encoder to write jxl bitstreams that do
|
||||
not decode with the old decoder. In particular this will happen when using
|
||||
cjxl with PFM, EXR, or floating point PSD input, and a combination of XYB
|
||||
and modular mode is used (which caused an encoder error before), e.g.
|
||||
using options like `-m -q 80` (lossy modular), `-d 4.5` or `--progressive_dc=1`
|
||||
(modular DC frame), or default lossy encoding on an image where patches
|
||||
end up being used. There is no problem when using cjxl with PNG, JPEG, GIF,
|
||||
APNG, PPM, PGM, PGX, or integer (8-bit or 16-bit) PSD input.
|
||||
- `libjxl` static library now bundles skcms, fixing static linking in
|
||||
downstream projects when skcms is used.
|
||||
- Spline rendering performance improvements.
|
||||
- Butteraugli changes for less visual masking.
|
||||
|
||||
## [0.5] - 2021-08-02
|
||||
### Added
|
||||
- API: New function to decode the image using a callback outputting a part of a
|
||||
row per call.
|
||||
- API: 16-bit float output support.
|
||||
- API: `JxlDecoderRewind` and `JxlDecoderSkipFrames` functions to skip more
|
||||
efficiently to earlier animation frames.
|
||||
- API: `JxlDecoderSetPreferredColorProfile` function to choose color profile in
|
||||
certain circumstances.
|
||||
- encoder: Adding `center_x` and `center_y` flags for more control of the tile
|
||||
order.
|
||||
- New encoder speeds `lightning` (1) and `thunder` (2).
|
||||
|
||||
### Changed
|
||||
- Re-licensed the project under a BSD 3-Clause license. See the
|
||||
[LICENSE](LICENSE) and [PATENTS](PATENTS) files for details.
|
||||
- Full JPEG XL part 1 specification support: Implemented all the spec required
|
||||
to decode files to pixels, including cases that are not used by the encoder
|
||||
yet. Part 2 of the spec (container format) is final but not fully implemented
|
||||
here.
|
||||
- Butteraugli metric improvements. Exact numbers are different from previous
|
||||
versions.
|
||||
- Memory reductions during decoding.
|
||||
- Reduce the size of the jxl_dec library by removing dependencies.
|
||||
- A few encoding speedups.
|
||||
- Clarify the security policy.
|
||||
- Significant encoding improvements (~5 %) and less ringing.
|
||||
- Butteraugli metric to have some less masking.
|
||||
- `cjxl` flag `--speed` is deprecated and replaced by the `--effort` synonym.
|
||||
|
||||
### Removed
|
||||
- API for returning a downsampled DC was deprecated
|
||||
(`JxlDecoderDCOutBufferSize` and `JxlDecoderSetDCOutBuffer`) and will be
|
||||
removed in the next release.
|
||||
|
||||
## [0.3.7] - 2021-03-29
|
||||
### Changed
|
||||
- Fix a rounding issue in 8-bit decoding.
|
||||
|
||||
## [0.3.6] - 2021-03-25
|
||||
### Changed
|
||||
- Fix a bug that could result in the generation of invalid codestreams as
|
||||
well as failure to decode valid streams.
|
||||
|
||||
## [0.3.5] - 2021-03-23
|
||||
### Added
|
||||
- New encode-time options for faster decoding at the cost of quality.
|
||||
- Man pages for cjxl and djxl.
|
||||
|
||||
### Changed
|
||||
- Memory usage improvements.
|
||||
- Faster decoding to 8-bit output with the C API.
|
||||
- GIMP plugin: avoid the sRGB conversion dialog for sRGB images, do not show
|
||||
a console window on Windows.
|
||||
- Various bug fixes.
|
||||
|
||||
## [0.3.4] - 2021-03-16
|
||||
### Changed
|
||||
- Improved box parsing.
|
||||
- Improved metadata handling.
|
||||
- Performance and memory usage improvements.
|
||||
|
||||
## [0.3.3] - 2021-03-05
|
||||
### Changed
|
||||
- Performance improvements for small images.
|
||||
- Add a (flag-protected) non-high-precision mode with better speed.
|
||||
- Significantly speed up the PQ EOTF.
|
||||
- Allow optional HDR tone mapping in djxl (--tone_map, --display_nits).
|
||||
- Change the behavior of djxl -j to make it consistent with cjxl (#153).
|
||||
- Improve image quality.
|
||||
- Improve EXIF handling.
|
||||
|
||||
## [0.3.2] - 2021-02-12
|
||||
### Changed
|
||||
- Fix embedded ICC encoding regression
|
||||
[#149](https://gitlab.com/wg1/jpeg-xl/-/issues/149).
|
||||
|
||||
## [0.3.1] - 2021-02-10
|
||||
### Changed
|
||||
- New experimental Butteraugli API (`jxl/butteraugli.h`).
|
||||
- Encoder improvements to low quality settings.
|
||||
- Bug fixes, including fuzzer-found potential security bug fixes.
|
||||
- Fixed `-q 100` and `-d 0` not triggering lossless modes.
|
||||
|
||||
## [0.3] - 2021-01-29
|
||||
### Changed
|
||||
- Minor change to the Decoder C API to accommodate future work for other ways
|
||||
to provide input.
|
||||
- Future decoder C API changes will be backwards compatible.
|
||||
- Lots of bug fixes since the previous version.
|
||||
|
||||
## [0.2] - 2020-12-24
|
||||
### Added
|
||||
- JPEG XL bitstream format is frozen. Files encoded with 0.2 will be supported
|
||||
by future versions.
|
||||
|
||||
### Changed
|
||||
- Files encoded with previous versions are not supported.
|
||||
|
||||
## [0.1.1] - 2020-12-01
|
||||
|
||||
## [0.1] - 2020-11-14
|
||||
### Added
|
||||
- Initial release of an encoder (`cjxl`) and decoder (`djxl`) that work
|
||||
together as well as a benchmark tool for comparison with other codecs
|
||||
(`benchmark_xl`).
|
||||
- Note: JPEG XL format is in the final stages of standardization, minor changes
|
||||
to the codestream format are still possible but we are not expecting any
|
||||
changes beyond what is required by bug fixing.
|
||||
- API: new decoder API in C, check the `examples/` directory for its example
|
||||
usage. The C API is a work in progress and likely to change both in API and
|
||||
ABI in future releases.
|
||||
476
thirdparty/SDL3_image/external/libjxl/CMakeLists.txt
vendored
Normal file
476
thirdparty/SDL3_image/external/libjxl/CMakeLists.txt
vendored
Normal file
|
|
@ -0,0 +1,476 @@
|
|||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
# Ubuntu bionic ships with cmake 3.10.
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
|
||||
# Honor VISIBILITY_INLINES_HIDDEN on all types of targets.
|
||||
if(POLICY CMP0063)
|
||||
cmake_policy(SET CMP0063 NEW)
|
||||
endif()
|
||||
# Pass CMAKE_EXE_LINKER_FLAGS to CC and CXX compilers when testing if they work.
|
||||
if(POLICY CMP0065)
|
||||
cmake_policy(SET CMP0065 NEW)
|
||||
endif()
|
||||
|
||||
# Set PIE flags for POSITION_INDEPENDENT_CODE targets, added in 3.14.
|
||||
if(POLICY CMP0083)
|
||||
cmake_policy(SET CMP0083 NEW)
|
||||
endif()
|
||||
|
||||
project(LIBJXL LANGUAGES C CXX)
|
||||
|
||||
include(CheckCXXSourceCompiles)
|
||||
check_cxx_source_compiles(
|
||||
"int main() {
|
||||
#if !defined(__EMSCRIPTEN__)
|
||||
static_assert(false, \"__EMSCRIPTEN__ is not defined\");
|
||||
#endif
|
||||
return 0;
|
||||
}"
|
||||
JPEGXL_EMSCRIPTEN
|
||||
)
|
||||
|
||||
message(STATUS "CMAKE_SYSTEM_PROCESSOR is ${CMAKE_SYSTEM_PROCESSOR}")
|
||||
include(CheckCXXCompilerFlag)
|
||||
check_cxx_compiler_flag("-fsanitize=fuzzer-no-link" CXX_FUZZERS_SUPPORTED)
|
||||
check_cxx_compiler_flag("-Xclang -mconstructor-aliases" CXX_CONSTRUCTOR_ALIASES_SUPPORTED)
|
||||
check_cxx_compiler_flag("-fmacro-prefix-map=OLD=NEW" CXX_MACRO_PREFIX_MAP)
|
||||
check_cxx_compiler_flag("-fno-rtti" CXX_NO_RTTI_SUPPORTED)
|
||||
|
||||
# Add "DebugOpt" CMake build type. Unlike builtin DEBUG it is optimized.
|
||||
string(REGEX REPLACE "-DNDEBUG " "" CMAKE_CXX_FLAGS_DEBUGOPT "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -DDEBUG" )
|
||||
string(REGEX REPLACE "-DNDEBUG " "" CMAKE_C_FLAGS_DEBUGOPT "${CMAKE_C_FLAGS_RELWITHDEBINFO} -DDEBUG" )
|
||||
|
||||
# Enabled PIE binaries by default if supported.
|
||||
include(CheckPIESupported OPTIONAL RESULT_VARIABLE CHECK_PIE_SUPPORTED)
|
||||
if(CHECK_PIE_SUPPORTED)
|
||||
check_pie_supported(LANGUAGES CXX)
|
||||
if(CMAKE_CXX_LINK_PIE_SUPPORTED)
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE TRUE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
### Project build options:
|
||||
if(CXX_FUZZERS_SUPPORTED)
|
||||
# Enabled by default except on arm64, Windows and Apple builds.
|
||||
set(ENABLE_FUZZERS_DEFAULT true)
|
||||
endif()
|
||||
find_package(PkgConfig)
|
||||
if(NOT APPLE AND NOT WIN32 AND NOT HAIKU AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64")
|
||||
pkg_check_modules(TCMallocMinimalVersionCheck QUIET IMPORTED_TARGET
|
||||
libtcmalloc_minimal)
|
||||
if(TCMallocMinimalVersionCheck_FOUND AND
|
||||
NOT TCMallocMinimalVersionCheck_VERSION VERSION_EQUAL 2.8.0)
|
||||
# Enabled by default except on Windows and Apple builds for
|
||||
# tcmalloc != 2.8.0. tcmalloc 2.8.1 already has a fix for this issue.
|
||||
set(ENABLE_TCMALLOC_DEFAULT true)
|
||||
else()
|
||||
message(STATUS
|
||||
"tcmalloc version ${TCMallocMinimalVersionCheck_VERSION} -- "
|
||||
"tcmalloc 2.8.0 disabled due to "
|
||||
"https://github.com/gperftools/gperftools/issues/1204")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
check_cxx_source_compiles(
|
||||
"int main() {
|
||||
#if !defined(HWY_DISABLED_TARGETS)
|
||||
static_assert(false, \"HWY_DISABLED_TARGETS is not defined\");
|
||||
#endif
|
||||
return 0;
|
||||
}"
|
||||
JXL_HWY_DISABLED_TARGETS_FORCED
|
||||
)
|
||||
|
||||
set(WARNINGS_AS_ERRORS_DEFAULT false)
|
||||
|
||||
if((SANITIZER STREQUAL "msan") OR JPEGXL_EMSCRIPTEN)
|
||||
set(BUNDLE_LIBPNG_DEFAULT YES)
|
||||
else()
|
||||
set(BUNDLE_LIBPNG_DEFAULT NO)
|
||||
endif()
|
||||
|
||||
# Standard cmake naming for building shared libraries.
|
||||
get_property(SHARED_LIBS_SUPPORTED GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS)
|
||||
option(BUILD_SHARED_LIBS "Build shared libraries instead of static ones" ${SHARED_LIBS_SUPPORTED})
|
||||
|
||||
set(JPEGXL_ENABLE_FUZZERS ${ENABLE_FUZZERS_DEFAULT} CACHE BOOL
|
||||
"Build JPEGXL fuzzer targets.")
|
||||
set(JPEGXL_ENABLE_DEVTOOLS false CACHE BOOL
|
||||
"Build JPEGXL developer tools.")
|
||||
set(JPEGXL_ENABLE_TOOLS true CACHE BOOL
|
||||
"Build JPEGXL user tools: cjxl and djxl.")
|
||||
set(JPEGXL_ENABLE_DOXYGEN true CACHE BOOL
|
||||
"Generate C API documentation using Doxygen.")
|
||||
set(JPEGXL_ENABLE_MANPAGES true CACHE BOOL
|
||||
"Build and install man pages for the command-line tools.")
|
||||
set(JPEGXL_ENABLE_BENCHMARK true CACHE BOOL
|
||||
"Build JPEGXL benchmark tools.")
|
||||
set(JPEGXL_ENABLE_EXAMPLES true CACHE BOOL
|
||||
"Build JPEGXL library usage examples.")
|
||||
set(JPEGXL_BUNDLE_LIBPNG ${BUNDLE_LIBPNG_DEFAULT} CACHE BOOL
|
||||
"Build libpng from source and link it statically.")
|
||||
set(JPEGXL_ENABLE_JNI true CACHE BOOL
|
||||
"Build JPEGXL JNI Java wrapper, if Java dependencies are installed.")
|
||||
set(JPEGXL_ENABLE_SJPEG true CACHE BOOL
|
||||
"Build JPEGXL with support for encoding with sjpeg.")
|
||||
set(JPEGXL_ENABLE_OPENEXR true CACHE BOOL
|
||||
"Build JPEGXL with support for OpenEXR if available.")
|
||||
set(JPEGXL_ENABLE_SKCMS true CACHE BOOL
|
||||
"Build with skcms instead of lcms2.")
|
||||
set(JPEGXL_BUNDLE_SKCMS true CACHE BOOL
|
||||
"When building with skcms, bundle it into libjxl.a.")
|
||||
set(JPEGXL_ENABLE_VIEWERS false CACHE BOOL
|
||||
"Build JPEGXL viewer tools for evaluation.")
|
||||
set(JPEGXL_ENABLE_TCMALLOC ${ENABLE_TCMALLOC_DEFAULT} CACHE BOOL
|
||||
"Build JPEGXL using gperftools (tcmalloc) allocator.")
|
||||
set(JPEGXL_ENABLE_PLUGINS false CACHE BOOL
|
||||
"Build third-party plugins to support JPEG XL in other applications.")
|
||||
set(JPEGXL_ENABLE_COVERAGE false CACHE BOOL
|
||||
"Enable code coverage tracking for libjxl. This also enables debug and disables optimizations.")
|
||||
set(JPEGXL_ENABLE_PROFILER false CACHE BOOL
|
||||
"Builds in support for profiling (printed by tools if extra flags given)")
|
||||
set(JPEGXL_ENABLE_SIZELESS_VECTORS false CACHE BOOL
|
||||
"Builds in support for SVE/RVV vectorization")
|
||||
set(JPEGXL_ENABLE_TRANSCODE_JPEG true CACHE BOOL
|
||||
"Builds in support for decoding transcoded JXL files back to JPEG,\
|
||||
disabling it makes the decoder reject JXL_DEC_JPEG_RECONSTRUCTION events,\
|
||||
(default enabled)")
|
||||
set(JPEGXL_STATIC false CACHE BOOL
|
||||
"Build tools as static binaries.")
|
||||
set(JPEGXL_WARNINGS_AS_ERRORS ${WARNINGS_AS_ERRORS_DEFAULT} CACHE BOOL
|
||||
"Treat warnings as errors during compilation.")
|
||||
set(JPEGXL_DEP_LICENSE_DIR "" CACHE STRING
|
||||
"Directory where to search for system dependencies \"copyright\" files.")
|
||||
set(JPEGXL_FORCE_NEON false CACHE BOOL
|
||||
"Set flags to enable NEON in arm if not enabled by your toolchain.")
|
||||
|
||||
|
||||
# Force system dependencies.
|
||||
set(JPEGXL_FORCE_SYSTEM_BROTLI false CACHE BOOL
|
||||
"Force using system installed brotli instead of third_party/brotli source.")
|
||||
set(JPEGXL_FORCE_SYSTEM_GTEST false CACHE BOOL
|
||||
"Force using system installed googletest (gtest/gmock) instead of third_party/googletest source.")
|
||||
set(JPEGXL_FORCE_SYSTEM_LCMS2 false CACHE BOOL
|
||||
"Force using system installed lcms2 instead of third_party/lcms source.")
|
||||
set(JPEGXL_FORCE_SYSTEM_HWY false CACHE BOOL
|
||||
"Force using system installed highway (libhwy-dev) instead of third_party/highway source.")
|
||||
|
||||
# Check minimum compiler versions. Older compilers are not supported and fail
|
||||
# with hard to understand errors.
|
||||
if (NOT CMAKE_C_COMPILER_ID STREQUAL CMAKE_CXX_COMPILER_ID)
|
||||
message(FATAL_ERROR "Different C/C++ compilers set: "
|
||||
"${CMAKE_C_COMPILER_ID} vs ${CMAKE_CXX_COMPILER_ID}")
|
||||
endif()
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
# Android NDK's toolchain.cmake fakes the clang version in
|
||||
# CMAKE_CXX_COMPILER_VERSION with an incorrect number, so ignore this.
|
||||
if (NOT CMAKE_ANDROID_NDK_TOOLCHAIN_VERSION MATCHES "clang"
|
||||
AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5)
|
||||
message(FATAL_ERROR
|
||||
"Minimum Clang version required is Clang 5, please update.")
|
||||
endif()
|
||||
elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 7)
|
||||
message(FATAL_ERROR
|
||||
"Minimum GCC version required is 7, please update.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
message(STATUS
|
||||
"Compiled IDs C:${CMAKE_C_COMPILER_ID}, C++:${CMAKE_CXX_COMPILER_ID}")
|
||||
|
||||
# CMAKE_EXPORT_COMPILE_COMMANDS is used to generate the compilation database
|
||||
# used by clang-tidy.
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
if(JPEGXL_STATIC)
|
||||
set(BUILD_SHARED_LIBS 0)
|
||||
# Clang developers say that in case to use "static" we have to build stdlib
|
||||
# ourselves; for real use case we don't care about stdlib, as it is "granted",
|
||||
# so just linking all other libraries is fine.
|
||||
if (NOT MSVC AND NOT APPLE)
|
||||
set(CMAKE_FIND_LIBRARY_SUFFIXES .a)
|
||||
set(CMAKE_EXE_LINKER_FLAGS
|
||||
"${CMAKE_EXE_LINKER_FLAGS} -static -static-libgcc -static-libstdc++")
|
||||
endif()
|
||||
endif() # JPEGXL_STATIC
|
||||
|
||||
# Threads
|
||||
set(THREADS_PREFER_PTHREAD_FLAG YES)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
# These settings are important to drive check_cxx_source_compiles
|
||||
# See CMP0067 (min cmake version is 3.10 anyway)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED YES)
|
||||
|
||||
# Atomics
|
||||
find_package(Atomics REQUIRED)
|
||||
|
||||
if(JPEGXL_STATIC)
|
||||
if (MINGW)
|
||||
# In MINGW libstdc++ uses pthreads directly. When building statically a
|
||||
# program (regardless of whether the source code uses pthread or not) the
|
||||
# toolchain will add stdc++ and pthread to the linking step but stdc++ will
|
||||
# be linked statically while pthread will be linked dynamically.
|
||||
# To avoid this and have pthread statically linked with need to pass it in
|
||||
# the command line with "-Wl,-Bstatic -lpthread -Wl,-Bdynamic" but the
|
||||
# linker will discard it if not used by anything else up to that point in
|
||||
# the linker command line. If the program or any dependency don't use
|
||||
# pthread directly -lpthread is discarded and libstdc++ (added by the
|
||||
# toolchain later) will then use the dynamic version. For this we also need
|
||||
# to pass -lstdc++ explicitly before -lpthread. For pure C programs -lstdc++
|
||||
# will be discarded anyway.
|
||||
# This adds these flags as dependencies for *all* targets. Adding this to
|
||||
# CMAKE_EXE_LINKER_FLAGS instead would cause them to be included before any
|
||||
# object files and therefore discarded. This should be set in the
|
||||
# INTERFACE_LINK_LIBRARIES of Threads::Threads but some third_part targets
|
||||
# don't depend on it.
|
||||
link_libraries(-Wl,-Bstatic -lstdc++ -lpthread -Wl,-Bdynamic)
|
||||
elseif(CMAKE_USE_PTHREADS_INIT)
|
||||
# "whole-archive" is not supported on OSX.
|
||||
if (NOT APPLE)
|
||||
# Set pthreads as a whole-archive, otherwise weak symbols in the static
|
||||
# libraries will discard pthreads symbols leading to segmentation fault at
|
||||
# runtime.
|
||||
message(STATUS "Using -lpthread as --whole-archive")
|
||||
set_target_properties(Threads::Threads PROPERTIES
|
||||
INTERFACE_LINK_LIBRARIES
|
||||
"-Wl,--whole-archive;-lpthread;-Wl,--no-whole-archive")
|
||||
endif()
|
||||
endif()
|
||||
endif() # JPEGXL_STATIC
|
||||
|
||||
if (JPEGXL_EMSCRIPTEN)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pthread")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread")
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -pthread")
|
||||
endif()
|
||||
|
||||
if (CXX_MACRO_PREFIX_MAP)
|
||||
add_compile_options(-fmacro-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=.)
|
||||
endif()
|
||||
|
||||
if (CXX_NO_RTTI_SUPPORTED)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti")
|
||||
endif()
|
||||
|
||||
if (MSVC)
|
||||
# TODO(janwas): add flags
|
||||
else ()
|
||||
|
||||
# Global compiler flags for all targets here and in subdirectories.
|
||||
add_definitions(
|
||||
# Avoid changing the binary based on the current time and date.
|
||||
-D__DATE__="redacted"
|
||||
-D__TIMESTAMP__="redacted"
|
||||
-D__TIME__="redacted"
|
||||
)
|
||||
|
||||
# Avoid log spam from fopen etc.
|
||||
if(MSVC)
|
||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
||||
endif()
|
||||
|
||||
# TODO(eustas): JXL currently compiles, but does not pass tests...
|
||||
if (NOT JXL_HWY_DISABLED_TARGETS_FORCED AND NOT JPEGXL_ENABLE_SIZELESS_VECTORS)
|
||||
add_definitions(-DHWY_DISABLED_TARGETS=\(HWY_SVE|HWY_SVE2|HWY_SVE_256|HWY_SVE2_128|HWY_RVV\))
|
||||
message("Warning: HWY_SVE, HWY_SVE2, HWY_SVE_256, HWY_SVE2_128 and HWY_RVV CPU targets are disabled")
|
||||
endif()
|
||||
|
||||
# In CMake before 3.12 it is problematic to pass repeated flags like -Xclang.
|
||||
# For this reason we place them in CMAKE_CXX_FLAGS instead.
|
||||
# See https://gitlab.kitware.com/cmake/cmake/issues/15826
|
||||
|
||||
# Machine flags.
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -funwind-tables")
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -mrelax-all")
|
||||
endif()
|
||||
if (CXX_CONSTRUCTOR_ALIASES_SUPPORTED)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -mconstructor-aliases")
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
# Not supported by clang-cl, but frame pointers are default on Windows
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer")
|
||||
endif()
|
||||
|
||||
# CPU flags - remove once we have NEON dynamic dispatch
|
||||
|
||||
# TODO(janwas): this also matches M1, but only ARMv7 is intended/needed.
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm")
|
||||
if(JPEGXL_FORCE_NEON)
|
||||
# GCC requires these flags, otherwise __ARM_NEON is undefined.
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \
|
||||
-mfpu=neon-vfpv4 -mfloat-abi=hard")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Force build with optimizations in release mode.
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2")
|
||||
|
||||
add_compile_options(
|
||||
# Ignore this to allow redefining __DATE__ and others.
|
||||
-Wno-builtin-macro-redefined
|
||||
|
||||
# Global warning settings.
|
||||
-Wall
|
||||
)
|
||||
|
||||
if (JPEGXL_WARNINGS_AS_ERRORS)
|
||||
add_compile_options(-Werror)
|
||||
endif ()
|
||||
endif () # !MSVC
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
# Separately build/configure testing frameworks and other third_party libraries
|
||||
# to allow disabling tests in those libraries.
|
||||
set(BUILD_TESTING OFF) # for SDL
|
||||
#include(third_party/testing.cmake)
|
||||
add_subdirectory(third_party)
|
||||
# Copy the JXL license file to the output build directory.
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/LICENSE"
|
||||
${PROJECT_BINARY_DIR}/LICENSE.jpeg-xl COPYONLY)
|
||||
|
||||
# Enable tests regardless of where they are defined.
|
||||
#enable_testing()
|
||||
#include(CTest)
|
||||
# Specify default location of `testdata`:
|
||||
if(NOT DEFINED JPEGXL_TEST_DATA_PATH)
|
||||
set(JPEGXL_TEST_DATA_PATH "${PROJECT_SOURCE_DIR}/testdata")
|
||||
endif()
|
||||
|
||||
# Libraries.
|
||||
add_subdirectory(lib)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
# Script to run tests over the source code in bash.
|
||||
find_program (BASH_PROGRAM bash)
|
||||
if(BASH_PROGRAM)
|
||||
add_test(
|
||||
NAME bash_test
|
||||
COMMAND ${BASH_PROGRAM} ${CMAKE_CURRENT_SOURCE_DIR}/bash_test.sh)
|
||||
endif()
|
||||
endif() # BUILD_TESTING
|
||||
|
||||
# Documentation generated by Doxygen
|
||||
if(JPEGXL_ENABLE_DOXYGEN)
|
||||
find_package(Doxygen)
|
||||
if(DOXYGEN_FOUND)
|
||||
set(DOXYGEN_GENERATE_HTML "YES")
|
||||
set(DOXYGEN_GENERATE_XML "YES")
|
||||
set(DOXYGEN_STRIP_FROM_PATH "${CMAKE_CURRENT_SOURCE_DIR}/lib/include")
|
||||
if(JPEGXL_WARNINGS_AS_ERRORS)
|
||||
set(DOXYGEN_WARN_AS_ERROR "YES")
|
||||
endif()
|
||||
set(DOXYGEN_QUIET "YES")
|
||||
doxygen_add_docs(doc
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/lib/include"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/doc/api.txt"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
|
||||
COMMENT "Generating C API documentation")
|
||||
|
||||
# Add sphinx doc build step for readthedocs.io (requires doxygen too).
|
||||
find_program(SPHINX_BUILD_PROGRAM sphinx-build)
|
||||
if(SPHINX_BUILD_PROGRAM)
|
||||
add_custom_command(
|
||||
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/rtd/nonexistent"
|
||||
COMMENT "Generating readthedocs.io output on ${CMAKE_CURRENT_BINARY_DIR}/rtd"
|
||||
COMMAND ${SPHINX_BUILD_PROGRAM} -q -W -b html -j auto
|
||||
${CMAKE_SOURCE_DIR}/doc/sphinx
|
||||
${CMAKE_CURRENT_BINARY_DIR}/rtd
|
||||
DEPENDS doc
|
||||
)
|
||||
# This command runs the documentation generation every time since the output
|
||||
# target file doesn't exist.
|
||||
add_custom_target(rtd-html
|
||||
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/rtd/nonexistent
|
||||
)
|
||||
else() # SPHINX_BUILD_PROGRAM\
|
||||
message(WARNING "sphinx-build not found, skipping rtd documentation")
|
||||
endif() # SPHINX_BUILD_PROGRAM
|
||||
|
||||
else()
|
||||
# Create a "doc" target for compatibility since "doc" is not otherwise added to
|
||||
# the build when doxygen is not installed.
|
||||
add_custom_target(doc false
|
||||
COMMENT "Error: Can't generate doc since Doxygen not installed.")
|
||||
endif() # DOXYGEN_FOUND
|
||||
endif() # JPEGXL_ENABLE_DOXYGEN
|
||||
|
||||
if(JPEGXL_ENABLE_MANPAGES)
|
||||
find_program(ASCIIDOC a2x)
|
||||
if(ASCIIDOC)
|
||||
file(STRINGS "${ASCIIDOC}" ASCIIDOC_SHEBANG LIMIT_COUNT 1)
|
||||
if(ASCIIDOC_SHEBANG MATCHES "/sh|/bash")
|
||||
set(ASCIIDOC_PY_FOUND ON)
|
||||
# Run the program directly and set ASCIIDOC as empty.
|
||||
set(ASCIIDOC_PY "${ASCIIDOC}")
|
||||
set(ASCIIDOC "")
|
||||
elseif(ASCIIDOC_SHEBANG MATCHES "python2")
|
||||
find_package(Python2 COMPONENTS Interpreter)
|
||||
set(ASCIIDOC_PY_FOUND "${Python2_Interpreter_FOUND}")
|
||||
set(ASCIIDOC_PY Python2::Interpreter)
|
||||
elseif(ASCIIDOC_SHEBANG MATCHES "python3")
|
||||
find_package(Python3 COMPONENTS Interpreter)
|
||||
set(ASCIIDOC_PY_FOUND "${Python3_Interpreter_FOUND}")
|
||||
set(ASCIIDOC_PY Python3::Interpreter)
|
||||
else()
|
||||
find_package(Python COMPONENTS Interpreter QUIET)
|
||||
if(NOT Python_Interpreter_FOUND)
|
||||
find_program(ASCIIDOC_PY python)
|
||||
if(ASCIIDOC_PY)
|
||||
set(ASCIIDOC_PY_FOUND ON)
|
||||
endif()
|
||||
else()
|
||||
set(ASCIIDOC_PY_FOUND "${Python_Interpreter_FOUND}")
|
||||
set(ASCIIDOC_PY Python::Interpreter)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (ASCIIDOC_PY_FOUND)
|
||||
set(MANPAGE_FILES "")
|
||||
set(MANPAGES "")
|
||||
foreach(PAGE IN ITEMS cjxl djxl)
|
||||
# Invoking the Python interpreter ourselves instead of running the a2x binary
|
||||
# directly is necessary on MSYS2, otherwise it is run through cmd.exe which
|
||||
# does not recognize it.
|
||||
add_custom_command(
|
||||
OUTPUT "${PAGE}.1"
|
||||
COMMAND "${ASCIIDOC_PY}"
|
||||
ARGS ${ASCIIDOC}
|
||||
--format manpage --destination-dir="${CMAKE_CURRENT_BINARY_DIR}"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/doc/man/${PAGE}.txt"
|
||||
MAIN_DEPENDENCY "${CMAKE_CURRENT_SOURCE_DIR}/doc/man/${PAGE}.txt")
|
||||
list(APPEND MANPAGE_FILES "${CMAKE_CURRENT_BINARY_DIR}/${PAGE}.1")
|
||||
list(APPEND MANPAGES "${PAGE}.1")
|
||||
endforeach()
|
||||
add_custom_target(manpages ALL DEPENDS ${MANPAGES})
|
||||
install(FILES ${MANPAGE_FILES} DESTINATION ${CMAKE_INSTALL_MANDIR}/man1)
|
||||
endif() # ASCIIDOC_PY_FOUND
|
||||
else()
|
||||
message(WARNING "asciidoc was not found, the man pages will not be installed.")
|
||||
endif() # ASCIIDOC
|
||||
endif() # JPEGXL_ENABLE_MANPAGES
|
||||
|
||||
# Example usage code.
|
||||
if (JPEGXL_ENABLE_EXAMPLES)
|
||||
include(examples/examples.cmake)
|
||||
endif ()
|
||||
|
||||
# Plugins for third-party software
|
||||
if (JPEGXL_ENABLE_PLUGINS)
|
||||
add_subdirectory(plugins)
|
||||
endif ()
|
||||
|
||||
# Binary tools
|
||||
add_subdirectory(tools)
|
||||
93
thirdparty/SDL3_image/external/libjxl/CODE_OF_CONDUCT.md
vendored
Normal file
93
thirdparty/SDL3_image/external/libjxl/CODE_OF_CONDUCT.md
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
# Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of
|
||||
experience, education, socio-economic status, nationality, personal appearance,
|
||||
race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, or to ban temporarily or permanently any
|
||||
contributor for other behaviors that they deem inappropriate, threatening,
|
||||
offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
This Code of Conduct also applies outside the project spaces when the Project
|
||||
Steward has a reasonable belief that an individual's behavior may have a
|
||||
negative impact on the project or its community.
|
||||
|
||||
## Conflict Resolution
|
||||
|
||||
We do not believe that all conflict is bad; healthy debate and disagreement
|
||||
often yield positive results. However, it is never okay to be disrespectful or
|
||||
to engage in behavior that violates the project’s code of conduct.
|
||||
|
||||
If you see someone violating the code of conduct, you are encouraged to address
|
||||
the behavior directly with those involved. Many issues can be resolved quickly
|
||||
and easily, and this gives people more control over the outcome of their
|
||||
dispute. If you are unable to resolve the matter for any reason, or if the
|
||||
behavior is threatening or harassing, report it. We are dedicated to providing
|
||||
an environment where participants feel welcome and safe.
|
||||
|
||||
Reports should be directed to Jyrki Alakuijala <jyrki@google.com>, the
|
||||
Project Steward(s) for JPEG XL. It is the Project Steward’s duty to
|
||||
receive and address reported violations of the code of conduct. They will then
|
||||
work with a committee consisting of representatives from the Open Source
|
||||
Programs Office and the Google Open Source Strategy team. If for any reason you
|
||||
are uncomfortable reaching out to the Project Steward, please email
|
||||
opensource@google.com.
|
||||
|
||||
We will investigate every complaint, but you may not receive a direct response.
|
||||
We will use our discretion in determining when and how to follow up on reported
|
||||
incidents, which may range from not taking action to permanent expulsion from
|
||||
the project and project-sponsored spaces. We will notify the accused of the
|
||||
report and provide them an opportunity to discuss it before any action is taken.
|
||||
The identity of the reporter will be omitted from the details of the report
|
||||
supplied to the accused. In potentially harmful situations, such as ongoing
|
||||
harassment or threats to anyone's safety, we may take action without notice.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the Contributor Covenant, version 1.4,
|
||||
available at
|
||||
https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
132
thirdparty/SDL3_image/external/libjxl/CONTRIBUTING.md
vendored
Normal file
132
thirdparty/SDL3_image/external/libjxl/CONTRIBUTING.md
vendored
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
# Contributing to libjxl
|
||||
|
||||
## Contributing with bug reports
|
||||
|
||||
For security-related issues please see [SECURITY.md](SECURITY.md).
|
||||
|
||||
We welcome suggestions, feature requests and bug reports. Before opening a new
|
||||
issue please take a look if there is already an existing one in the following
|
||||
link:
|
||||
|
||||
* https://github.com/libjxl/libjxl/issues
|
||||
|
||||
## Contributing with patches and Pull Requests
|
||||
|
||||
We'd love to accept your contributions to the JPEG XL Project. Please read
|
||||
through this section before sending a Pull Request.
|
||||
|
||||
### Contributor License Agreements
|
||||
|
||||
Our project is open source under the terms outlined in the [LICENSE](LICENSE)
|
||||
and [PATENTS](PATENTS) files. Before we can accept your contributions, even for
|
||||
small changes, there are just a few small guidelines you need to follow:
|
||||
|
||||
Please fill out either the individual or corporate Contributor License Agreement
|
||||
(CLA) with Google. JPEG XL Project is an an effort by multiple individuals and
|
||||
companies, including the initial contributors Cloudinary and Google, but Google
|
||||
is the legal entity in charge of receiving these CLA and relicensing this
|
||||
software:
|
||||
|
||||
* If you are an individual writing original source code and you're sure you
|
||||
own the intellectual property, then you'll need to sign an [individual
|
||||
CLA](https://code.google.com/legal/individual-cla-v1.0.html).
|
||||
|
||||
* If you work for a company that wants to allow you to contribute your work,
|
||||
then you'll need to sign a [corporate
|
||||
CLA](https://code.google.com/legal/corporate-cla-v1.0.html).
|
||||
|
||||
Follow either of the two links above to access the appropriate CLA and
|
||||
instructions for how to sign and return it. Once we receive it, we'll be able
|
||||
to accept your pull requests.
|
||||
|
||||
***NOTE***: Only original source code from you and other people that have signed
|
||||
the CLA can be accepted into the main repository.
|
||||
|
||||
### License
|
||||
|
||||
Contributions are licensed under the project's [LICENSE](LICENSE). Each new
|
||||
file must include the following header when possible, with comment style adapted
|
||||
to the language as needed:
|
||||
|
||||
```
|
||||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
```
|
||||
|
||||
### Code Reviews
|
||||
|
||||
All submissions, including submissions by project members, require review. We
|
||||
use GitHub pull requests for this purpose. Consult
|
||||
[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
|
||||
information on using pull requests.
|
||||
|
||||
### Contribution philosophy
|
||||
|
||||
* Prefer small changes, even if they don't implement a complete feature. Small
|
||||
changes are easier to review and can be submitted faster. Think about what's
|
||||
the smallest unit you can send that makes sense to review and submit in
|
||||
isolation. For example, new modules that are not yet used by the tools but
|
||||
have their own unittests are ok. If you have unrelated changes that
|
||||
you discovered while working on something else, please send them in a
|
||||
different Pull Request. If your are refactoring code and changing
|
||||
functionality try to send the refactor first without any change in
|
||||
functionality. Reviewers may ask you to split a Pull Request and it is
|
||||
easier to create a smaller change from the beginning.
|
||||
|
||||
* Describe your commits. Add a meaningful description to your commit message, explain what you are changing if it is not trivially obvious, but more importantly explain *why* you are making those changes. For example "Fix
|
||||
build" is not a good commit message, describe what build and if it makes sense
|
||||
why is this fixing it or why was it failing without this. It is very likely
|
||||
that people far in the future without any context you have right now will be
|
||||
looking at your commit trying to figure out why was the change introduced. If
|
||||
related to an issue in this or another repository include a link to it.
|
||||
|
||||
* Code Style: We follow the [Google C++ Coding
|
||||
Style](https://google.github.io/styleguide/cppguide.html). A
|
||||
[clang-format](https://clang.llvm.org/docs/ClangFormat.html) configuration
|
||||
file is available to automatically format your code, you can invoke it with
|
||||
the `./ci.sh lint` helper tool.
|
||||
|
||||
* Testing: Test your change and explain in the commit message *how* your
|
||||
commit was tested. For example adding unittests or in some cases just testing
|
||||
with the existing ones is enough. In any case, mention what testing was
|
||||
performed so reviewers can evaluate whether that's enough testing. In many
|
||||
cases, testing that the Continuous Integration workflow passes is enough.
|
||||
|
||||
* Make one commit per Pull Request / review, unless there's a good reason not
|
||||
to. If you have multiple changes send multiple Pull Requests and each one can
|
||||
have its own review.
|
||||
|
||||
* When addressing comments from reviewers prefer to squash or fixup your
|
||||
edits and force-push your commit. When merging changes into the repository we
|
||||
don't want to include the history of code review back and forth changes or
|
||||
typos. Reviewers can click on the "force-pushed" automatic comment on a Pull
|
||||
Request to see the changes between versions. We use "Rebase and merge" policy
|
||||
to keep a linear git history which is easier to reason about.
|
||||
|
||||
* Your change must pass the build and test workflows. There's a `ci.sh` script
|
||||
to help building and testing these configurations. See [building and
|
||||
testing](doc/building_and_testing.md) for more details.
|
||||
|
||||
### Contributing checklist.
|
||||
|
||||
* Sign the CLA (only needed once per user, see above).
|
||||
|
||||
* AUTHORS: If this is your first contribution, add your name or your
|
||||
company name to the [AUTHORS](AUTHORS) file for copyright tracking purposes.
|
||||
|
||||
* Style guide. Check `./ci.sh lint`.
|
||||
|
||||
* Meaningful commit description: What and *why*, links to issues, testing
|
||||
procedure.
|
||||
|
||||
* Squashed multiple edits into a single commit.
|
||||
|
||||
* Upload your changes to your fork and [create a Pull
|
||||
Request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request).
|
||||
|
||||
# Community Guidelines
|
||||
|
||||
This project follows [Google's Open Source Community
|
||||
Guidelines](https://opensource.google.com/conduct/).
|
||||
23
thirdparty/SDL3_image/external/libjxl/CONTRIBUTORS
vendored
Normal file
23
thirdparty/SDL3_image/external/libjxl/CONTRIBUTORS
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# This files lists individuals who made significant contributions to the JPEG XL
|
||||
# code base, such as design, adding features, performing experiments, ...
|
||||
# Small changes such as a small bugfix or fixing spelling errors are not
|
||||
# included. If you'd like to be included in this file thanks to a significant
|
||||
# contribution, feel free to send a pull request changing this file.
|
||||
Alex Deymo
|
||||
Alexander Rhatushnyak
|
||||
Evgenii Kliuchnikov
|
||||
Iulia-Maria Comșa
|
||||
Jan Wassenberg
|
||||
Jon Sneyers
|
||||
Jyrki Alakuijala
|
||||
Krzysztof Potempa
|
||||
Lode Vandevenne
|
||||
Luca Versari
|
||||
Martin Bruse
|
||||
Moritz Firsching
|
||||
Renata Khasanova
|
||||
Robert Obryk
|
||||
Sami Boukortt
|
||||
Sebastian Gomez-Gonzalez
|
||||
Thomas Fischbacher
|
||||
Zoltan Szabadka
|
||||
27
thirdparty/SDL3_image/external/libjxl/LICENSE
vendored
Normal file
27
thirdparty/SDL3_image/external/libjxl/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) the JPEG XL Project Authors.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
22
thirdparty/SDL3_image/external/libjxl/PATENTS
vendored
Normal file
22
thirdparty/SDL3_image/external/libjxl/PATENTS
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
Additional IP Rights Grant (Patents)
|
||||
|
||||
"This implementation" means the copyrightable works distributed by
|
||||
Google as part of the JPEG XL project.
|
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
||||
patent license to make, have made, use, offer to sell, sell, import,
|
||||
transfer and otherwise run, modify and propagate the contents of this
|
||||
implementation of JPEG XL, where such license applies only to those patent
|
||||
claims, both currently owned or controlled by Google and acquired in
|
||||
the future, licensable by Google that are necessarily infringed by this
|
||||
implementation of JPEG XL. This grant does not include claims that would be
|
||||
infringed only as a consequence of further modification of this
|
||||
implementation. If you or your agent or exclusive licensee institute or
|
||||
order or agree to the institution of patent litigation against any
|
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
||||
that this implementation of JPEG XL or any code incorporated within this
|
||||
implementation of JPEG XL constitutes direct or contributory patent
|
||||
infringement, or inducement of patent infringement, then any patent
|
||||
rights granted to you under this License for this implementation of JPEG XL
|
||||
shall terminate as of the date such litigation is filed.
|
||||
20
thirdparty/SDL3_image/external/libjxl/README.Haiku.md
vendored
Normal file
20
thirdparty/SDL3_image/external/libjxl/README.Haiku.md
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
## Disclaimer
|
||||
|
||||
Haiku builds are not officially supported, i.e. the build might not work at all,
|
||||
some tests may fail and some sub-projects are excluded from build.
|
||||
|
||||
This manual outlines Haiku-specific setup. For general building and testing
|
||||
instructions see "[README](README.md)" and
|
||||
"[Building and Testing changes](doc/building_and_testing.md)".
|
||||
|
||||
## Dependencies
|
||||
|
||||
```shell
|
||||
pkgman install llvm9_clang ninja cmake doxygen libjpeg_turbo_devel giflib_devel
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
```shell
|
||||
TEST_STACK_LIMIT=none CMAKE_FLAGS="-I/boot/system/develop/tools/lib/gcc/x86_64-unknown-haiku/8.3.0/include/c++ -I/boot/system/develop/tools/lib/gcc/x86_64-unknown-haiku/8.3.0/include/c++/x86_64-unknown-haiku" CMAKE_SHARED_LINKER_FLAGS="-shared -Xlinker -soname=libjpegxl.so -lpthread" ./ci.sh opt
|
||||
```
|
||||
41
thirdparty/SDL3_image/external/libjxl/README.OSX.md
vendored
Normal file
41
thirdparty/SDL3_image/external/libjxl/README.OSX.md
vendored
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
## Disclaimer
|
||||
|
||||
OSX builds have "best effort" support, i.e. build might not work at all, some
|
||||
tests may fail and some sub-projects are excluded from build.
|
||||
|
||||
This manual outlines OSX specific setup. For general building and testing
|
||||
instructions see "[README](README.md)" and
|
||||
"[Building and Testing changes](doc/building_and_testing.md)".
|
||||
|
||||
[Homebrew](https://brew.sh/) is a popular package manager. JPEG XL library and
|
||||
binaries could be installed using it:
|
||||
|
||||
```bash
|
||||
brew install jpeg-xl
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
Make sure that `brew doctor` does not report serious problems and up-to-date
|
||||
version of XCode is installed.
|
||||
|
||||
Installing (actually, building) `clang` might take a couple hours.
|
||||
|
||||
```bash
|
||||
brew install llvm
|
||||
```
|
||||
|
||||
```bash
|
||||
brew install coreutils cmake giflib jpeg-turbo libpng ninja zlib
|
||||
```
|
||||
|
||||
Before building the project check that `which clang` is
|
||||
`/usr/local/opt/llvm/bin/clang`, not the one provided by XCode. If not, update
|
||||
`PATH` environment variable.
|
||||
|
||||
Also, setting `CMAKE_PREFIX_PATH` might be necessary for correct include paths
|
||||
resolving, e.g.:
|
||||
|
||||
```bash
|
||||
export CMAKE_PREFIX_PATH=`brew --prefix giflib`:`brew --prefix jpeg-turbo`:`brew --prefix libpng`:`brew --prefix zlib`
|
||||
```
|
||||
197
thirdparty/SDL3_image/external/libjxl/README.md
vendored
Normal file
197
thirdparty/SDL3_image/external/libjxl/README.md
vendored
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
# JPEG XL reference implementation
|
||||
|
||||
[](
|
||||
https://github.com/libjxl/libjxl/actions/workflows/build_test.yml)
|
||||
[](
|
||||
https://github.com/libjxl/libjxl/actions/workflows/build_test_cross.yml)
|
||||
[](
|
||||
https://github.com/libjxl/libjxl/actions/workflows/conformance.yml)
|
||||
[](
|
||||
https://github.com/libjxl/libjxl/actions/workflows/fuzz.yml)
|
||||
[](
|
||||
https://github.com/libjxl/libjxl/actions/workflows/release.yaml)
|
||||
[](
|
||||
https://libjxl.readthedocs.io/en/latest/?badge=latest)
|
||||
[](
|
||||
https://codecov.io/gh/libjxl/libjxl)
|
||||
|
||||
<img src="doc/jxl.svg" width="100" align="right" alt="JXL logo">
|
||||
|
||||
This repository contains a reference implementation of JPEG XL (encoder and
|
||||
decoder), called `libjxl`. This software library is
|
||||
[used by many applications that support JPEG XL](doc/software_support.md).
|
||||
|
||||
JPEG XL is in the final stages of standardization and its codestream and file format
|
||||
are frozen.
|
||||
|
||||
The library API, command line options, and tools in this repository are subject
|
||||
to change, however files encoded with `cjxl` conform to the JPEG XL format
|
||||
specification and can be decoded with current and future `djxl` decoders or
|
||||
`libjxl` decoding library.
|
||||
|
||||
## Quick start guide
|
||||
|
||||
For more details and other workflows see the "Advanced guide" below.
|
||||
|
||||
### Checking out the code
|
||||
|
||||
```bash
|
||||
git clone https://github.com/libjxl/libjxl.git --recursive --shallow-submodules
|
||||
```
|
||||
|
||||
This repository uses git submodules to handle some third party dependencies
|
||||
under `third_party`, that's why is important to pass `--recursive`. If you
|
||||
didn't check out with `--recursive`, or any submodule has changed, run:
|
||||
|
||||
```bash
|
||||
git submodule update --init --recursive --depth 1 --recommend-shallow
|
||||
```
|
||||
|
||||
The `--shallow-submodules` and `--depth 1 --recommend-shallow` options create
|
||||
shallow clones which only downloads the commits requested, and is all that is
|
||||
needed to build `libjxl`. Should full clones be necessary, you could always run:
|
||||
|
||||
```bash
|
||||
git submodule foreach git fetch --unshallow
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
which pulls the rest of the commits in the submodules.
|
||||
|
||||
Important: If you downloaded a zip file or tarball from the web interface you
|
||||
won't get the needed submodules and the code will not compile. You can download
|
||||
these external dependencies from source running `./deps.sh`. The git workflow
|
||||
described above is recommended instead.
|
||||
|
||||
### Installing dependencies
|
||||
|
||||
Required dependencies for compiling the code, in a Debian/Ubuntu based
|
||||
distribution run:
|
||||
|
||||
```bash
|
||||
sudo apt install cmake pkg-config libbrotli-dev
|
||||
```
|
||||
|
||||
Optional dependencies for supporting other formats in the `cjxl`/`djxl` tools,
|
||||
in a Debian/Ubuntu based distribution run:
|
||||
|
||||
```bash
|
||||
sudo apt install libgif-dev libjpeg-dev libopenexr-dev libpng-dev libwebp-dev
|
||||
```
|
||||
|
||||
We recommend using a recent Clang compiler (version 7 or newer), for that
|
||||
install clang and set `CC` and `CXX` variables.
|
||||
|
||||
```bash
|
||||
sudo apt install clang
|
||||
export CC=clang CXX=clang++
|
||||
```
|
||||
|
||||
### Building
|
||||
|
||||
```bash
|
||||
cd libjxl
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF ..
|
||||
cmake --build . -- -j$(nproc)
|
||||
```
|
||||
|
||||
The encoder/decoder tools will be available in the `build/tools` directory.
|
||||
|
||||
### <a name="installing"></a> Installing
|
||||
|
||||
```bash
|
||||
sudo cmake --install .
|
||||
```
|
||||
|
||||
### Basic encoder/decoder
|
||||
|
||||
To encode a source image to JPEG XL with default settings:
|
||||
|
||||
```bash
|
||||
build/tools/cjxl input.png output.jxl
|
||||
```
|
||||
|
||||
For more settings run `build/tools/cjxl --help` or for a full list of options
|
||||
run `build/tools/cjxl -v -v --help`.
|
||||
|
||||
To decode a JPEG XL file run:
|
||||
|
||||
```bash
|
||||
build/tools/djxl input.jxl output.png
|
||||
```
|
||||
|
||||
When possible `cjxl`/`djxl` are able to read/write the following
|
||||
image formats: .exr, .gif, .jpeg/.jpg, .pfm, .pgm/.ppm, .pgx, .png.
|
||||
|
||||
### Benchmarking
|
||||
|
||||
For speed benchmarks on single images in single or multi-threaded decoding
|
||||
`djxl` can print decoding speed information. See `djxl --help` for details
|
||||
on the decoding options and note that the output image is optional for
|
||||
benchmarking purposes.
|
||||
|
||||
For more comprehensive benchmarking options, see the
|
||||
[benchmarking guide](doc/benchmarking.md).
|
||||
|
||||
## Advanced guide
|
||||
|
||||
### Building with Docker
|
||||
|
||||
We build a common environment based on Debian/Ubuntu using Docker. Other
|
||||
systems may have different combinations of versions and dependencies that
|
||||
have not been tested and may not work. For those cases we recommend using the
|
||||
Docker container as explained in the
|
||||
[step by step guide](doc/developing_in_docker.md).
|
||||
|
||||
### Building JPEG XL for developers
|
||||
|
||||
For experienced developers, we provide build instructions for several other environments:
|
||||
|
||||
* [Building on Debian](doc/developing_in_debian.md)
|
||||
* Building on Windows with [vcpkg](doc/developing_in_windows_vcpkg.md) (Visual Studio 2019)
|
||||
* Building on Windows with [MSYS2](doc/developing_in_windows_msys.md)
|
||||
* [Cross Compiling for Windows with Crossroad](doc/developing_with_crossroad.md)
|
||||
|
||||
If you encounter any difficulties, please use Docker instead.
|
||||
|
||||
## License
|
||||
|
||||
This software is available under a 3-clause BSD license which can be found in
|
||||
the [LICENSE](LICENSE) file, with an "Additional IP Rights Grant" as outlined in
|
||||
the [PATENTS](PATENTS) file.
|
||||
|
||||
Please note that the PATENTS file only mentions Google since Google is the legal
|
||||
entity receiving the Contributor License Agreements (CLA) from all contributors
|
||||
to the JPEG XL Project, including the initial main contributors to the JPEG XL
|
||||
format: Cloudinary and Google.
|
||||
|
||||
## Additional documentation
|
||||
|
||||
### Codec description
|
||||
|
||||
* [JPEG XL Format Overview](doc/format_overview.md)
|
||||
* [Introductory paper](https://www.spiedigitallibrary.org/proceedings/Download?fullDOI=10.1117%2F12.2529237) (open-access)
|
||||
* [XL Overview](doc/xl_overview.md) - a brief introduction to the source code modules
|
||||
* [JPEG XL white paper](https://ds.jpeg.org/whitepapers/jpeg-xl-whitepaper.pdf)
|
||||
* [JPEG XL official website](https://jpeg.org/jpegxl)
|
||||
* [JPEG XL community website](https://jpegxl.info)
|
||||
|
||||
### Development process
|
||||
|
||||
* [More information on testing/build options](doc/building_and_testing.md)
|
||||
* [Git guide for JPEG XL](doc/developing_in_github.md) - for developers
|
||||
* [Fuzzing](doc/fuzzing.md) - for developers
|
||||
* [Building Web Assembly artifacts](doc/building_wasm.md)
|
||||
* [Test coverage on Codecov.io](https://app.codecov.io/gh/libjxl/libjxl) - for
|
||||
developers
|
||||
* [libjxl documentation on readthedocs.io](https://libjxl.readthedocs.io/)
|
||||
|
||||
### Contact
|
||||
|
||||
If you encounter a bug or other issue with the software, please open an Issue here.
|
||||
|
||||
There is a [subreddit about JPEG XL](https://www.reddit.com/r/jpegxl/), and
|
||||
informal chatting with developers and early adopters of `libjxl` can be done on the
|
||||
[JPEG XL Discord server](https://discord.gg/DqkQgDRTFu).
|
||||
73
thirdparty/SDL3_image/external/libjxl/SECURITY.md
vendored
Normal file
73
thirdparty/SDL3_image/external/libjxl/SECURITY.md
vendored
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# Security and Vulnerability Policy for libjxl
|
||||
|
||||
## TL;DR:
|
||||
|
||||
CPE prefix: `cpe:2.3:a:libjxl_project:libjxl`
|
||||
|
||||
To report a security issue, please email libjxl-security@google.com.
|
||||
|
||||
Include in your email a description of the issue, the steps you took to create
|
||||
the issue, affected versions, and if known, mitigations for the issue. Our
|
||||
vulnerability management team will acknowledge receiving your email within 3
|
||||
working days.
|
||||
|
||||
This project follows a 90 day disclosure timeline.
|
||||
|
||||
For all other bugs, where there are no security implications about disclosing
|
||||
the unpatched bug, open a [new issue](https://github.com/libjxl/libjxl/issues)
|
||||
checking first for existing similar issues. If in doubt about the security
|
||||
impact of a bug you discovered, email first.
|
||||
|
||||
## Policy overview
|
||||
|
||||
libjxl's Security Policy is based on the [Google Open Source program
|
||||
guidelines](https://github.com/google/oss-vulnerability-guide) for coordinated
|
||||
vulnerability disclosure.
|
||||
|
||||
Early versions of `libjxl` had a different security policy that didn't provide
|
||||
security and vulnerability disclosure support. Versions up to and including
|
||||
0.3.7 are not covered and won't receive any security advisory.
|
||||
|
||||
Only released versions, starting from version 0.5, are covered by this policy.
|
||||
Development branches, arbitrary commits from `main` branch or even releases with
|
||||
backported features externally patched on top are not covered. Only those
|
||||
versions with a release tag in `libjxl`'s repository are covered, starting from
|
||||
version 0.5.
|
||||
|
||||
## What's a "Security bug"
|
||||
|
||||
A security bug is a bug that can potentially be exploited to let an attacker
|
||||
gain unauthorized access or privileges such as disclosing information or
|
||||
arbitrary code execution. Not all fuzzer-found bugs and not all assert()
|
||||
failures are considered security bugs in libjxl. For a detailed explanation and
|
||||
examples see our [Security Vulnerabilities Playbook](doc/vuln_playbook.md).
|
||||
|
||||
## What to expect
|
||||
|
||||
To report a security issue, please email libjxl-security@google.com with all the
|
||||
details about the bug you encountered.
|
||||
|
||||
* Include a description of the issue, steps to reproduce, etc. Compiler
|
||||
versions, flags, exact version used and even CPU are often relevant given our
|
||||
usage of SIMD and run-time dispatch of SIMD instructions.
|
||||
|
||||
* A member of our security team will reply to you within 3 business days. Note
|
||||
that business days are different in different countries.
|
||||
|
||||
* We will evaluate the issue and we may require more input from your side to
|
||||
reproduce it.
|
||||
|
||||
* If the issue fits in the description of a security bug, we will issue a
|
||||
CVE, publish a fix and make a new minor or patch release with it. There is
|
||||
a maximum of 90 day disclosure timeline, we ask you to not publish the
|
||||
details before the 90 day deadline or the release date (whichever comes
|
||||
first).
|
||||
|
||||
* In the case that we publish a CVE we will credit the external researcher who
|
||||
reported the issue. When reporting security issues please let us know if you
|
||||
need to include specific information while doing so, like for example a
|
||||
company affiliation.
|
||||
|
||||
Our security team follows the [Security Vulnerabilities
|
||||
Playbook](doc/vuln_playbook.md). For more details about the process and policies
|
||||
please take a look at it.
|
||||
42
thirdparty/SDL3_image/external/libjxl/android/jxl/jxl_export.h
vendored
Normal file
42
thirdparty/SDL3_image/external/libjxl/android/jxl/jxl_export.h
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
|
||||
#ifndef JXL_EXPORT_H
|
||||
#define JXL_EXPORT_H
|
||||
|
||||
#ifdef JXL_STATIC_DEFINE
|
||||
# define JXL_EXPORT
|
||||
# define JXL_NO_EXPORT
|
||||
#else
|
||||
# ifndef JXL_EXPORT
|
||||
# ifdef JXL_INTERNAL_LIBRARY_BUILD
|
||||
/* We are building this library */
|
||||
# define JXL_EXPORT __attribute__((visibility("default")))
|
||||
# else
|
||||
/* We are using this library */
|
||||
# define JXL_EXPORT __attribute__((visibility("default")))
|
||||
# endif
|
||||
# endif
|
||||
|
||||
# ifndef JXL_NO_EXPORT
|
||||
# define JXL_NO_EXPORT __attribute__((visibility("hidden")))
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef JXL_DEPRECATED
|
||||
# define JXL_DEPRECATED __attribute__ ((__deprecated__))
|
||||
#endif
|
||||
|
||||
#ifndef JXL_DEPRECATED_EXPORT
|
||||
# define JXL_DEPRECATED_EXPORT JXL_EXPORT JXL_DEPRECATED
|
||||
#endif
|
||||
|
||||
#ifndef JXL_DEPRECATED_NO_EXPORT
|
||||
# define JXL_DEPRECATED_NO_EXPORT JXL_NO_EXPORT JXL_DEPRECATED
|
||||
#endif
|
||||
|
||||
#if 0 /* DEFINE_NO_DEPRECATED */
|
||||
# ifndef JXL_NO_DEPRECATED
|
||||
# define JXL_NO_DEPRECATED
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#endif /* JXL_EXPORT_H */
|
||||
39
thirdparty/SDL3_image/external/libjxl/android/jxl/version.h
vendored
Normal file
39
thirdparty/SDL3_image/external/libjxl/android/jxl/version.h
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/* Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style
|
||||
* license that can be found in the LICENSE file.
|
||||
*/
|
||||
|
||||
/** @addtogroup libjxl_common
|
||||
* @{
|
||||
* @file version.h
|
||||
* @brief libjxl version information
|
||||
*/
|
||||
|
||||
#ifndef JXL_VERSION_H_
|
||||
#define JXL_VERSION_H_
|
||||
|
||||
#define JPEGXL_MAJOR_VERSION 0 ///< JPEG XL Major version
|
||||
#define JPEGXL_MINOR_VERSION 7 ///< JPEG XL Minor version
|
||||
#define JPEGXL_PATCH_VERSION 2 ///< JPEG XL Patch version
|
||||
|
||||
/** Can be used to conditionally compile code for a specific JXL version
|
||||
* @param[maj] major version
|
||||
* @param[min] minor version
|
||||
*
|
||||
* @code
|
||||
* #if JPEGXL_NUMERIC_VERSION < JPEGXL_COMPUTE_NUMERIC_VERSION(0,8,0)
|
||||
* // use old/deprecated api
|
||||
* #else
|
||||
* // use current api
|
||||
* #endif
|
||||
* @endcode
|
||||
*/
|
||||
#define JPEGXL_COMPUTE_NUMERIC_VERSION(major,minor,patch) ((major<<24) | (minor<<16) | (patch<<8) | 0)
|
||||
|
||||
/* Numeric representation of the version */
|
||||
#define JPEGXL_NUMERIC_VERSION JPEGXL_COMPUTE_NUMERIC_VERSION(JPEGXL_MAJOR_VERSION,JPEGXL_MINOR_VERSION,JPEGXL_PATCH_VERSION)
|
||||
|
||||
#endif /* JXL_VERSION_H_ */
|
||||
|
||||
/** @}*/
|
||||
314
thirdparty/SDL3_image/external/libjxl/bash_test.sh
vendored
Executable file
314
thirdparty/SDL3_image/external/libjxl/bash_test.sh
vendored
Executable file
|
|
@ -0,0 +1,314 @@
|
|||
#!/bin/bash
|
||||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
# Tests implemented in bash. These typically will run checks about the source
|
||||
# code rather than the compiled one.
|
||||
|
||||
MYDIR=$(dirname $(realpath "$0"))
|
||||
|
||||
set -u
|
||||
|
||||
test_includes() {
|
||||
local ret=0
|
||||
local f
|
||||
for f in $(git ls-files | grep -E '(\.cc|\.cpp|\.h)$'); do
|
||||
if [ ! -e "$f" ]; then
|
||||
continue
|
||||
fi
|
||||
# Check that the public files (in lib/include/ directory) don't use the full
|
||||
# path to the public header since users of the library will include the
|
||||
# library as: #include "jxl/foobar.h".
|
||||
if [[ "${f#lib/include/}" != "${f}" ]]; then
|
||||
if grep -i -H -n -E '#include\s*[<"]lib/include/jxl' "$f" >&2; then
|
||||
echo "Don't add \"include/\" to the include path of public headers." >&2
|
||||
ret=1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "${f#third_party/}" == "$f" ]]; then
|
||||
# $f is not in third_party/
|
||||
|
||||
# Check that local files don't use the full path to third_party/
|
||||
# directory since the installed versions will not have that path.
|
||||
# Add an exception for third_party/dirent.h.
|
||||
if grep -v -F 'third_party/dirent.h' "$f" | \
|
||||
grep -i -H -n -E '#include\s*[<"]third_party/' >&2 &&
|
||||
[[ $ret -eq 0 ]]; then
|
||||
cat >&2 <<EOF
|
||||
$f: Don't add third_party/ to the include path of third_party projects. This \
|
||||
makes it harder to use installed system libraries instead of the third_party/ \
|
||||
ones.
|
||||
EOF
|
||||
ret=1
|
||||
fi
|
||||
fi
|
||||
|
||||
done
|
||||
return ${ret}
|
||||
}
|
||||
|
||||
test_include_collision() {
|
||||
local ret=0
|
||||
local f
|
||||
for f in $(git ls-files | grep -E '^lib/include/'); do
|
||||
if [ ! -e "$f" ]; then
|
||||
continue
|
||||
fi
|
||||
local base=${f#lib/include/}
|
||||
if [[ -e "lib/${base}" ]]; then
|
||||
echo "$f: Name collision, both $f and lib/${base} exist." >&2
|
||||
ret=1
|
||||
fi
|
||||
done
|
||||
return ${ret}
|
||||
}
|
||||
|
||||
test_copyright() {
|
||||
local ret=0
|
||||
local f
|
||||
for f in $(
|
||||
git ls-files | grep -E \
|
||||
'(Dockerfile.*|\.c|\.cc|\.cpp|\.gni|\.h|\.java|\.sh|\.m|\.py|\.ui|\.yml)$'); do
|
||||
if [ ! -e "$f" ]; then
|
||||
continue
|
||||
fi
|
||||
if [[ "${f#third_party/}" == "$f" ]]; then
|
||||
# $f is not in third_party/
|
||||
if ! head -n 10 "$f" |
|
||||
grep -F 'Copyright (c) the JPEG XL Project Authors.' >/dev/null ; then
|
||||
echo "$f: Missing Copyright blob near the top of the file." >&2
|
||||
ret=1
|
||||
fi
|
||||
if ! head -n 10 "$f" |
|
||||
grep -F 'Use of this source code is governed by a BSD-style' \
|
||||
>/dev/null ; then
|
||||
echo "$f: Missing License blob near the top of the file." >&2
|
||||
ret=1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
return ${ret}
|
||||
}
|
||||
|
||||
# Check that we don't use "%zu" or "%zd" in format string for size_t.
|
||||
test_printf_size_t() {
|
||||
local ret=0
|
||||
if grep -n -E '%[0-9]*z[udx]' \
|
||||
$(git ls-files | grep -E '(\.c|\.cc|\.cpp|\.h)$'); then
|
||||
echo "Don't use '%zu' or '%zd' in a format string, instead use " \
|
||||
"'%\" PRIuS \"' or '%\" PRIdS \"'." >&2
|
||||
ret=1
|
||||
fi
|
||||
|
||||
if grep -n -E 'gmock\.h' \
|
||||
$(git ls-files | grep -E '(\.c|\.cc|\.cpp|\.h)$' | grep -v -F /test_utils.h); then
|
||||
echo "Don't include gmock directly, instead include 'test_utils.h'. " >&2
|
||||
ret=1
|
||||
fi
|
||||
|
||||
local f
|
||||
for f in $(git ls-files | grep -E "\.cc$" | xargs grep 'PRI[udx]S' |
|
||||
cut -f 1 -d : | uniq); do
|
||||
if [ ! -e "$f" ]; then
|
||||
continue
|
||||
fi
|
||||
if ! grep -F printf_macros.h "$f" >/dev/null; then
|
||||
echo "$f: Add lib/jxl/base/printf_macros.h for PRI.S, or use other " \
|
||||
"types for code outside lib/jxl library." >&2
|
||||
ret=1
|
||||
fi
|
||||
done
|
||||
|
||||
for f in $(git ls-files | grep -E "\.h$" | grep -v -E '(printf_macros\.h|test_utils\.h)' |
|
||||
xargs grep -n 'PRI[udx]S'); do
|
||||
# Having PRIuS / PRIdS in a header file means that printf_macros.h may
|
||||
# be included before a system header, in particular before gtest headers.
|
||||
# those may re-define PRIuS unconditionally causing a compile error.
|
||||
echo "$f: Don't use PRI.S in header files. Sorry."
|
||||
ret=1
|
||||
done
|
||||
|
||||
return ${ret}
|
||||
}
|
||||
|
||||
# Check that "dec_" code doesn't depend on "enc_" headers.
|
||||
test_dec_enc_deps() {
|
||||
local ret=0
|
||||
local f
|
||||
for f in $(git ls-files | grep -E '/dec_'); do
|
||||
if [ ! -e "$f" ]; then
|
||||
continue
|
||||
fi
|
||||
if [[ "${f#third_party/}" == "$f" ]]; then
|
||||
# $f is not in third_party/
|
||||
if grep -n -H -E "#include.*/enc_" "$f" >&2; then
|
||||
echo "$f: Don't include \"enc_*\" files from \"dec_*\" files." >&2
|
||||
ret=1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
return ${ret}
|
||||
}
|
||||
|
||||
# Check for git merge conflict markers.
|
||||
test_merge_conflict() {
|
||||
local ret=0
|
||||
TEXT_FILES='(\.cc|\.cpp|\.h|\.sh|\.m|\.py|\.md|\.txt|\.cmake)$'
|
||||
for f in $(git ls-files | grep -E "${TEXT_FILES}"); do
|
||||
if [ ! -e "$f" ]; then
|
||||
continue
|
||||
fi
|
||||
if grep -E '^<<<<<<< ' "$f"; then
|
||||
echo "$f: Found git merge conflict marker. Please resolve." >&2
|
||||
ret=1
|
||||
fi
|
||||
done
|
||||
return ${ret}
|
||||
}
|
||||
|
||||
# Check that the library and the package have the same version. This prevents
|
||||
# accidentally having them out of sync.
|
||||
get_version() {
|
||||
local varname=$1
|
||||
local line=$(grep -F "set(${varname} " lib/CMakeLists.txt | head -n 1)
|
||||
[[ -n "${line}" ]]
|
||||
line="${line#set(${varname} }"
|
||||
line="${line%)}"
|
||||
echo "${line}"
|
||||
}
|
||||
|
||||
test_version() {
|
||||
local major=$(get_version JPEGXL_MAJOR_VERSION)
|
||||
local minor=$(get_version JPEGXL_MINOR_VERSION)
|
||||
local patch=$(get_version JPEGXL_PATCH_VERSION)
|
||||
# Check that the version is not empty
|
||||
if [[ -z "${major}${minor}${patch}" ]]; then
|
||||
echo "Couldn't parse version from CMakeLists.txt" >&2
|
||||
return 1
|
||||
fi
|
||||
local pkg_version=$(head -n 1 debian/changelog)
|
||||
# Get only the part between the first "jpeg-xl (" and the following ")".
|
||||
pkg_version="${pkg_version#jpeg-xl (}"
|
||||
pkg_version="${pkg_version%%)*}"
|
||||
if [[ -z "${pkg_version}" ]]; then
|
||||
echo "Couldn't parse version from debian package" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
local lib_version="${major}.${minor}.${patch}"
|
||||
lib_version="${lib_version%.0}"
|
||||
if [[ "${pkg_version}" != "${lib_version}"* ]]; then
|
||||
echo "Debian package version (${pkg_version}) doesn't match library" \
|
||||
"version (${lib_version})." >&2
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Check that the SHA versions in deps.sh matches the git submodules.
|
||||
test_deps_version() {
|
||||
while IFS= read -r line; do
|
||||
if [[ "${line:0:10}" != "[submodule" ]]; then
|
||||
continue
|
||||
fi
|
||||
line="${line#[submodule \"}"
|
||||
line="${line%\"]}"
|
||||
local varname=$(tr '[:lower:]' '[:upper:]' <<< "${line}")
|
||||
varname="${varname/\//_}"
|
||||
if ! grep -F "${varname}=" deps.sh >/dev/null; then
|
||||
# Ignoring submodule not in deps.sh
|
||||
continue
|
||||
fi
|
||||
local deps_sha=$(grep -F "${varname}=" deps.sh | cut -f 2 -d '"')
|
||||
[[ -n "${deps_sha}" ]]
|
||||
local git_sha=$(git ls-tree -r HEAD "${line}" | cut -f 1 | cut -f 3 -d ' ')
|
||||
if [[ "${deps_sha}" != "${git_sha}" ]]; then
|
||||
cat >&2 <<EOF
|
||||
deps.sh: SHA for project ${line} is at ${deps_sha} but the git submodule is at
|
||||
${git_sha}. Please update deps.sh
|
||||
|
||||
If you did not intend to change the submodule's SHA value, it is possible that
|
||||
you accidentally included this change in your commit after a rebase or checkout
|
||||
without running "git submodule --init". To revert the submodule change run from
|
||||
the top checkout directory:
|
||||
|
||||
git -C ${line} checkout ${deps_sha}
|
||||
git commit --amend ${line}
|
||||
|
||||
EOF
|
||||
return 1
|
||||
fi
|
||||
done < .gitmodules
|
||||
}
|
||||
|
||||
# Make sure that all the Fields objects are fuzzed directly.
|
||||
test_fuzz_fields() {
|
||||
local ret=0
|
||||
# List all the classes of the form "ClassName : public Fields".
|
||||
# This doesn't catch class names that are too long to fit.
|
||||
local field_classes=$( git ls-files |
|
||||
grep -E '\.(cc|h)' | grep -v 'test\.cc$' |
|
||||
xargs grep -h -o -E '\b[^ ]+ : public Fields' | cut -f 1 -d ' ')
|
||||
local classname
|
||||
for classname in ${field_classes}; do
|
||||
if [ ! -e "$classname" ]; then
|
||||
continue
|
||||
fi
|
||||
if ! grep -E "\\b${classname}\\b" tools/fields_fuzzer.cc >/dev/null; then
|
||||
cat >&2 <<EOF
|
||||
tools/fields_fuzzer.cc: Class ${classname} not found in the fields_fuzzer.
|
||||
EOF
|
||||
ret=1
|
||||
fi
|
||||
done
|
||||
return $ret
|
||||
}
|
||||
|
||||
# Test that we don't use %n in C++ code to avoid using it in printf and scanf.
|
||||
# This test is not very precise but in cases where "module n" is needed we would
|
||||
# normally have "% n" instead of "%n". Using %n is not allowed in Android 10+.
|
||||
test_percent_n() {
|
||||
local ret=0
|
||||
local f
|
||||
for f in $(git ls-files | grep -E '(\.cc|\.cpp|\.h)$'); do
|
||||
if [ ! -e "$f" ]; then
|
||||
continue
|
||||
fi
|
||||
if grep -i -H -n -E '%h*n' "$f" >&2; then
|
||||
echo "Don't use \"%n\"." >&2
|
||||
ret=1
|
||||
fi
|
||||
done
|
||||
return ${ret}
|
||||
}
|
||||
|
||||
main() {
|
||||
local ret=0
|
||||
cd "${MYDIR}"
|
||||
|
||||
if ! git rev-parse >/dev/null 2>/dev/null; then
|
||||
echo "Not a git checkout, skipping bash_test"
|
||||
return 0
|
||||
fi
|
||||
|
||||
IFS=$'\n'
|
||||
for f in $(declare -F); do
|
||||
local test_name=$(echo "$f" | cut -f 3 -d ' ')
|
||||
# Runs all the local bash functions that start with "test_".
|
||||
if [[ "${test_name}" == test_* ]]; then
|
||||
echo "Test ${test_name}: Start"
|
||||
if ${test_name}; then
|
||||
echo "Test ${test_name}: PASS"
|
||||
else
|
||||
echo "Test ${test_name}: FAIL"
|
||||
ret=1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
return ${ret}
|
||||
}
|
||||
|
||||
main "$@"
|
||||
1520
thirdparty/SDL3_image/external/libjxl/ci.sh
vendored
Executable file
1520
thirdparty/SDL3_image/external/libjxl/ci.sh
vendored
Executable file
File diff suppressed because it is too large
Load diff
53
thirdparty/SDL3_image/external/libjxl/cmake/FindAtomics.cmake
vendored
Normal file
53
thirdparty/SDL3_image/external/libjxl/cmake/FindAtomics.cmake
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
# Original issue:
|
||||
# * https://gitlab.kitware.com/cmake/cmake/-/issues/23021#note_1098733
|
||||
#
|
||||
# For reference:
|
||||
# * https://gcc.gnu.org/wiki/Atomic/GCCMM
|
||||
#
|
||||
# riscv64 specific:
|
||||
# * https://lists.debian.org/debian-riscv/2022/01/msg00009.html
|
||||
#
|
||||
# ATOMICS_FOUND - system has c++ atomics
|
||||
# ATOMICS_LIBRARIES - libraries needed to use c++ atomics
|
||||
|
||||
include(CheckCXXSourceCompiles)
|
||||
|
||||
# RISC-V only has 32-bit and 64-bit atomic instructions. GCC is supposed
|
||||
# to convert smaller atomics to those larger ones via masking and
|
||||
# shifting like LLVM, but it’s a known bug that it does not. This means
|
||||
# anything that wants to use atomics on 1-byte or 2-byte types needs
|
||||
# -latomic, but not 4-byte or 8-byte (though it does no harm).
|
||||
set(atomic_code
|
||||
"
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
std::atomic<uint8_t> n8 (0); // riscv64
|
||||
std::atomic<uint64_t> n64 (0); // armel, mipsel, powerpc
|
||||
int main() {
|
||||
++n8;
|
||||
++n64;
|
||||
return 0;
|
||||
}")
|
||||
|
||||
check_cxx_source_compiles("${atomic_code}" ATOMICS_LOCK_FREE_INSTRUCTIONS)
|
||||
|
||||
if(ATOMICS_LOCK_FREE_INSTRUCTIONS)
|
||||
set(ATOMICS_FOUND TRUE)
|
||||
set(ATOMICS_LIBRARIES)
|
||||
else()
|
||||
set(CMAKE_REQUIRED_LIBRARIES "-latomic")
|
||||
check_cxx_source_compiles("${atomic_code}" ATOMICS_IN_LIBRARY)
|
||||
set(CMAKE_REQUIRED_LIBRARIES)
|
||||
if(ATOMICS_IN_LIBRARY)
|
||||
set(ATOMICS_LIBRARY atomic)
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Atomics DEFAULT_MSG ATOMICS_LIBRARY)
|
||||
set(ATOMICS_LIBRARIES ${ATOMICS_LIBRARY})
|
||||
unset(ATOMICS_LIBRARY)
|
||||
else()
|
||||
if(Atomics_FIND_REQUIRED)
|
||||
message(FATAL_ERROR "Neither lock free instructions nor -latomic found.")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
unset(atomic_code)
|
||||
85
thirdparty/SDL3_image/external/libjxl/cmake/FindBrotli.cmake
vendored
Normal file
85
thirdparty/SDL3_image/external/libjxl/cmake/FindBrotli.cmake
vendored
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
set(brlibs brotlicommon brotlienc brotlidec)
|
||||
|
||||
find_package(PkgConfig QUIET)
|
||||
if (PkgConfig_FOUND)
|
||||
foreach(brlib IN ITEMS ${brlibs})
|
||||
string(TOUPPER "${brlib}" BRPREFIX)
|
||||
pkg_check_modules("PC_${BRPREFIX}" lib${brlib})
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
find_path(BROTLI_INCLUDE_DIR
|
||||
NAMES brotli/decode.h
|
||||
HINTS ${PC_BROTLICOMMON_INCLUDEDIR} ${PC_BROTLICOMMON_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
foreach(brlib IN ITEMS ${brlibs})
|
||||
string(TOUPPER "${brlib}" BRPREFIX)
|
||||
find_library(${BRPREFIX}_LIBRARY
|
||||
NAMES ${${BRPREFIX}_NAMES} ${brlib}
|
||||
HINTS ${PC_${BRPREFIX}_LIBDIR} ${PC_${BRPREFIX}_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
if (${BRPREFIX}_LIBRARY AND NOT TARGET ${brlib})
|
||||
if(CMAKE_VERSION VERSION_LESS "3.13.5")
|
||||
add_library(${brlib} INTERFACE IMPORTED GLOBAL)
|
||||
set_property(TARGET ${brlib} PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${BROTLI_INCLUDE_DIR})
|
||||
target_link_libraries(${brlib} INTERFACE ${${BRPREFIX}_LIBRARY})
|
||||
set_property(TARGET ${brlib} PROPERTY INTERFACE_COMPILE_OPTIONS ${PC_${BRPREFIX}_CFLAGS_OTHER})
|
||||
|
||||
add_library(${brlib}-static INTERFACE IMPORTED GLOBAL)
|
||||
set_property(TARGET ${brlib}-static PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${BROTLI_INCLUDE_DIR})
|
||||
target_link_libraries(${brlib}-static INTERFACE ${${BRPREFIX}_LIBRARY})
|
||||
set_property(TARGET ${brlib}-static PROPERTY INTERFACE_COMPILE_OPTIONS ${PC_${BRPREFIX}_CFLAGS_OTHER})
|
||||
else()
|
||||
add_library(${brlib} INTERFACE IMPORTED GLOBAL)
|
||||
target_include_directories(${brlib}
|
||||
INTERFACE ${BROTLI_INCLUDE_DIR})
|
||||
target_link_libraries(${brlib}
|
||||
INTERFACE ${${BRPREFIX}_LIBRARY})
|
||||
target_link_options(${brlib}
|
||||
INTERFACE ${PC_${BRPREFIX}_LDFLAGS_OTHER})
|
||||
target_compile_options(${brlib}
|
||||
INTERFACE ${PC_${BRPREFIX}_CFLAGS_OTHER})
|
||||
|
||||
# TODO(deymo): Remove the -static library versions, this target is
|
||||
# currently needed by brunsli.cmake. When importing it this way, the
|
||||
# brotli*-static target is just an alias.
|
||||
add_library(${brlib}-static ALIAS ${brlib})
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if (BROTLICOMMON_FOUND AND BROTLIENC_FOUND AND BROTLIDEC_FOUND)
|
||||
set(Brotli_FOUND ON)
|
||||
else ()
|
||||
set(Brotli_FOUND OFF)
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Brotli
|
||||
FOUND_VAR Brotli_FOUND
|
||||
REQUIRED_VARS
|
||||
BROTLI_INCLUDE_DIR
|
||||
BROTLICOMMON_LIBRARY
|
||||
BROTLIENC_LIBRARY
|
||||
BROTLIDEC_LIBRARY
|
||||
VERSION_VAR Brotli_VERSION
|
||||
)
|
||||
|
||||
mark_as_advanced(
|
||||
BROTLI_INCLUDE_DIR
|
||||
BROTLICOMMON_LIBRARY
|
||||
BROTLIENC_LIBRARY
|
||||
BROTLIDEC_LIBRARY
|
||||
)
|
||||
|
||||
if (Brotli_FOUND)
|
||||
set(Brotli_LIBRARIES ${BROTLICOMMON_LIBRARY} ${BROTLIENC_LIBRARY} ${BROTLIDEC_LIBRARY})
|
||||
set(Brotli_INCLUDE_DIRS ${BROTLI_INCLUDE_DIR})
|
||||
endif()
|
||||
66
thirdparty/SDL3_image/external/libjxl/cmake/FindHWY.cmake
vendored
Normal file
66
thirdparty/SDL3_image/external/libjxl/cmake/FindHWY.cmake
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
find_package(PkgConfig QUIET)
|
||||
if (PkgConfig_FOUND)
|
||||
pkg_check_modules(PC_HWY QUIET libhwy)
|
||||
set(HWY_VERSION ${PC_HWY_VERSION})
|
||||
endif ()
|
||||
|
||||
find_path(HWY_INCLUDE_DIR
|
||||
NAMES hwy/highway.h
|
||||
HINTS ${PC_HWY_INCLUDEDIR} ${PC_HWY_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
find_library(HWY_LIBRARY
|
||||
NAMES ${HWY_NAMES} hwy
|
||||
HINTS ${PC_HWY_LIBDIR} ${PC_HWY_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
if (HWY_INCLUDE_DIR AND NOT HWY_VERSION)
|
||||
if (EXISTS "${HWY_INCLUDE_DIR}/hwy/highway.h")
|
||||
file(READ "${HWY_INCLUDE_DIR}/hwy/highway.h" HWY_VERSION_CONTENT)
|
||||
|
||||
string(REGEX MATCH "#define HWY_MAJOR +([0-9]+)" _dummy "${HWY_VERSION_CONTENT}")
|
||||
set(HWY_VERSION_MAJOR "${CMAKE_MATCH_1}")
|
||||
|
||||
string(REGEX MATCH "#define +HWY_MINOR +([0-9]+)" _dummy "${HWY_VERSION_CONTENT}")
|
||||
set(HWY_VERSION_MINOR "${CMAKE_MATCH_1}")
|
||||
|
||||
string(REGEX MATCH "#define +HWY_PATCH +([0-9]+)" _dummy "${HWY_VERSION_CONTENT}")
|
||||
set(HWY_VERSION_PATCH "${CMAKE_MATCH_1}")
|
||||
|
||||
set(HWY_VERSION "${HWY_VERSION_MAJOR}.${HWY_VERSION_MINOR}.${HWY_VERSION_PATCH}")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(HWY
|
||||
FOUND_VAR HWY_FOUND
|
||||
REQUIRED_VARS HWY_LIBRARY HWY_INCLUDE_DIR
|
||||
VERSION_VAR HWY_VERSION
|
||||
)
|
||||
|
||||
if (HWY_LIBRARY AND NOT TARGET hwy)
|
||||
add_library(hwy INTERFACE IMPORTED GLOBAL)
|
||||
|
||||
if(CMAKE_VERSION VERSION_LESS "3.13.5")
|
||||
set_property(TARGET hwy PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${HWY_INCLUDE_DIR})
|
||||
target_link_libraries(hwy INTERFACE ${HWY_LIBRARY})
|
||||
set_property(TARGET hwy PROPERTY INTERFACE_COMPILE_OPTIONS ${PC_HWY_CFLAGS_OTHER})
|
||||
else()
|
||||
target_include_directories(hwy INTERFACE ${HWY_INCLUDE_DIR})
|
||||
target_link_libraries(hwy INTERFACE ${HWY_LIBRARY})
|
||||
target_link_options(hwy INTERFACE ${PC_HWY_LDFLAGS_OTHER})
|
||||
target_compile_options(hwy INTERFACE ${PC_HWY_CFLAGS_OTHER})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
mark_as_advanced(HWY_INCLUDE_DIR HWY_LIBRARY)
|
||||
|
||||
if (HWY_FOUND)
|
||||
set(HWY_LIBRARIES ${HWY_LIBRARY})
|
||||
set(HWY_INCLUDE_DIRS ${HWY_INCLUDE_DIR})
|
||||
endif ()
|
||||
59
thirdparty/SDL3_image/external/libjxl/cmake/FindLCMS2.cmake
vendored
Normal file
59
thirdparty/SDL3_image/external/libjxl/cmake/FindLCMS2.cmake
vendored
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
find_package(PkgConfig QUIET)
|
||||
if (PkgConfig_FOUND)
|
||||
pkg_check_modules(PC_LCMS2 QUIET libLCMS2)
|
||||
set(LCMS2_VERSION ${PC_LCMS2_VERSION})
|
||||
endif ()
|
||||
|
||||
find_path(LCMS2_INCLUDE_DIR
|
||||
NAMES lcms2.h
|
||||
HINTS ${PC_LCMS2_INCLUDEDIR} ${PC_LCMS2_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
find_library(LCMS2_LIBRARY
|
||||
NAMES ${LCMS2_NAMES} lcms2 liblcms2 lcms-2 liblcms-2
|
||||
HINTS ${PC_LCMS2_LIBDIR} ${PC_LCMS2_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
if (LCMS2_INCLUDE_DIR AND NOT LCMS_VERSION)
|
||||
file(READ ${LCMS2_INCLUDE_DIR}/lcms2.h LCMS2_VERSION_CONTENT)
|
||||
string(REGEX MATCH "#define[ \t]+LCMS_VERSION[ \t]+([0-9]+)[ \t]*\n" LCMS2_VERSION_MATCH ${LCMS2_VERSION_CONTENT})
|
||||
if (LCMS2_VERSION_MATCH)
|
||||
string(SUBSTRING ${CMAKE_MATCH_1} 0 1 LCMS2_VERSION_MAJOR)
|
||||
string(SUBSTRING ${CMAKE_MATCH_1} 1 2 LCMS2_VERSION_MINOR)
|
||||
set(LCMS2_VERSION "${LCMS2_VERSION_MAJOR}.${LCMS2_VERSION_MINOR}")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(LCMS2
|
||||
FOUND_VAR LCMS2_FOUND
|
||||
REQUIRED_VARS LCMS2_LIBRARY LCMS2_INCLUDE_DIR
|
||||
VERSION_VAR LCMS2_VERSION
|
||||
)
|
||||
|
||||
if (LCMS2_LIBRARY AND NOT TARGET lcms2)
|
||||
add_library(lcms2 INTERFACE IMPORTED GLOBAL)
|
||||
|
||||
if(CMAKE_VERSION VERSION_LESS "3.13.5")
|
||||
set_property(TARGET lcms2 PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${LCMS2_INCLUDE_DIR})
|
||||
target_link_libraries(lcms2 INTERFACE ${LCMS2_LIBRARY})
|
||||
set_property(TARGET lcms2 PROPERTY INTERFACE_COMPILE_OPTIONS ${PC_LCMS2_CFLAGS_OTHER})
|
||||
else()
|
||||
target_include_directories(lcms2 INTERFACE ${LCMS2_INCLUDE_DIR})
|
||||
target_link_libraries(lcms2 INTERFACE ${LCMS2_LIBRARY})
|
||||
target_link_options(lcms2 INTERFACE ${PC_LCMS2_LDFLAGS_OTHER})
|
||||
target_compile_options(lcms2 INTERFACE ${PC_LCMS2_CFLAGS_OTHER})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
mark_as_advanced(LCMS2_INCLUDE_DIR LCMS2_LIBRARY)
|
||||
|
||||
if (LCMS2_FOUND)
|
||||
set(LCMS2_LIBRARIES ${LCMS2_LIBRARY})
|
||||
set(LCMS2_INCLUDE_DIRS ${LCMS2_INCLUDE_DIR})
|
||||
endif ()
|
||||
84
thirdparty/SDL3_image/external/libjxl/deps.sh
vendored
Executable file
84
thirdparty/SDL3_image/external/libjxl/deps.sh
vendored
Executable file
|
|
@ -0,0 +1,84 @@
|
|||
#!/usr/bin/env bash
|
||||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
# This file downloads the dependencies needed to build JPEG XL into third_party.
|
||||
# These dependencies are normally pulled by gtest.
|
||||
|
||||
set -eu
|
||||
|
||||
MYDIR=$(dirname $(realpath "$0"))
|
||||
|
||||
# Git revisions we use for the given submodules. Update these whenever you
|
||||
# update a git submodule.
|
||||
THIRD_PARTY_BROTLI="35ef5c554d888bef217d449346067de05e269b30"
|
||||
THIRD_PARTY_HIGHWAY="22e3d7276f4157d4a47586ba9fd91dd6303f441a"
|
||||
THIRD_PARTY_SKCMS="64374756e03700d649f897dbd98c95e78c30c7da"
|
||||
THIRD_PARTY_SJPEG="94e0df6d0f8b44228de5be0ff35efb9f946a13c9" # Wed Apr 2 15:42:02 2025 -0700
|
||||
THIRD_PARTY_ZLIB="09155eaa2f9270dc4ed1fa13e2b4b2613e6e4851" # v1.3
|
||||
THIRD_PARTY_LIBPNG="a40189cf881e9f0db80511c382292a5604c3c3d1"
|
||||
|
||||
# Download the target revision from GitHub.
|
||||
download_github() {
|
||||
local path="$1"
|
||||
local project="$2"
|
||||
|
||||
local varname=`echo "$path" | tr '[:lower:]' '[:upper:]'`
|
||||
varname="${varname/\//_}"
|
||||
local sha
|
||||
eval "sha=\${${varname}}"
|
||||
|
||||
local down_dir="${MYDIR}/downloads"
|
||||
local local_fn="${down_dir}/${sha}.tar.gz"
|
||||
if [[ -e "${local_fn}" && -d "${MYDIR}/${path}" ]]; then
|
||||
echo "${path} already up to date." >&2
|
||||
return 0
|
||||
fi
|
||||
|
||||
local url
|
||||
local strip_components=0
|
||||
if [[ "${project:0:4}" == "http" ]]; then
|
||||
# "project" is a googlesource.com base url.
|
||||
url="${project}${sha}.tar.gz"
|
||||
else
|
||||
# GitHub files have a top-level directory
|
||||
strip_components=1
|
||||
url="https://github.com/${project}/tarball/${sha}"
|
||||
fi
|
||||
|
||||
echo "Downloading ${path} version ${sha}..." >&2
|
||||
mkdir -p "${down_dir}"
|
||||
curl -L --show-error -o "${local_fn}.tmp" "${url}"
|
||||
mkdir -p "${MYDIR}/${path}"
|
||||
tar -zxf "${local_fn}.tmp" -C "${MYDIR}/${path}" \
|
||||
--strip-components="${strip_components}"
|
||||
mv "${local_fn}.tmp" "${local_fn}"
|
||||
}
|
||||
|
||||
|
||||
main() {
|
||||
if git -C "${MYDIR}" rev-parse; then
|
||||
cat >&2 <<EOF
|
||||
Current directory is a git repository, downloading dependencies via git:
|
||||
|
||||
git submodule update --init --recursive
|
||||
|
||||
EOF
|
||||
git -C "${MYDIR}" submodule update --init --recursive --depth 1 --recommend-shallow
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Sources downloaded from a tarball.
|
||||
download_github third_party/brotli google/brotli
|
||||
download_github third_party/highway google/highway
|
||||
download_github third_party/sjpeg webmproject/sjpeg
|
||||
download_github third_party/skcms \
|
||||
"https://skia.googlesource.com/skcms/+archive/"
|
||||
download_github third_party/zlib madler/zlib
|
||||
download_github third_party/libpng glennrp/libpng
|
||||
echo "Done."
|
||||
}
|
||||
|
||||
main "$@"
|
||||
21
thirdparty/SDL3_image/external/libjxl/docker/Dockerfile.jpegxl-builder
vendored
Normal file
21
thirdparty/SDL3_image/external/libjxl/docker/Dockerfile.jpegxl-builder
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
# Build an Ubuntu-based docker image with the installed software needed to
|
||||
# develop and test JPEG XL.
|
||||
|
||||
FROM ubuntu:bionic
|
||||
|
||||
# Set a prompt for when using it locally.
|
||||
ENV PS1="\[\033[01;33m\]\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ "
|
||||
|
||||
COPY scripts/99_norecommends /etc/apt/apt.conf.d/99_norecommends
|
||||
|
||||
COPY scripts /jpegxl_scripts
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN /jpegxl_scripts/jpegxl_builder.sh && \
|
||||
rm -rf /jpegxl_scripts
|
||||
37
thirdparty/SDL3_image/external/libjxl/docker/Dockerfile.jpegxl-builder-run-aarch64
vendored
Normal file
37
thirdparty/SDL3_image/external/libjxl/docker/Dockerfile.jpegxl-builder-run-aarch64
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
# Build an Ubuntu-based docker image for aarch64 with the installed software
|
||||
# needed to run JPEG XL. This is only useful when running on actual aarch64
|
||||
# hardware.
|
||||
|
||||
FROM arm64v8/ubuntu:bionic
|
||||
|
||||
COPY scripts/99_norecommends /etc/apt/apt.conf.d/99_norecommends
|
||||
|
||||
# Set a prompt for when using it locally.
|
||||
ENV PS1="\[\033[01;33m\]\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ "
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN set -ex; \
|
||||
apt-get update -y; \
|
||||
apt-get install -y \
|
||||
bsdmainutils \
|
||||
cmake \
|
||||
curl \
|
||||
ca-certificates \
|
||||
extra-cmake-modules \
|
||||
git \
|
||||
imagemagick \
|
||||
libjpeg8 \
|
||||
libgif7 \
|
||||
libgoogle-perftools4 \
|
||||
libopenexr22 \
|
||||
libpng16-16 \
|
||||
libqt5x11extras5 \
|
||||
libsdl2-2.0-0 \
|
||||
parallel; \
|
||||
rm -rf /var/lib/apt/lists/*;
|
||||
7
thirdparty/SDL3_image/external/libjxl/docker/README.md
vendored
Normal file
7
thirdparty/SDL3_image/external/libjxl/docker/README.md
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
### Docker container infrastructure for JPEG XL
|
||||
|
||||
This directory contains the requirements to build a docker image for the
|
||||
JPEG XL project builder.
|
||||
|
||||
Docker images need to be created and upload manually. See ./build.sh for
|
||||
details.
|
||||
83
thirdparty/SDL3_image/external/libjxl/docker/build.sh
vendored
Executable file
83
thirdparty/SDL3_image/external/libjxl/docker/build.sh
vendored
Executable file
|
|
@ -0,0 +1,83 @@
|
|||
#!/usr/bin/env bash
|
||||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
set -eu
|
||||
|
||||
MYDIR=$(dirname $(realpath "$0"))
|
||||
|
||||
declare -a TARGETS
|
||||
|
||||
load_targets() {
|
||||
# Built-in OSX "find" does not support "-m".
|
||||
FIND=$(which "gfind" || which "find")
|
||||
for f in $(${FIND} -maxdepth 1 -name 'Dockerfile.*' | sort); do
|
||||
local target="${f#*Dockerfile.}"
|
||||
TARGETS+=("${target}")
|
||||
done
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat >&2 <<EOF
|
||||
Use: $1 [targets]
|
||||
|
||||
Available targets:
|
||||
* all
|
||||
EOF
|
||||
for target in "${TARGETS[@]}"; do
|
||||
echo " * ${target}" >&2
|
||||
done
|
||||
}
|
||||
|
||||
build_target() {
|
||||
local target="$1"
|
||||
|
||||
local dockerfile="${MYDIR}/Dockerfile.${target}"
|
||||
# JPEG XL builder images are stored in the gcr.io/jpegxl project.
|
||||
local tag="gcr.io/jpegxl/${target}"
|
||||
|
||||
echo "Building ${target}"
|
||||
if ! sudo docker build --no-cache -t "${tag}" -f "${dockerfile}" "${MYDIR}" \
|
||||
>"${target}.log" 2>&1; then
|
||||
echo "${target} failed. See ${target}.log" >&2
|
||||
else
|
||||
echo "Done, to upload image run:" >&2
|
||||
echo " sudo docker push ${tag}"
|
||||
if [[ "${JPEGXL_PUSH:-}" == "1" ]]; then
|
||||
echo "sudo docker push ${tag}" >&2
|
||||
sudo docker push "${tag}"
|
||||
# The RepoDigest is only created after it is pushed.
|
||||
local fulltag=$(sudo docker inspect --format="{{.RepoDigests}}" "${tag}")
|
||||
fulltag="${fulltag#[}"
|
||||
fulltag="${fulltag%]}"
|
||||
echo "Updating .gitlab-ci.yml to ${fulltag}" >&2
|
||||
sed -E "s;${tag}@sha256:[0-9a-f]+;${fulltag};" \
|
||||
-i "${MYDIR}/../.gitlab-ci.yml"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
cd "${MYDIR}"
|
||||
local target="${1:-}"
|
||||
|
||||
load_targets
|
||||
if [[ -z "${target}" ]]; then
|
||||
usage $0
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${target}" == "all" ]]; then
|
||||
for target in "${TARGETS[@]}"; do
|
||||
build_target "${target}"
|
||||
done
|
||||
else
|
||||
for target in "$@"; do
|
||||
build_target "${target}"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
1
thirdparty/SDL3_image/external/libjxl/docker/scripts/99_norecommends
vendored
Normal file
1
thirdparty/SDL3_image/external/libjxl/docker/scripts/99_norecommends
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
APT::Install-Recommends "false";
|
||||
28
thirdparty/SDL3_image/external/libjxl/docker/scripts/binutils_align_fix.patch
vendored
Normal file
28
thirdparty/SDL3_image/external/libjxl/docker/scripts/binutils_align_fix.patch
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
Description: fix lack of alignment in relocations (crashes on mingw)
|
||||
See https://sourceware.org/git/?p=binutils-gdb.git;a=patch;h=73af69e74974eaa155eec89867e3ccc77ab39f6d
|
||||
From: Marc <marc@groundctl.com>
|
||||
Date: Fri, 9 Nov 2018 11:13:50 +0000
|
||||
Subject: [PATCH] Allow for compilers that do not produce aligned .rdat
|
||||
sections in PE format files.
|
||||
|
||||
--- a/upstream/ld/scripttempl/pe.sc 2020-05-12 18:45:12.000000000 +0200
|
||||
+++ b/upstream/ld/scripttempl/pe.sc 2020-05-12 18:47:12.000000000 +0200
|
||||
@@ -143,6 +143,7 @@
|
||||
.rdata ${RELOCATING+BLOCK(__section_alignment__)} :
|
||||
{
|
||||
${R_RDATA}
|
||||
+ . = ALIGN(4);
|
||||
${RELOCATING+__rt_psrelocs_start = .;}
|
||||
${RELOCATING+KEEP(*(.rdata_runtime_pseudo_reloc))}
|
||||
${RELOCATING+__rt_psrelocs_end = .;}
|
||||
--- a/upstream/ld/scripttempl/pep.sc 2020-05-12 18:45:19.000000000 +0200
|
||||
+++ b/upstream/ld/scripttempl/pep.sc 2020-05-12 18:47:18.000000000 +0200
|
||||
@@ -143,6 +143,7 @@
|
||||
.rdata ${RELOCATING+BLOCK(__section_alignment__)} :
|
||||
{
|
||||
${R_RDATA}
|
||||
+ . = ALIGN(4);
|
||||
${RELOCATING+__rt_psrelocs_start = .;}
|
||||
${RELOCATING+KEEP(*(.rdata_runtime_pseudo_reloc))}
|
||||
${RELOCATING+__rt_psrelocs_end = .;}
|
||||
|
||||
37
thirdparty/SDL3_image/external/libjxl/docker/scripts/emsdk_install.sh
vendored
Executable file
37
thirdparty/SDL3_image/external/libjxl/docker/scripts/emsdk_install.sh
vendored
Executable file
|
|
@ -0,0 +1,37 @@
|
|||
#!/usr/bin/env bash
|
||||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
EMSDK_URL="https://github.com/emscripten-core/emsdk/archive/main.tar.gz"
|
||||
EMSDK_DIR="/opt/emsdk"
|
||||
|
||||
EMSDK_RELEASE="2.0.23"
|
||||
|
||||
set -eu -x
|
||||
|
||||
# Temporary files cleanup hooks.
|
||||
CLEANUP_FILES=()
|
||||
cleanup() {
|
||||
if [[ ${#CLEANUP_FILES[@]} -ne 0 ]]; then
|
||||
rm -fr "${CLEANUP_FILES[@]}"
|
||||
fi
|
||||
}
|
||||
trap "{ set +x; } 2>/dev/null; cleanup" INT TERM EXIT
|
||||
|
||||
main() {
|
||||
local workdir=$(mktemp -d --suffix=emsdk)
|
||||
CLEANUP_FILES+=("${workdir}")
|
||||
|
||||
local emsdktar="${workdir}/emsdk.tar.gz"
|
||||
curl --output "${emsdktar}" "${EMSDK_URL}" --location
|
||||
mkdir -p "${EMSDK_DIR}"
|
||||
tar -zxf "${emsdktar}" -C "${EMSDK_DIR}" --strip-components=1
|
||||
|
||||
cd "${EMSDK_DIR}"
|
||||
./emsdk install --shallow "${EMSDK_RELEASE}"
|
||||
./emsdk activate --embedded "${EMSDK_RELEASE}"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
518
thirdparty/SDL3_image/external/libjxl/docker/scripts/jpegxl_builder.sh
vendored
Executable file
518
thirdparty/SDL3_image/external/libjxl/docker/scripts/jpegxl_builder.sh
vendored
Executable file
|
|
@ -0,0 +1,518 @@
|
|||
#!/usr/bin/env bash
|
||||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
# Main entry point for all the Dockerfile for jpegxl-builder. This centralized
|
||||
# file helps sharing code and configuration between Dockerfiles.
|
||||
|
||||
set -eux
|
||||
|
||||
MYDIR=$(dirname $(realpath "$0"))
|
||||
|
||||
# libjpeg-turbo.
|
||||
JPEG_TURBO_RELEASE="2.0.4"
|
||||
JPEG_TURBO_URL="https://github.com/libjpeg-turbo/libjpeg-turbo/archive/${JPEG_TURBO_RELEASE}.tar.gz"
|
||||
JPEG_TURBO_SHA256="7777c3c19762940cff42b3ba4d7cd5c52d1671b39a79532050c85efb99079064"
|
||||
|
||||
# zlib (dependency of libpng)
|
||||
ZLIB_RELEASE="1.2.11"
|
||||
ZLIB_URL="https://www.zlib.net/zlib-${ZLIB_RELEASE}.tar.gz"
|
||||
ZLIB_SHA256="c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1"
|
||||
# The name in the .pc and the .dll generated don't match in zlib for Windows
|
||||
# because they use different .dll names in Windows. We avoid that by defining
|
||||
# UNIX=1. We also install all the .dll files to ${prefix}/lib instead of the
|
||||
# default ${prefix}/bin.
|
||||
ZLIB_FLAGS='-DUNIX=1 -DINSTALL_PKGCONFIG_DIR=/${CMAKE_INSTALL_PREFIX}/lib/pkgconfig -DINSTALL_BIN_DIR=/${CMAKE_INSTALL_PREFIX}/lib'
|
||||
|
||||
# libpng
|
||||
LIBPNG_RELEASE="1.6.37"
|
||||
LIBPNG_URL="https://github.com/glennrp/libpng/archive/v${LIBPNG_RELEASE}.tar.gz"
|
||||
LIBPNG_SHA256="ca74a0dace179a8422187671aee97dd3892b53e168627145271cad5b5ac81307"
|
||||
|
||||
# giflib
|
||||
GIFLIB_RELEASE="5.2.1"
|
||||
GIFLIB_URL="https://netcologne.dl.sourceforge.net/project/giflib/giflib-${GIFLIB_RELEASE}.tar.gz"
|
||||
GIFLIB_SHA256="31da5562f44c5f15d63340a09a4fd62b48c45620cd302f77a6d9acf0077879bd"
|
||||
|
||||
# A patch needed to compile GIFLIB in mingw.
|
||||
GIFLIB_PATCH_URL="https://github.com/msys2/MINGW-packages/raw/3afde38fcee7b3ba2cafd97d76cca8f06934504f/mingw-w64-giflib/001-mingw-build.patch"
|
||||
GIFLIB_PATCH_SHA256="2b2262ddea87fc07be82e10aeb39eb699239f883c899aa18a16e4d4e40af8ec8"
|
||||
|
||||
# webp
|
||||
WEBP_RELEASE="1.0.2"
|
||||
WEBP_URL="https://codeload.github.com/webmproject/libwebp/tar.gz/v${WEBP_RELEASE}"
|
||||
WEBP_SHA256="347cf85ddc3497832b5fa9eee62164a37b249c83adae0ba583093e039bf4881f"
|
||||
|
||||
# Google benchmark
|
||||
BENCHMARK_RELEASE="1.5.2"
|
||||
BENCHMARK_URL="https://github.com/google/benchmark/archive/v${BENCHMARK_RELEASE}.tar.gz"
|
||||
BENCHMARK_SHA256="dccbdab796baa1043f04982147e67bb6e118fe610da2c65f88912d73987e700c"
|
||||
BENCHMARK_FLAGS="-DGOOGLETEST_PATH=${MYDIR}/../../third_party/googletest"
|
||||
# attribute(format(__MINGW_PRINTF_FORMAT, ...)) doesn't work in our
|
||||
# environment, so we disable the warning.
|
||||
BENCHMARK_FLAGS="-DCMAKE_BUILD_TYPE=Release -DBENCHMARK_ENABLE_TESTING=OFF \
|
||||
-DCMAKE_CXX_FLAGS=-Wno-ignored-attributes \
|
||||
-DCMAKE_POSITION_INDEPENDENT_CODE=ON"
|
||||
|
||||
# V8
|
||||
V8_VERSION="9.3.22"
|
||||
|
||||
# Temporary files cleanup hooks.
|
||||
CLEANUP_FILES=()
|
||||
cleanup() {
|
||||
if [[ ${#CLEANUP_FILES[@]} -ne 0 ]]; then
|
||||
rm -fr "${CLEANUP_FILES[@]}"
|
||||
fi
|
||||
}
|
||||
trap "{ set +x; } 2>/dev/null; cleanup" INT TERM EXIT
|
||||
|
||||
# List of Ubuntu arch names supported by the builder (such as "i386").
|
||||
LIST_ARCHS=(
|
||||
amd64
|
||||
i386
|
||||
arm64
|
||||
armhf
|
||||
)
|
||||
|
||||
# List of target triplets supported by the builder.
|
||||
LIST_TARGETS=(
|
||||
x86_64-linux-gnu
|
||||
i686-linux-gnu
|
||||
arm-linux-gnueabihf
|
||||
aarch64-linux-gnu
|
||||
)
|
||||
LIST_MINGW_TARGETS=(
|
||||
i686-w64-mingw32
|
||||
x86_64-w64-mingw32
|
||||
)
|
||||
LIST_WASM_TARGETS=(
|
||||
wasm32
|
||||
)
|
||||
|
||||
# Setup the apt repositories and supported architectures.
|
||||
setup_apt() {
|
||||
apt-get update -y
|
||||
apt-get install -y curl gnupg ca-certificates
|
||||
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 1E9377A2BA9EF27F
|
||||
|
||||
# node sources.
|
||||
cat >/etc/apt/sources.list.d/nodesource.list <<EOF
|
||||
deb https://deb.nodesource.com/node_14.x bionic main
|
||||
deb-src https://deb.nodesource.com/node_14.x bionic main
|
||||
EOF
|
||||
curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -
|
||||
|
||||
local port_list=()
|
||||
local main_list=()
|
||||
local ubarch
|
||||
for ubarch in "${LIST_ARCHS[@]}"; do
|
||||
if [[ "${ubarch}" != "amd64" && "${ubarch}" != "i386" ]]; then
|
||||
# other archs are not part of the main mirrors, but available in
|
||||
# ports.ubuntu.com.
|
||||
port_list+=("${ubarch}")
|
||||
else
|
||||
main_list+=("${ubarch}")
|
||||
fi
|
||||
# Add the arch to the system.
|
||||
if [[ "${ubarch}" != "amd64" ]]; then
|
||||
dpkg --add-architecture "${ubarch}"
|
||||
fi
|
||||
done
|
||||
|
||||
# Update the sources.list with the split of supported architectures.
|
||||
local bkplist="/etc/apt/sources.list.bkp"
|
||||
[[ -e "${bkplist}" ]] || \
|
||||
mv /etc/apt/sources.list "${bkplist}"
|
||||
|
||||
local newlist="/etc/apt/sources.list.tmp"
|
||||
rm -f "${newlist}"
|
||||
port_list=$(echo "${port_list[@]}" | tr ' ' ,)
|
||||
if [[ -n "${port_list}" ]]; then
|
||||
local port_url="http://ports.ubuntu.com/ubuntu-ports/"
|
||||
grep -v -E '^#' "${bkplist}" |
|
||||
sed -E "s;^deb (http[^ ]+) (.*)\$;deb [arch=${port_list}] ${port_url} \\2;" \
|
||||
>>"${newlist}"
|
||||
fi
|
||||
|
||||
main_list=$(echo "${main_list[@]}" | tr ' ' ,)
|
||||
grep -v -E '^#' "${bkplist}" |
|
||||
sed -E "s;^deb (http[^ ]+) (.*)\$;deb [arch=${main_list}] \\1 \\2\ndeb-src [arch=${main_list}] \\1 \\2;" \
|
||||
>>"${newlist}"
|
||||
mv "${newlist}" /etc/apt/sources.list
|
||||
}
|
||||
|
||||
install_pkgs() {
|
||||
packages=(
|
||||
# Native compilers (minimum for SIMD is clang-7)
|
||||
clang-7 clang-format-7 clang-tidy-7
|
||||
|
||||
# TODO: Consider adding clang-8 to every builder:
|
||||
# clang-8 clang-format-8 clang-tidy-8
|
||||
|
||||
# For cross-compiling to Windows with mingw.
|
||||
mingw-w64
|
||||
wine64
|
||||
wine-binfmt
|
||||
|
||||
# Native tools.
|
||||
bsdmainutils
|
||||
cmake
|
||||
extra-cmake-modules
|
||||
git
|
||||
llvm
|
||||
nasm
|
||||
ninja-build
|
||||
parallel
|
||||
pkg-config
|
||||
|
||||
# For compiling / testing JNI wrapper. JDK8 is almost 2x smaller than JDK11
|
||||
# openjdk-8-jdk-headless would be 50MB smaller, unfortunately, CMake
|
||||
# does mistakenly thinks it does not contain JNI feature.
|
||||
openjdk-8-jdk
|
||||
|
||||
# These are used by the ./ci.sh lint in the native builder.
|
||||
clang-format-7
|
||||
clang-format-8
|
||||
|
||||
# For coverage builds
|
||||
gcovr
|
||||
|
||||
# For compiling giflib documentation.
|
||||
xmlto
|
||||
|
||||
# Common libraries.
|
||||
libstdc++-8-dev
|
||||
|
||||
# We don't use tcmalloc on archs other than amd64. This installs
|
||||
# libgoogle-perftools4:amd64.
|
||||
google-perftools
|
||||
|
||||
# NodeJS for running WASM tests
|
||||
nodejs
|
||||
|
||||
# To generate API documentation.
|
||||
doxygen
|
||||
|
||||
# Freezes version that builds (passes tests). Newer version
|
||||
# (2.30-21ubuntu1~18.04.4) claims to fix "On Intel Skylake
|
||||
# (-march=native) generated avx512 instruction can be wrong",
|
||||
# but newly added tests does not pass. Perhaps the problem is
|
||||
# that mingw package is not updated.
|
||||
binutils-source=2.30-15ubuntu1
|
||||
)
|
||||
|
||||
# Install packages that are arch-dependent.
|
||||
local ubarch
|
||||
for ubarch in "${LIST_ARCHS[@]}"; do
|
||||
packages+=(
|
||||
# Library dependencies. These normally depend on the target architecture
|
||||
# we are compiling for and can't usually be installed for multiple
|
||||
# architectures at the same time.
|
||||
libgif7:"${ubarch}"
|
||||
libjpeg-dev:"${ubarch}"
|
||||
libpng-dev:"${ubarch}"
|
||||
libqt5x11extras5-dev:"${ubarch}"
|
||||
|
||||
libstdc++-8-dev:"${ubarch}"
|
||||
qtbase5-dev:"${ubarch}"
|
||||
|
||||
# For OpenEXR:
|
||||
libilmbase12:"${ubarch}"
|
||||
libopenexr22:"${ubarch}"
|
||||
|
||||
# TCMalloc dependency
|
||||
libunwind-dev:"${ubarch}"
|
||||
|
||||
# Cross-compiling tools per arch.
|
||||
libc6-dev-"${ubarch}"-cross
|
||||
libstdc++-8-dev-"${ubarch}"-cross
|
||||
)
|
||||
done
|
||||
|
||||
local target
|
||||
for target in "${LIST_TARGETS[@]}"; do
|
||||
# Per target cross-compiling tools.
|
||||
if [[ "${target}" != "x86_64-linux-gnu" ]]; then
|
||||
packages+=(
|
||||
binutils-"${target}"
|
||||
gcc-"${target}"
|
||||
)
|
||||
fi
|
||||
done
|
||||
|
||||
# Install all the manual packages via "apt install" for the main arch. These
|
||||
# will be installed for other archs via manual download and unpack.
|
||||
apt install -y "${packages[@]}" "${UNPACK_PKGS[@]}"
|
||||
}
|
||||
|
||||
# binutils <2.32 need a patch.
|
||||
install_binutils() {
|
||||
local workdir=$(mktemp -d --suffix=_install)
|
||||
CLEANUP_FILES+=("${workdir}")
|
||||
pushd "${workdir}"
|
||||
apt source binutils-mingw-w64
|
||||
apt -y build-dep binutils-mingw-w64
|
||||
cd binutils-mingw-w64-8ubuntu1
|
||||
cp "${MYDIR}/binutils_align_fix.patch" debian/patches
|
||||
echo binutils_align_fix.patch >> debian/patches/series
|
||||
dpkg-buildpackage -b
|
||||
cd ..
|
||||
dpkg -i *deb
|
||||
popd
|
||||
}
|
||||
|
||||
# Install a library from the source code for multiple targets.
|
||||
# Usage: install_from_source <tar_url> <sha256> <target> [<target...>]
|
||||
install_from_source() {
|
||||
local package="$1"
|
||||
shift
|
||||
|
||||
local url
|
||||
eval "url=\${${package}_URL}"
|
||||
local sha256
|
||||
eval "sha256=\${${package}_SHA256}"
|
||||
# Optional package flags
|
||||
local pkgflags
|
||||
eval "pkgflags=\${${package}_FLAGS:-}"
|
||||
|
||||
local workdir=$(mktemp -d --suffix=_install)
|
||||
CLEANUP_FILES+=("${workdir}")
|
||||
|
||||
local tarfile="${workdir}"/$(basename "${url}")
|
||||
curl -L --output "${tarfile}" "${url}"
|
||||
if ! echo "${sha256} ${tarfile}" | sha256sum -c --status -; then
|
||||
echo "SHA256 mismatch for ${url}: expected ${sha256} but found:"
|
||||
sha256sum "${tarfile}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local target
|
||||
for target in "$@"; do
|
||||
echo "Installing ${package} for target ${target} from ${url}"
|
||||
|
||||
local srcdir="${workdir}/source-${target}"
|
||||
mkdir -p "${srcdir}"
|
||||
tar -zxf "${tarfile}" -C "${srcdir}" --strip-components=1
|
||||
|
||||
local prefix="/usr"
|
||||
if [[ "${target}" != "x86_64-linux-gnu" ]]; then
|
||||
prefix="/usr/${target}"
|
||||
fi
|
||||
|
||||
# Apply patches to buildfiles.
|
||||
if [[ "${package}" == "GIFLIB" && "${target}" == *mingw32 ]]; then
|
||||
# GIFLIB Makefile has several problems so we need to fix them here. We are
|
||||
# using a patch from MSYS2 that already fixes the compilation for mingw.
|
||||
local make_patch="${srcdir}/libgif.patch"
|
||||
curl -L "${GIFLIB_PATCH_URL}" -o "${make_patch}"
|
||||
echo "${GIFLIB_PATCH_SHA256} ${make_patch}" | sha256sum -c --status -
|
||||
patch "${srcdir}/Makefile" < "${make_patch}"
|
||||
elif [[ "${package}" == "LIBPNG" && "${target}" == wasm* ]]; then
|
||||
# Cut the dependency to libm; there is pull request to fix it, so this
|
||||
# might not be needed in the future.
|
||||
sed -i 's/APPLE/EMSCRIPTEN/g' "${srcdir}/CMakeLists.txt"
|
||||
fi
|
||||
|
||||
local cmake_args=()
|
||||
local export_args=("CC=clang-7" "CXX=clang++-7")
|
||||
local cmake="cmake"
|
||||
local make="make"
|
||||
local system_name="Linux"
|
||||
if [[ "${target}" == *mingw32 ]]; then
|
||||
system_name="Windows"
|
||||
# When compiling with clang, CMake doesn't detect that we are using mingw.
|
||||
cmake_args+=(
|
||||
-DMINGW=1
|
||||
# Googletest needs this when cross-compiling to windows
|
||||
-DCMAKE_CROSSCOMPILING=1
|
||||
-DHAVE_STD_REGEX=0
|
||||
-DHAVE_POSIX_REGEX=0
|
||||
-DHAVE_GNU_POSIX_REGEX=0
|
||||
)
|
||||
local windres=$(which ${target}-windres || true)
|
||||
if [[ -n "${windres}" ]]; then
|
||||
cmake_args+=(-DCMAKE_RC_COMPILER="${windres}")
|
||||
fi
|
||||
fi
|
||||
if [[ "${target}" == wasm* ]]; then
|
||||
system_name="WASM"
|
||||
cmake="emcmake cmake"
|
||||
make="emmake make"
|
||||
export_args=()
|
||||
cmake_args+=(
|
||||
-DCMAKE_FIND_ROOT_PATH="${prefix}"
|
||||
-DCMAKE_PREFIX_PATH="${prefix}"
|
||||
)
|
||||
# Static and shared library link to the same file -> race condition.
|
||||
nproc=1
|
||||
else
|
||||
nproc=`nproc --all`
|
||||
fi
|
||||
cmake_args+=(-DCMAKE_SYSTEM_NAME="${system_name}")
|
||||
|
||||
if [[ "${target}" != "x86_64-linux-gnu" ]]; then
|
||||
# Cross-compiling.
|
||||
cmake_args+=(
|
||||
-DCMAKE_C_COMPILER_TARGET="${target}"
|
||||
-DCMAKE_CXX_COMPILER_TARGET="${target}"
|
||||
-DCMAKE_SYSTEM_PROCESSOR="${target%%-*}"
|
||||
)
|
||||
fi
|
||||
|
||||
if [[ -e "${srcdir}/CMakeLists.txt" ]]; then
|
||||
# Most packages use cmake for building which is easier to configure for
|
||||
# cross-compiling.
|
||||
if [[ "${package}" == "JPEG_TURBO" && "${target}" == wasm* ]]; then
|
||||
# JT erroneously detects WASM CPU as i386 and tries to use asm.
|
||||
# Wasm/Emscripten support for dynamic linking is incomplete; disable
|
||||
# to avoid CMake warning.
|
||||
cmake_args+=(-DWITH_SIMD=0 -DENABLE_SHARED=OFF)
|
||||
fi
|
||||
(
|
||||
cd "${srcdir}"
|
||||
export ${export_args[@]}
|
||||
${cmake} \
|
||||
-DCMAKE_INSTALL_PREFIX="${prefix}" \
|
||||
"${cmake_args[@]}" ${pkgflags}
|
||||
${make} -j${nproc}
|
||||
${make} install
|
||||
)
|
||||
elif [[ "${package}" == "GIFLIB" ]]; then
|
||||
# GIFLIB doesn't yet have a cmake build system. There is a pull
|
||||
# request in giflib for adding CMakeLists.txt so this might not be
|
||||
# needed in the future.
|
||||
(
|
||||
cd "${srcdir}"
|
||||
local giflib_make_flags=(
|
||||
CFLAGS="-O2 --target=${target} -std=gnu99"
|
||||
PREFIX="${prefix}"
|
||||
)
|
||||
if [[ "${target}" != wasm* ]]; then
|
||||
giflib_make_flags+=(CC=clang-7)
|
||||
fi
|
||||
# giflib make dependencies are not properly set up so parallel building
|
||||
# doesn't work for everything.
|
||||
${make} -j${nproc} libgif.a "${giflib_make_flags[@]}"
|
||||
${make} -j${nproc} all "${giflib_make_flags[@]}"
|
||||
${make} install "${giflib_make_flags[@]}"
|
||||
)
|
||||
else
|
||||
echo "Don't know how to install ${package}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# CMake mistakenly uses ".so" libraries and EMCC fails to link properly.
|
||||
if [[ "${target}" == wasm* ]]; then
|
||||
rm -f "${prefix}/lib"/*.so*
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# Packages that are manually unpacked for each architecture.
|
||||
UNPACK_PKGS=(
|
||||
libgif-dev
|
||||
libclang-common-7-dev
|
||||
|
||||
# For OpenEXR:
|
||||
libilmbase-dev
|
||||
libopenexr-dev
|
||||
|
||||
# TCMalloc
|
||||
libgoogle-perftools-dev
|
||||
libtcmalloc-minimal4
|
||||
libgoogle-perftools4
|
||||
)
|
||||
|
||||
# Main script entry point.
|
||||
main() {
|
||||
cd "${MYDIR}"
|
||||
|
||||
# Configure the repositories with the sources for multi-arch cross
|
||||
# compilation.
|
||||
setup_apt
|
||||
apt-get update -y
|
||||
apt-get dist-upgrade -y
|
||||
|
||||
install_pkgs
|
||||
install_binutils
|
||||
apt clean
|
||||
|
||||
# Remove prebuilt Java classes cache.
|
||||
rm /usr/lib/jvm/java-8-openjdk-amd64/jre/lib/amd64/server/classes.jsa
|
||||
|
||||
# Manually extract packages for the target arch that can't install it directly
|
||||
# at the same time as the native ones.
|
||||
local ubarch
|
||||
for ubarch in "${LIST_ARCHS[@]}"; do
|
||||
if [[ "${ubarch}" != "amd64" ]]; then
|
||||
local pkg
|
||||
for pkg in "${UNPACK_PKGS[@]}"; do
|
||||
apt download "${pkg}":"${ubarch}"
|
||||
dpkg -x "${pkg}"_*_"${ubarch}".deb /
|
||||
done
|
||||
fi
|
||||
done
|
||||
# TODO: Add clang from the llvm repos. This is problematic since we are
|
||||
# installing libclang-common-7-dev:"${ubarch}" from the ubuntu ports repos
|
||||
# which is not available in the llvm repos so it might have a different
|
||||
# version than the ubuntu ones.
|
||||
|
||||
# Remove the win32 libgcc version. The gcc-mingw-w64-x86-64 (and i686)
|
||||
# packages install two libgcc versions:
|
||||
# /usr/lib/gcc/x86_64-w64-mingw32/7.3-posix
|
||||
# /usr/lib/gcc/x86_64-w64-mingw32/7.3-win32
|
||||
# (exact libgcc version number depends on the package version).
|
||||
#
|
||||
# Clang will pick the best libgcc, sorting by version, but it doesn't
|
||||
# seem to be a way to specify one or the other one, except by passing
|
||||
# -nostdlib and setting all the include paths from the command line.
|
||||
# To check which one is being used you can run:
|
||||
# clang++-7 --target=x86_64-w64-mingw32 -v -print-libgcc-file-name
|
||||
# We need to use the "posix" versions for thread support, so here we
|
||||
# just remove the other one.
|
||||
local target
|
||||
for target in "${LIST_MINGW_TARGETS[@]}"; do
|
||||
update-alternatives --set "${target}-gcc" $(which "${target}-gcc-posix")
|
||||
local gcc_win32_path=$("${target}-cpp-win32" -print-libgcc-file-name)
|
||||
rm -rf $(dirname "${gcc_win32_path}")
|
||||
done
|
||||
|
||||
# TODO: Add msan for the target when cross-compiling. This only installs it
|
||||
# for amd64.
|
||||
./msan_install.sh
|
||||
|
||||
# Build and install qemu user-linux targets.
|
||||
./qemu_install.sh
|
||||
|
||||
# Install emscripten SDK.
|
||||
./emsdk_install.sh
|
||||
|
||||
# Setup environment for building WASM libraries from sources.
|
||||
source /opt/emsdk/emsdk_env.sh
|
||||
|
||||
# Install some dependency libraries manually for the different targets.
|
||||
|
||||
install_from_source JPEG_TURBO "${LIST_MINGW_TARGETS[@]}" "${LIST_WASM_TARGETS[@]}"
|
||||
install_from_source ZLIB "${LIST_MINGW_TARGETS[@]}" "${LIST_WASM_TARGETS[@]}"
|
||||
install_from_source LIBPNG "${LIST_MINGW_TARGETS[@]}" "${LIST_WASM_TARGETS[@]}"
|
||||
install_from_source GIFLIB "${LIST_MINGW_TARGETS[@]}" "${LIST_WASM_TARGETS[@]}"
|
||||
# webp in Ubuntu is relatively old so we install it from source for everybody.
|
||||
install_from_source WEBP "${LIST_TARGETS[@]}" "${LIST_MINGW_TARGETS[@]}"
|
||||
|
||||
install_from_source BENCHMARK "${LIST_TARGETS[@]}" "${LIST_MINGW_TARGETS[@]}"
|
||||
|
||||
# Install v8. v8 has better WASM SIMD support than NodeJS 14 (LTS).
|
||||
# First we need the installer to install v8.
|
||||
npm install jsvu -g
|
||||
# install specific version;
|
||||
HOME=/opt jsvu --os=linux64 "v8@${V8_VERSION}"
|
||||
ln -s "/opt/.jsvu/v8-${V8_VERSION}" "/opt/.jsvu/v8"
|
||||
|
||||
# Cleanup.
|
||||
find /var/lib/apt/lists/ -mindepth 1 -delete
|
||||
}
|
||||
|
||||
main "$@"
|
||||
131
thirdparty/SDL3_image/external/libjxl/docker/scripts/msan_install.sh
vendored
Executable file
131
thirdparty/SDL3_image/external/libjxl/docker/scripts/msan_install.sh
vendored
Executable file
|
|
@ -0,0 +1,131 @@
|
|||
#!/usr/bin/env bash
|
||||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
set -eu
|
||||
|
||||
MYDIR=$(dirname $(realpath "$0"))
|
||||
|
||||
# Convenience flag to pass both CMAKE_C_FLAGS and CMAKE_CXX_FLAGS
|
||||
CMAKE_FLAGS=${CMAKE_FLAGS:-}
|
||||
CMAKE_C_FLAGS=${CMAKE_C_FLAGS:-${CMAKE_FLAGS}}
|
||||
CMAKE_CXX_FLAGS=${CMAKE_CXX_FLAGS:-${CMAKE_FLAGS}}
|
||||
CMAKE_EXE_LINKER_FLAGS=${CMAKE_EXE_LINKER_FLAGS:-}
|
||||
|
||||
CLANG_VERSION="${CLANG_VERSION:-}"
|
||||
# Detect the clang version suffix and store it in CLANG_VERSION. For example,
|
||||
# "6.0" for clang 6 or "7" for clang 7.
|
||||
detect_clang_version() {
|
||||
if [[ -n "${CLANG_VERSION}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
local clang_version=$("${CC:-clang}" --version | head -n1)
|
||||
local llvm_tag
|
||||
case "${clang_version}" in
|
||||
"clang version 6."*)
|
||||
CLANG_VERSION="6.0"
|
||||
;;
|
||||
"clang version 14."*)
|
||||
CLANG_VERSION="14"
|
||||
;;
|
||||
"clang version 8."*)
|
||||
CLANG_VERSION="8"
|
||||
;;
|
||||
"clang version 9."*)
|
||||
CLANG_VERSION="9"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown clang version: ${clang_version}" >&2
|
||||
return 1
|
||||
esac
|
||||
}
|
||||
|
||||
# Temporary files cleanup hooks.
|
||||
CLEANUP_FILES=()
|
||||
cleanup() {
|
||||
if [[ ${#CLEANUP_FILES[@]} -ne 0 ]]; then
|
||||
rm -fr "${CLEANUP_FILES[@]}"
|
||||
fi
|
||||
}
|
||||
trap "{ set +x; } 2>/dev/null; cleanup" INT TERM EXIT
|
||||
|
||||
# Install libc++ libraries compiled with msan in the msan_prefix for the current
|
||||
# compiler version.
|
||||
cmd_msan_install() {
|
||||
local tmpdir=$(mktemp -d)
|
||||
CLEANUP_FILES+=("${tmpdir}")
|
||||
# Detect the llvm to install:
|
||||
export CC="${CC:-clang}"
|
||||
export CXX="${CXX:-clang++}"
|
||||
detect_clang_version
|
||||
local llvm_tag
|
||||
case "${CLANG_VERSION}" in
|
||||
"6.0")
|
||||
llvm_tag="llvmorg-6.0.1"
|
||||
;;
|
||||
"7")
|
||||
llvm_tag="llvmorg-7.0.1"
|
||||
;;
|
||||
"8")
|
||||
llvm_tag="llvmorg-8.0.0"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown clang version: ${clang_version}" >&2
|
||||
return 1
|
||||
esac
|
||||
local llvm_targz="${tmpdir}/${llvm_tag}.tar.gz"
|
||||
curl -L --show-error -o "${llvm_targz}" \
|
||||
"https://github.com/llvm/llvm-project/archive/${llvm_tag}.tar.gz"
|
||||
tar -C "${tmpdir}" -zxf "${llvm_targz}"
|
||||
local llvm_root="${tmpdir}/llvm-project-${llvm_tag}"
|
||||
|
||||
local msan_prefix="${HOME}/.msan/${CLANG_VERSION}"
|
||||
rm -rf "${msan_prefix}"
|
||||
|
||||
declare -A CMAKE_EXTRAS
|
||||
CMAKE_EXTRAS[libcxx]="\
|
||||
-DLIBCXX_CXX_ABI=libstdc++ \
|
||||
-DLIBCXX_INSTALL_EXPERIMENTAL_LIBRARY=ON"
|
||||
|
||||
for project in libcxx; do
|
||||
local proj_build="${tmpdir}/build-${project}"
|
||||
local proj_dir="${llvm_root}/${project}"
|
||||
mkdir -p "${proj_build}"
|
||||
cmake -B"${proj_build}" -H"${proj_dir}" \
|
||||
-G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DLLVM_USE_SANITIZER=Memory \
|
||||
-DLLVM_PATH="${llvm_root}/llvm" \
|
||||
-DLLVM_CONFIG_PATH="$(which llvm-config llvm-config-7 llvm-config-6.0 | \
|
||||
head -n1)" \
|
||||
-DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS}" \
|
||||
-DCMAKE_C_FLAGS="${CMAKE_C_FLAGS}" \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="${CMAKE_EXE_LINKER_FLAGS}" \
|
||||
-DCMAKE_INSTALL_PREFIX="${msan_prefix}" \
|
||||
${CMAKE_EXTRAS[${project}]}
|
||||
cmake --build "${proj_build}"
|
||||
ninja -C "${proj_build}" install
|
||||
done
|
||||
}
|
||||
|
||||
main() {
|
||||
set -x
|
||||
for version in 6.0 7 8; do
|
||||
if ! which "clang-${version}" >/dev/null; then
|
||||
echo "Skipping msan install for clang version ${version}"
|
||||
continue
|
||||
fi
|
||||
(
|
||||
trap "{ set +x; } 2>/dev/null; cleanup" INT TERM EXIT
|
||||
export CLANG_VERSION=${version}
|
||||
export CC=clang-${version}
|
||||
export CXX=clang++-${version}
|
||||
cmd_msan_install
|
||||
) &
|
||||
done
|
||||
wait
|
||||
}
|
||||
|
||||
main "$@"
|
||||
83
thirdparty/SDL3_image/external/libjxl/docker/scripts/qemu_install.sh
vendored
Executable file
83
thirdparty/SDL3_image/external/libjxl/docker/scripts/qemu_install.sh
vendored
Executable file
|
|
@ -0,0 +1,83 @@
|
|||
#!/usr/bin/env bash
|
||||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
QEMU_RELEASE="4.1.0"
|
||||
QEMU_URL="https://download.qemu.org/qemu-${QEMU_RELEASE}.tar.xz"
|
||||
QEMU_ARCHS=(
|
||||
aarch64
|
||||
arm
|
||||
i386
|
||||
# TODO: Consider adding these:
|
||||
# aarch64_be
|
||||
# mips64el
|
||||
# mips64
|
||||
# mips
|
||||
# ppc64
|
||||
# ppc
|
||||
)
|
||||
|
||||
# Ubuntu packages not installed that are needed to build qemu.
|
||||
QEMU_BUILD_DEPS=(
|
||||
libglib2.0-dev
|
||||
libpixman-1-dev
|
||||
flex
|
||||
bison
|
||||
)
|
||||
|
||||
set -eu -x
|
||||
|
||||
# Temporary files cleanup hooks.
|
||||
CLEANUP_FILES=()
|
||||
cleanup() {
|
||||
if [[ ${#CLEANUP_FILES[@]} -ne 0 ]]; then
|
||||
rm -fr "${CLEANUP_FILES[@]}"
|
||||
fi
|
||||
}
|
||||
trap "{ set +x; } 2>/dev/null; cleanup" INT TERM EXIT
|
||||
|
||||
main() {
|
||||
local workdir=$(mktemp -d --suffix=qemu)
|
||||
CLEANUP_FILES+=("${workdir}")
|
||||
|
||||
apt install -y "${QEMU_BUILD_DEPS[@]}"
|
||||
|
||||
local qemutar="${workdir}/qemu.tar.gz"
|
||||
curl --output "${qemutar}" "${QEMU_URL}"
|
||||
tar -Jxf "${qemutar}" -C "${workdir}"
|
||||
local srcdir="${workdir}/qemu-${QEMU_RELEASE}"
|
||||
|
||||
local builddir="${workdir}/build"
|
||||
local prefixdir="${workdir}/prefix"
|
||||
mkdir -p "${builddir}"
|
||||
|
||||
# List of targets to build.
|
||||
local targets=""
|
||||
local make_targets=()
|
||||
local target
|
||||
for target in "${QEMU_ARCHS[@]}"; do
|
||||
targets="${targets} ${target}-linux-user"
|
||||
# Build just the linux-user targets.
|
||||
make_targets+=("${target}-linux-user/all")
|
||||
done
|
||||
|
||||
cd "${builddir}"
|
||||
"${srcdir}/configure" \
|
||||
--prefix="${prefixdir}" \
|
||||
--static --disable-system --enable-linux-user \
|
||||
--target-list="${targets}"
|
||||
|
||||
make -j $(nproc --all || echo 1) "${make_targets[@]}"
|
||||
|
||||
# Manually install these into the non-standard location. This script runs as
|
||||
# root anyway.
|
||||
for target in "${QEMU_ARCHS[@]}"; do
|
||||
cp "${target}-linux-user/qemu-${target}" "/usr/bin/qemu-${target}-static"
|
||||
done
|
||||
|
||||
apt autoremove -y --purge "${QEMU_BUILD_DEPS[@]}"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
56
thirdparty/SDL3_image/external/libjxl/examples/CMakeLists.txt
vendored
Normal file
56
thirdparty/SDL3_image/external/libjxl/examples/CMakeLists.txt
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
# Example project using libjxl.
|
||||
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
project(SAMPLE_LIBJXL LANGUAGES C CXX)
|
||||
|
||||
# Use pkg-config to find libjxl.
|
||||
find_package(PkgConfig)
|
||||
pkg_check_modules(Jxl REQUIRED IMPORTED_TARGET libjxl)
|
||||
pkg_check_modules(JxlThreads REQUIRED IMPORTED_TARGET libjxl_threads)
|
||||
|
||||
# Build the example encoder/decoder binaries using the default shared libraries
|
||||
# installed.
|
||||
add_executable(decode_oneshot decode_oneshot.cc)
|
||||
target_link_libraries(decode_oneshot PkgConfig::Jxl PkgConfig::JxlThreads)
|
||||
|
||||
add_executable(decode_progressive decode_progressive.cc)
|
||||
target_link_libraries(decode_progressive PkgConfig::Jxl PkgConfig::JxlThreads)
|
||||
|
||||
add_executable(encode_oneshot encode_oneshot.cc)
|
||||
target_link_libraries(encode_oneshot PkgConfig::Jxl PkgConfig::JxlThreads)
|
||||
|
||||
|
||||
# Building a static binary with the static libjxl dependencies. How to load
|
||||
# static library configs from pkg-config and how to build static binaries
|
||||
# depends on the platform, and building static binaries in general has problems.
|
||||
# If you don't need static binaries you can remove this section.
|
||||
add_library(StaticJxl INTERFACE IMPORTED GLOBAL)
|
||||
set_target_properties(StaticJxl PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${Jxl_STATIC_INCLUDE_DIR}"
|
||||
INTERFACE_COMPILE_OPTIONS "${Jxl_STATIC_CFLAGS_OTHER}"
|
||||
INTERFACE_LINK_LIBRARIES "${Jxl_STATIC_LDFLAGS}"
|
||||
)
|
||||
add_library(StaticJxlThreads INTERFACE IMPORTED GLOBAL)
|
||||
set_target_properties(StaticJxlThreads PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${JxlThreads_STATIC_INCLUDE_DIR}"
|
||||
INTERFACE_COMPILE_OPTIONS "${JxlThreads_STATIC_CFLAGS_OTHER}"
|
||||
# libgcc uses weak symbols for pthread which means that -lpthread is not
|
||||
# linked when compiling a static binary. This is a platform-specific fix for
|
||||
# that.
|
||||
INTERFACE_LINK_LIBRARIES
|
||||
"${JxlThreads_STATIC_LDFLAGS} -Wl,--whole-archive -lpthread -Wl,--no-whole-archive"
|
||||
)
|
||||
|
||||
add_executable(decode_oneshot_static decode_oneshot.cc)
|
||||
target_link_libraries(decode_oneshot_static
|
||||
-static StaticJxl StaticJxlThreads)
|
||||
|
||||
add_executable(encode_oneshot_static encode_oneshot.cc)
|
||||
target_link_libraries(encode_oneshot_static
|
||||
-static StaticJxl StaticJxlThreads)
|
||||
173
thirdparty/SDL3_image/external/libjxl/examples/decode_exif_metadata.cc
vendored
Normal file
173
thirdparty/SDL3_image/external/libjxl/examples/decode_exif_metadata.cc
vendored
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This C++ example decodes a JPEG XL image in one shot (all input bytes
|
||||
// available at once). The example outputs the pixels and color information to a
|
||||
// floating point image and an ICC profile on disk.
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/decode.h"
|
||||
#include "jxl/decode_cxx.h"
|
||||
|
||||
bool DecodeJpegXlExif(const uint8_t* jxl, size_t size,
|
||||
std::vector<uint8_t>* exif) {
|
||||
auto dec = JxlDecoderMake(nullptr);
|
||||
|
||||
// We're only interested in the Exif boxes in this example, so don't
|
||||
// subscribe to events related to pixel data.
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BOX)) {
|
||||
fprintf(stderr, "JxlDecoderSubscribeEvents failed\n");
|
||||
return false;
|
||||
}
|
||||
bool support_decompression = true;
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderSetDecompressBoxes(dec.get(), JXL_TRUE)) {
|
||||
fprintf(stderr,
|
||||
"NOTE: decompressing brob boxes not supported with the currently "
|
||||
"used jxl library.\n");
|
||||
support_decompression = false;
|
||||
}
|
||||
|
||||
JxlDecoderSetInput(dec.get(), jxl, size);
|
||||
JxlDecoderCloseInput(dec.get());
|
||||
|
||||
const constexpr size_t kChunkSize = 65536;
|
||||
size_t output_pos = 0;
|
||||
|
||||
for (;;) {
|
||||
JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
|
||||
if (status == JXL_DEC_ERROR) {
|
||||
fprintf(stderr, "Decoder error\n");
|
||||
return false;
|
||||
} else if (status == JXL_DEC_NEED_MORE_INPUT) {
|
||||
fprintf(stderr, "Error, already provided all input\n");
|
||||
return false;
|
||||
} else if (status == JXL_DEC_BOX) {
|
||||
if (!exif->empty()) {
|
||||
size_t remaining = JxlDecoderReleaseBoxBuffer(dec.get());
|
||||
exif->resize(exif->size() - remaining);
|
||||
// No need to wait for JXL_DEC_SUCCESS or decode other boxes.
|
||||
return true;
|
||||
}
|
||||
JxlBoxType type;
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderGetBoxType(dec.get(), type, support_decompression)) {
|
||||
fprintf(stderr, "Error, failed to get box type\n");
|
||||
return false;
|
||||
}
|
||||
if (!memcmp(type, "Exif", 4)) {
|
||||
exif->resize(kChunkSize);
|
||||
JxlDecoderSetBoxBuffer(dec.get(), exif->data(), exif->size());
|
||||
}
|
||||
} else if (status == JXL_DEC_BOX_NEED_MORE_OUTPUT) {
|
||||
size_t remaining = JxlDecoderReleaseBoxBuffer(dec.get());
|
||||
output_pos += kChunkSize - remaining;
|
||||
exif->resize(exif->size() + kChunkSize);
|
||||
JxlDecoderSetBoxBuffer(dec.get(), exif->data() + output_pos,
|
||||
exif->size() - output_pos);
|
||||
} else if (status == JXL_DEC_SUCCESS) {
|
||||
if (!exif->empty()) {
|
||||
size_t remaining = JxlDecoderReleaseBoxBuffer(dec.get());
|
||||
exif->resize(exif->size() - remaining);
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
fprintf(stderr, "Unknown decoder status\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool LoadFile(const char* filename, std::vector<uint8_t>* out) {
|
||||
FILE* file = fopen(filename, "rb");
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fseek(file, 0, SEEK_END) != 0) {
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
long size = ftell(file);
|
||||
// Avoid invalid file or directory.
|
||||
if (size >= LONG_MAX || size < 0) {
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fseek(file, 0, SEEK_SET) != 0) {
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
out->resize(size);
|
||||
size_t readsize = fread(out->data(), 1, size, file);
|
||||
if (fclose(file) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return readsize == static_cast<size_t>(size);
|
||||
}
|
||||
|
||||
bool WriteFile(const char* filename, const uint8_t* data, size_t size) {
|
||||
FILE* file = fopen(filename, "wb");
|
||||
if (!file) {
|
||||
fprintf(stderr, "Could not open %s for writing", filename);
|
||||
return false;
|
||||
}
|
||||
fwrite(data, 1, size, file);
|
||||
if (fclose(file) != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc != 3) {
|
||||
fprintf(stderr,
|
||||
"Usage: %s <jxl> <exif>\n"
|
||||
"Where:\n"
|
||||
" jxl = input JPEG XL image filename\n"
|
||||
" exif = output exif filename\n"
|
||||
"Output files will be overwritten.\n",
|
||||
argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char* jxl_filename = argv[1];
|
||||
const char* exif_filename = argv[2];
|
||||
|
||||
std::vector<uint8_t> jxl;
|
||||
if (!LoadFile(jxl_filename, &jxl)) {
|
||||
fprintf(stderr, "couldn't load %s\n", jxl_filename);
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> exif;
|
||||
if (!DecodeJpegXlExif(jxl.data(), jxl.size(), &exif)) {
|
||||
fprintf(stderr, "Error while decoding the jxl file\n");
|
||||
return 1;
|
||||
}
|
||||
if (exif.empty()) {
|
||||
printf("No exif data present in this image\n");
|
||||
} else {
|
||||
// TODO(lode): the exif box data contains the 4-byte TIFF header at the
|
||||
// beginning, check whether this is desired to be part of the output, or
|
||||
// should be removed.
|
||||
if (!WriteFile(exif_filename, exif.data(), exif.size())) {
|
||||
fprintf(stderr, "Error while writing the exif file\n");
|
||||
return 1;
|
||||
}
|
||||
printf("Successfully wrote %s\n", exif_filename);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
248
thirdparty/SDL3_image/external/libjxl/examples/decode_oneshot.cc
vendored
Normal file
248
thirdparty/SDL3_image/external/libjxl/examples/decode_oneshot.cc
vendored
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This C++ example decodes a JPEG XL image in one shot (all input bytes
|
||||
// available at once). The example outputs the pixels and color information to a
|
||||
// floating point image and an ICC profile on disk.
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/decode.h"
|
||||
#include "jxl/decode_cxx.h"
|
||||
#include "jxl/resizable_parallel_runner.h"
|
||||
#include "jxl/resizable_parallel_runner_cxx.h"
|
||||
|
||||
/** Decodes JPEG XL image to floating point pixels and ICC Profile. Pixel are
|
||||
* stored as floating point, as interleaved RGBA (4 floating point values per
|
||||
* pixel), line per line from top to bottom. Pixel values have nominal range
|
||||
* 0..1 but may go beyond this range for HDR or wide gamut. The ICC profile
|
||||
* describes the color format of the pixel data.
|
||||
*/
|
||||
bool DecodeJpegXlOneShot(const uint8_t* jxl, size_t size,
|
||||
std::vector<float>* pixels, size_t* xsize,
|
||||
size_t* ysize, std::vector<uint8_t>* icc_profile) {
|
||||
// Multi-threaded parallel runner.
|
||||
auto runner = JxlResizableParallelRunnerMake(nullptr);
|
||||
|
||||
auto dec = JxlDecoderMake(nullptr);
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BASIC_INFO |
|
||||
JXL_DEC_COLOR_ENCODING |
|
||||
JXL_DEC_FULL_IMAGE)) {
|
||||
fprintf(stderr, "JxlDecoderSubscribeEvents failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(),
|
||||
JxlResizableParallelRunner,
|
||||
runner.get())) {
|
||||
fprintf(stderr, "JxlDecoderSetParallelRunner failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
JxlBasicInfo info;
|
||||
JxlPixelFormat format = {4, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0};
|
||||
|
||||
JxlDecoderSetInput(dec.get(), jxl, size);
|
||||
JxlDecoderCloseInput(dec.get());
|
||||
|
||||
for (;;) {
|
||||
JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
|
||||
|
||||
if (status == JXL_DEC_ERROR) {
|
||||
fprintf(stderr, "Decoder error\n");
|
||||
return false;
|
||||
} else if (status == JXL_DEC_NEED_MORE_INPUT) {
|
||||
fprintf(stderr, "Error, already provided all input\n");
|
||||
return false;
|
||||
} else if (status == JXL_DEC_BASIC_INFO) {
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &info)) {
|
||||
fprintf(stderr, "JxlDecoderGetBasicInfo failed\n");
|
||||
return false;
|
||||
}
|
||||
*xsize = info.xsize;
|
||||
*ysize = info.ysize;
|
||||
JxlResizableParallelRunnerSetThreads(
|
||||
runner.get(),
|
||||
JxlResizableParallelRunnerSuggestThreads(info.xsize, info.ysize));
|
||||
} else if (status == JXL_DEC_COLOR_ENCODING) {
|
||||
// Get the ICC color profile of the pixel data
|
||||
size_t icc_size;
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderGetICCProfileSize(
|
||||
dec.get(), &format, JXL_COLOR_PROFILE_TARGET_DATA, &icc_size)) {
|
||||
fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n");
|
||||
return false;
|
||||
}
|
||||
icc_profile->resize(icc_size);
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsICCProfile(
|
||||
dec.get(), &format,
|
||||
JXL_COLOR_PROFILE_TARGET_DATA,
|
||||
icc_profile->data(), icc_profile->size())) {
|
||||
fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n");
|
||||
return false;
|
||||
}
|
||||
} else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
|
||||
size_t buffer_size;
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderImageOutBufferSize(dec.get(), &format, &buffer_size)) {
|
||||
fprintf(stderr, "JxlDecoderImageOutBufferSize failed\n");
|
||||
return false;
|
||||
}
|
||||
if (buffer_size != *xsize * *ysize * 16) {
|
||||
fprintf(stderr, "Invalid out buffer size %" PRIu64 " %" PRIu64 "\n",
|
||||
static_cast<uint64_t>(buffer_size),
|
||||
static_cast<uint64_t>(*xsize * *ysize * 16));
|
||||
return false;
|
||||
}
|
||||
pixels->resize(*xsize * *ysize * 4);
|
||||
void* pixels_buffer = (void*)pixels->data();
|
||||
size_t pixels_buffer_size = pixels->size() * sizeof(float);
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec.get(), &format,
|
||||
pixels_buffer,
|
||||
pixels_buffer_size)) {
|
||||
fprintf(stderr, "JxlDecoderSetImageOutBuffer failed\n");
|
||||
return false;
|
||||
}
|
||||
} else if (status == JXL_DEC_FULL_IMAGE) {
|
||||
// Nothing to do. Do not yet return. If the image is an animation, more
|
||||
// full frames may be decoded. This example only keeps the last one.
|
||||
} else if (status == JXL_DEC_SUCCESS) {
|
||||
// All decoding successfully finished.
|
||||
// It's not required to call JxlDecoderReleaseInput(dec.get()) here since
|
||||
// the decoder will be destroyed.
|
||||
return true;
|
||||
} else {
|
||||
fprintf(stderr, "Unknown decoder status\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Writes to .pfm file (Portable FloatMap). Gimp, tev viewer and ImageMagick
|
||||
* support viewing this format.
|
||||
* The input pixels are given as 32-bit floating point with 4-channel RGBA.
|
||||
* The alpha channel will not be written since .pfm does not support it.
|
||||
*/
|
||||
bool WritePFM(const char* filename, const float* pixels, size_t xsize,
|
||||
size_t ysize) {
|
||||
FILE* file = fopen(filename, "wb");
|
||||
if (!file) {
|
||||
fprintf(stderr, "Could not open %s for writing", filename);
|
||||
return false;
|
||||
}
|
||||
uint32_t endian_test = 1;
|
||||
uint8_t little_endian[4];
|
||||
memcpy(little_endian, &endian_test, 4);
|
||||
|
||||
fprintf(file, "PF\n%d %d\n%s\n", (int)xsize, (int)ysize,
|
||||
little_endian[0] ? "-1.0" : "1.0");
|
||||
for (int y = ysize - 1; y >= 0; y--) {
|
||||
for (size_t x = 0; x < xsize; x++) {
|
||||
for (size_t c = 0; c < 3; c++) {
|
||||
const float* f = &pixels[(y * xsize + x) * 4 + c];
|
||||
fwrite(f, 4, 1, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (fclose(file) != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadFile(const char* filename, std::vector<uint8_t>* out) {
|
||||
FILE* file = fopen(filename, "rb");
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fseek(file, 0, SEEK_END) != 0) {
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
long size = ftell(file);
|
||||
// Avoid invalid file or directory.
|
||||
if (size >= LONG_MAX || size < 0) {
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fseek(file, 0, SEEK_SET) != 0) {
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
out->resize(size);
|
||||
size_t readsize = fread(out->data(), 1, size, file);
|
||||
if (fclose(file) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return readsize == static_cast<size_t>(size);
|
||||
}
|
||||
|
||||
bool WriteFile(const char* filename, const uint8_t* data, size_t size) {
|
||||
FILE* file = fopen(filename, "wb");
|
||||
if (!file) {
|
||||
fprintf(stderr, "Could not open %s for writing", filename);
|
||||
return false;
|
||||
}
|
||||
fwrite(data, 1, size, file);
|
||||
if (fclose(file) != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc != 4) {
|
||||
fprintf(stderr,
|
||||
"Usage: %s <jxl> <pfm> <icc>\n"
|
||||
"Where:\n"
|
||||
" jxl = input JPEG XL image filename\n"
|
||||
" pfm = output Portable FloatMap image filename\n"
|
||||
" icc = output ICC color profile filename\n"
|
||||
"Output files will be overwritten.\n",
|
||||
argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char* jxl_filename = argv[1];
|
||||
const char* pfm_filename = argv[2];
|
||||
const char* icc_filename = argv[3];
|
||||
|
||||
std::vector<uint8_t> jxl;
|
||||
if (!LoadFile(jxl_filename, &jxl)) {
|
||||
fprintf(stderr, "couldn't load %s\n", jxl_filename);
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::vector<float> pixels;
|
||||
std::vector<uint8_t> icc_profile;
|
||||
size_t xsize = 0, ysize = 0;
|
||||
if (!DecodeJpegXlOneShot(jxl.data(), jxl.size(), &pixels, &xsize, &ysize,
|
||||
&icc_profile)) {
|
||||
fprintf(stderr, "Error while decoding the jxl file\n");
|
||||
return 1;
|
||||
}
|
||||
if (!WritePFM(pfm_filename, pixels.data(), xsize, ysize)) {
|
||||
fprintf(stderr, "Error while writing the PFM image file\n");
|
||||
return 1;
|
||||
}
|
||||
if (!WriteFile(icc_filename, icc_profile.data(), icc_profile.size())) {
|
||||
fprintf(stderr, "Error while writing the ICC profile file\n");
|
||||
return 1;
|
||||
}
|
||||
printf("Successfully wrote %s and %s\n", pfm_filename, icc_filename);
|
||||
return 0;
|
||||
}
|
||||
238
thirdparty/SDL3_image/external/libjxl/examples/decode_progressive.cc
vendored
Normal file
238
thirdparty/SDL3_image/external/libjxl/examples/decode_progressive.cc
vendored
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This C++ example decodes a JPEG XL image progressively (input bytes are
|
||||
// passed in chunks). The example outputs the intermediate steps to PAM files.
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/decode.h"
|
||||
#include "jxl/decode_cxx.h"
|
||||
#include "jxl/resizable_parallel_runner.h"
|
||||
#include "jxl/resizable_parallel_runner_cxx.h"
|
||||
|
||||
bool WritePAM(const char* filename, const uint8_t* buffer, size_t w, size_t h) {
|
||||
FILE* fp = fopen(filename, "wb");
|
||||
if (!fp) {
|
||||
fprintf(stderr, "Could not open %s for writing", filename);
|
||||
return false;
|
||||
}
|
||||
fprintf(fp,
|
||||
"P7\nWIDTH %" PRIu64 "\nHEIGHT %" PRIu64
|
||||
"\nDEPTH 4\nMAXVAL 255\nTUPLTYPE "
|
||||
"RGB_ALPHA\nENDHDR\n",
|
||||
static_cast<uint64_t>(w), static_cast<uint64_t>(h));
|
||||
fwrite(buffer, 1, w * h * 4, fp);
|
||||
if (fclose(fp) != 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Decodes JPEG XL image to 8-bit integer RGBA pixels and an ICC Profile, in a
|
||||
* progressive way, saving the intermediate steps.
|
||||
*/
|
||||
bool DecodeJpegXlProgressive(const uint8_t* jxl, size_t size,
|
||||
const char* filename, size_t chunksize) {
|
||||
std::vector<uint8_t> pixels;
|
||||
std::vector<uint8_t> icc_profile;
|
||||
size_t xsize = 0, ysize = 0;
|
||||
|
||||
// Multi-threaded parallel runner.
|
||||
auto runner = JxlResizableParallelRunnerMake(nullptr);
|
||||
|
||||
auto dec = JxlDecoderMake(nullptr);
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderSubscribeEvents(dec.get(), JXL_DEC_BASIC_INFO |
|
||||
JXL_DEC_COLOR_ENCODING |
|
||||
JXL_DEC_FULL_IMAGE)) {
|
||||
fprintf(stderr, "JxlDecoderSubscribeEvents failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec.get(),
|
||||
JxlResizableParallelRunner,
|
||||
runner.get())) {
|
||||
fprintf(stderr, "JxlDecoderSetParallelRunner failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
JxlBasicInfo info;
|
||||
JxlPixelFormat format = {4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0};
|
||||
|
||||
size_t seen = 0;
|
||||
JxlDecoderSetInput(dec.get(), jxl, chunksize);
|
||||
size_t remaining = chunksize;
|
||||
|
||||
for (;;) {
|
||||
JxlDecoderStatus status = JxlDecoderProcessInput(dec.get());
|
||||
|
||||
if (status == JXL_DEC_ERROR) {
|
||||
fprintf(stderr, "Decoder error\n");
|
||||
return false;
|
||||
} else if (status == JXL_DEC_NEED_MORE_INPUT || status == JXL_DEC_SUCCESS ||
|
||||
status == JXL_DEC_FULL_IMAGE) {
|
||||
seen += remaining - JxlDecoderReleaseInput(dec.get());
|
||||
printf("Flushing after %" PRIu64 " bytes\n", static_cast<uint64_t>(seen));
|
||||
if (status == JXL_DEC_NEED_MORE_INPUT &&
|
||||
JXL_DEC_SUCCESS != JxlDecoderFlushImage(dec.get())) {
|
||||
printf("flush error (no preview yet)\n");
|
||||
} else {
|
||||
char fname[1024];
|
||||
if (snprintf(fname, 1024, "%s-%" PRIu64 ".pam", filename,
|
||||
static_cast<uint64_t>(seen)) >= 1024) {
|
||||
fprintf(stderr, "Filename too long\n");
|
||||
return false;
|
||||
};
|
||||
if (!WritePAM(fname, pixels.data(), xsize, ysize)) {
|
||||
fprintf(stderr, "Error writing progressive output\n");
|
||||
}
|
||||
}
|
||||
remaining = size - seen;
|
||||
if (remaining > chunksize) remaining = chunksize;
|
||||
if (remaining == 0) {
|
||||
if (status == JXL_DEC_NEED_MORE_INPUT) {
|
||||
fprintf(stderr, "Error, already provided all input\n");
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
JxlDecoderSetInput(dec.get(), jxl + seen, remaining);
|
||||
} else if (status == JXL_DEC_BASIC_INFO) {
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec.get(), &info)) {
|
||||
fprintf(stderr, "JxlDecoderGetBasicInfo failed\n");
|
||||
return false;
|
||||
}
|
||||
xsize = info.xsize;
|
||||
ysize = info.ysize;
|
||||
JxlResizableParallelRunnerSetThreads(
|
||||
runner.get(),
|
||||
JxlResizableParallelRunnerSuggestThreads(info.xsize, info.ysize));
|
||||
} else if (status == JXL_DEC_COLOR_ENCODING) {
|
||||
// Get the ICC color profile of the pixel data
|
||||
size_t icc_size;
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderGetICCProfileSize(dec.get(), &format,
|
||||
JXL_COLOR_PROFILE_TARGET_ORIGINAL,
|
||||
&icc_size)) {
|
||||
fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n");
|
||||
return false;
|
||||
}
|
||||
icc_profile.resize(icc_size);
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsICCProfile(
|
||||
dec.get(), &format,
|
||||
JXL_COLOR_PROFILE_TARGET_ORIGINAL,
|
||||
icc_profile.data(), icc_profile.size())) {
|
||||
fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n");
|
||||
return false;
|
||||
}
|
||||
} else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
|
||||
size_t buffer_size;
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderImageOutBufferSize(dec.get(), &format, &buffer_size)) {
|
||||
fprintf(stderr, "JxlDecoderImageOutBufferSize failed\n");
|
||||
return false;
|
||||
}
|
||||
if (buffer_size != xsize * ysize * 4) {
|
||||
fprintf(stderr, "Invalid out buffer size %" PRIu64 " != %" PRIu64 "\n",
|
||||
static_cast<uint64_t>(buffer_size),
|
||||
static_cast<uint64_t>(xsize * ysize * 4));
|
||||
return false;
|
||||
}
|
||||
pixels.resize(xsize * ysize * 4);
|
||||
void* pixels_buffer = (void*)pixels.data();
|
||||
size_t pixels_buffer_size = pixels.size() * sizeof(float);
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec.get(), &format,
|
||||
pixels_buffer,
|
||||
pixels_buffer_size)) {
|
||||
fprintf(stderr, "JxlDecoderSetImageOutBuffer failed\n");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Unknown decoder status\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool LoadFile(const char* filename, std::vector<uint8_t>* out) {
|
||||
FILE* file = fopen(filename, "rb");
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fseek(file, 0, SEEK_END) != 0) {
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
long size = ftell(file);
|
||||
// Avoid invalid file or directory.
|
||||
if (size >= LONG_MAX || size < 0) {
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fseek(file, 0, SEEK_SET) != 0) {
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
out->resize(size);
|
||||
size_t readsize = fread(out->data(), 1, size, file);
|
||||
if (fclose(file) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return readsize == static_cast<size_t>(size);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc < 3) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"Usage: %s <jxl> <basename> [chunksize]\n"
|
||||
"Where:\n"
|
||||
" jxl = input JPEG XL image filename\n"
|
||||
" basename = prefix of output filenames\n"
|
||||
" chunksize = loads chunksize bytes at a time and writes\n"
|
||||
" intermediate results to basename-[bytes loaded].pam\n"
|
||||
"Output files will be overwritten.\n",
|
||||
argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char* jxl_filename = argv[1];
|
||||
const char* png_filename = argv[2];
|
||||
|
||||
std::vector<uint8_t> jxl;
|
||||
if (!LoadFile(jxl_filename, &jxl)) {
|
||||
fprintf(stderr, "couldn't load %s\n", jxl_filename);
|
||||
return 1;
|
||||
}
|
||||
size_t chunksize = jxl.size();
|
||||
if (argc > 3) {
|
||||
long cs = atol(argv[3]);
|
||||
if (cs < 100) {
|
||||
fprintf(stderr, "Chunk size is too low, try at least 100 bytes\n");
|
||||
return 1;
|
||||
}
|
||||
chunksize = cs;
|
||||
}
|
||||
|
||||
if (!DecodeJpegXlProgressive(jxl.data(), jxl.size(), png_filename,
|
||||
chunksize)) {
|
||||
fprintf(stderr, "Error while decoding the jxl file\n");
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
276
thirdparty/SDL3_image/external/libjxl/examples/encode_oneshot.cc
vendored
Normal file
276
thirdparty/SDL3_image/external/libjxl/examples/encode_oneshot.cc
vendored
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This example encodes a file containing a floating point image to another
|
||||
// file containing JPEG XL image with a single frame.
|
||||
|
||||
#include <limits.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/encode.h"
|
||||
#include "jxl/encode_cxx.h"
|
||||
#include "jxl/thread_parallel_runner.h"
|
||||
#include "jxl/thread_parallel_runner_cxx.h"
|
||||
|
||||
/**
|
||||
* Reads from .pfm file (Portable FloatMap)
|
||||
*
|
||||
* @param filename name of the file to read
|
||||
* @param pixels vector to fill with loaded pixels as 32-bit floating point with
|
||||
* 3-channel RGB
|
||||
* @param xsize set to width of loaded image
|
||||
* @param ysize set to height of loaded image
|
||||
*/
|
||||
bool ReadPFM(const char* filename, std::vector<float>* pixels, uint32_t* xsize,
|
||||
uint32_t* ysize) {
|
||||
FILE* file = fopen(filename, "rb");
|
||||
if (!file) {
|
||||
fprintf(stderr, "Could not open %s for reading.\n", filename);
|
||||
return false;
|
||||
}
|
||||
uint32_t endian_test = 1;
|
||||
uint8_t little_endian[4];
|
||||
memcpy(little_endian, &endian_test, 4);
|
||||
|
||||
if (fseek(file, 0, SEEK_END) != 0) {
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
long size = ftell(file);
|
||||
// Avoid invalid file or directory.
|
||||
if (size >= LONG_MAX || size < 0) {
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fseek(file, 0, SEEK_SET) != 0) {
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<char> data;
|
||||
data.resize(size);
|
||||
|
||||
size_t readsize = fread(data.data(), 1, size, file);
|
||||
if ((long)readsize != size) {
|
||||
return false;
|
||||
}
|
||||
if (fclose(file) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::stringstream datastream;
|
||||
std::string datastream_content(data.data(), data.size());
|
||||
datastream.str(datastream_content);
|
||||
|
||||
std::string pf_token;
|
||||
getline(datastream, pf_token, '\n');
|
||||
if (pf_token != "PF") {
|
||||
fprintf(stderr,
|
||||
"%s doesn't seem to be a 3 channel Portable FloatMap file (missing "
|
||||
"'PF\\n' "
|
||||
"bytes).\n",
|
||||
filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string xsize_token;
|
||||
getline(datastream, xsize_token, ' ');
|
||||
*xsize = std::stoi(xsize_token);
|
||||
|
||||
std::string ysize_token;
|
||||
getline(datastream, ysize_token, '\n');
|
||||
*ysize = std::stoi(ysize_token);
|
||||
|
||||
std::string endianness_token;
|
||||
getline(datastream, endianness_token, '\n');
|
||||
bool input_little_endian;
|
||||
if (endianness_token == "1.0") {
|
||||
input_little_endian = false;
|
||||
} else if (endianness_token == "-1.0") {
|
||||
input_little_endian = true;
|
||||
} else {
|
||||
fprintf(stderr,
|
||||
"%s doesn't seem to be a Portable FloatMap file (endianness token "
|
||||
"isn't '1.0' or '-1.0').\n",
|
||||
filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t offset = pf_token.size() + 1 + xsize_token.size() + 1 +
|
||||
ysize_token.size() + 1 + endianness_token.size() + 1;
|
||||
|
||||
if (data.size() != *ysize * *xsize * 3 * 4 + offset) {
|
||||
fprintf(stderr,
|
||||
"%s doesn't seem to be a Portable FloatMap file (pixel data bytes "
|
||||
"are %d, but expected %d * %d * 3 * 4 + %d (%d).\n",
|
||||
filename, (int)data.size(), (int)*ysize, (int)*xsize, (int)offset,
|
||||
(int)(*ysize * *xsize * 3 * 4 + offset));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!!little_endian[0] != input_little_endian) {
|
||||
fprintf(stderr,
|
||||
"%s has a different endianness than we do, conversion is not "
|
||||
"supported.\n",
|
||||
filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
pixels->resize(*ysize * *xsize * 3);
|
||||
|
||||
for (int y = *ysize - 1; y >= 0; y--) {
|
||||
for (int x = 0; x < (int)*xsize; x++) {
|
||||
for (int c = 0; c < 3; c++) {
|
||||
memcpy(pixels->data() + (y * *xsize + x) * 3 + c, data.data() + offset,
|
||||
sizeof(float));
|
||||
offset += sizeof(float);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compresses the provided pixels.
|
||||
*
|
||||
* @param pixels input pixels
|
||||
* @param xsize width of the input image
|
||||
* @param ysize height of the input image
|
||||
* @param compressed will be populated with the compressed bytes
|
||||
*/
|
||||
bool EncodeJxlOneshot(const std::vector<float>& pixels, const uint32_t xsize,
|
||||
const uint32_t ysize, std::vector<uint8_t>* compressed) {
|
||||
auto enc = JxlEncoderMake(/*memory_manager=*/nullptr);
|
||||
auto runner = JxlThreadParallelRunnerMake(
|
||||
/*memory_manager=*/nullptr,
|
||||
JxlThreadParallelRunnerDefaultNumWorkerThreads());
|
||||
if (JXL_ENC_SUCCESS != JxlEncoderSetParallelRunner(enc.get(),
|
||||
JxlThreadParallelRunner,
|
||||
runner.get())) {
|
||||
fprintf(stderr, "JxlEncoderSetParallelRunner failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
JxlPixelFormat pixel_format = {3, JXL_TYPE_FLOAT, JXL_NATIVE_ENDIAN, 0};
|
||||
|
||||
JxlBasicInfo basic_info;
|
||||
JxlEncoderInitBasicInfo(&basic_info);
|
||||
basic_info.xsize = xsize;
|
||||
basic_info.ysize = ysize;
|
||||
basic_info.bits_per_sample = 32;
|
||||
basic_info.exponent_bits_per_sample = 8;
|
||||
basic_info.uses_original_profile = JXL_FALSE;
|
||||
if (JXL_ENC_SUCCESS != JxlEncoderSetBasicInfo(enc.get(), &basic_info)) {
|
||||
fprintf(stderr, "JxlEncoderSetBasicInfo failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
JxlColorEncoding color_encoding = {};
|
||||
JxlColorEncodingSetToSRGB(&color_encoding,
|
||||
/*is_gray=*/pixel_format.num_channels < 3);
|
||||
if (JXL_ENC_SUCCESS !=
|
||||
JxlEncoderSetColorEncoding(enc.get(), &color_encoding)) {
|
||||
fprintf(stderr, "JxlEncoderSetColorEncoding failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
JxlEncoderFrameSettings* frame_settings =
|
||||
JxlEncoderFrameSettingsCreate(enc.get(), nullptr);
|
||||
|
||||
if (JXL_ENC_SUCCESS !=
|
||||
JxlEncoderAddImageFrame(frame_settings, &pixel_format,
|
||||
(void*)pixels.data(),
|
||||
sizeof(float) * pixels.size())) {
|
||||
fprintf(stderr, "JxlEncoderAddImageFrame failed\n");
|
||||
return false;
|
||||
}
|
||||
JxlEncoderCloseInput(enc.get());
|
||||
|
||||
compressed->resize(64);
|
||||
uint8_t* next_out = compressed->data();
|
||||
size_t avail_out = compressed->size() - (next_out - compressed->data());
|
||||
JxlEncoderStatus process_result = JXL_ENC_NEED_MORE_OUTPUT;
|
||||
while (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
|
||||
process_result = JxlEncoderProcessOutput(enc.get(), &next_out, &avail_out);
|
||||
if (process_result == JXL_ENC_NEED_MORE_OUTPUT) {
|
||||
size_t offset = next_out - compressed->data();
|
||||
compressed->resize(compressed->size() * 2);
|
||||
next_out = compressed->data() + offset;
|
||||
avail_out = compressed->size() - offset;
|
||||
}
|
||||
}
|
||||
compressed->resize(next_out - compressed->data());
|
||||
if (JXL_ENC_SUCCESS != process_result) {
|
||||
fprintf(stderr, "JxlEncoderProcessOutput failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes bytes to file.
|
||||
*/
|
||||
bool WriteFile(const std::vector<uint8_t>& bytes, const char* filename) {
|
||||
FILE* file = fopen(filename, "wb");
|
||||
if (!file) {
|
||||
fprintf(stderr, "Could not open %s for writing\n", filename);
|
||||
return false;
|
||||
}
|
||||
if (fwrite(bytes.data(), sizeof(uint8_t), bytes.size(), file) !=
|
||||
bytes.size()) {
|
||||
fprintf(stderr, "Could not write bytes to %s\n", filename);
|
||||
return false;
|
||||
}
|
||||
if (fclose(file) != 0) {
|
||||
fprintf(stderr, "Could not close %s\n", filename);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (argc != 3) {
|
||||
fprintf(stderr,
|
||||
"Usage: %s <pfm> <jxl>\n"
|
||||
"Where:\n"
|
||||
" pfm = input Portable FloatMap image filename\n"
|
||||
" jxl = output JPEG XL image filename\n"
|
||||
"Output files will be overwritten.\n",
|
||||
argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char* pfm_filename = argv[1];
|
||||
const char* jxl_filename = argv[2];
|
||||
|
||||
std::vector<float> pixels;
|
||||
uint32_t xsize;
|
||||
uint32_t ysize;
|
||||
if (!ReadPFM(pfm_filename, &pixels, &xsize, &ysize)) {
|
||||
fprintf(stderr, "Couldn't load %s\n", pfm_filename);
|
||||
return 2;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> compressed;
|
||||
if (!EncodeJxlOneshot(pixels, xsize, ysize, &compressed)) {
|
||||
fprintf(stderr, "Couldn't encode jxl\n");
|
||||
return 3;
|
||||
}
|
||||
|
||||
if (!WriteFile(compressed, jxl_filename)) {
|
||||
fprintf(stderr, "Couldn't write jxl file\n");
|
||||
return 4;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
11
thirdparty/SDL3_image/external/libjxl/examples/examples.cmake
vendored
Normal file
11
thirdparty/SDL3_image/external/libjxl/examples/examples.cmake
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
add_executable(decode_oneshot ${CMAKE_CURRENT_LIST_DIR}/decode_oneshot.cc)
|
||||
target_link_libraries(decode_oneshot jxl_dec jxl_threads)
|
||||
add_executable(decode_progressive ${CMAKE_CURRENT_LIST_DIR}/decode_progressive.cc)
|
||||
target_link_libraries(decode_progressive jxl_dec jxl_threads)
|
||||
add_executable(encode_oneshot ${CMAKE_CURRENT_LIST_DIR}/encode_oneshot.cc)
|
||||
target_link_libraries(encode_oneshot jxl jxl_threads)
|
||||
1
thirdparty/SDL3_image/external/libjxl/experimental/fast_lossless/.gitignore
vendored
Normal file
1
thirdparty/SDL3_image/external/libjxl/experimental/fast_lossless/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
build/
|
||||
26
thirdparty/SDL3_image/external/libjxl/experimental/fast_lossless/build-android.sh
vendored
Executable file
26
thirdparty/SDL3_image/external/libjxl/experimental/fast_lossless/build-android.sh
vendored
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env bash
|
||||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
set -e
|
||||
|
||||
DIR=$(realpath "$(dirname "$0")")
|
||||
|
||||
mkdir -p /tmp/build-android
|
||||
cd /tmp/build-android
|
||||
|
||||
CXX="$ANDROID_NDK"/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android30-clang++
|
||||
if ! command -v "$CXX" >/dev/null ; then
|
||||
printf >&2 '%s: Android C++ compiler not found, is ANDROID_NDK set properly?\n' "${0##*/}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
[ -f lodepng.cpp ] || curl -o lodepng.cpp --url 'https://raw.githubusercontent.com/lvandeve/lodepng/8c6a9e30576f07bf470ad6f09458a2dcd7a6a84a/lodepng.cpp'
|
||||
[ -f lodepng.h ] || curl -o lodepng.h --url 'https://raw.githubusercontent.com/lvandeve/lodepng/8c6a9e30576f07bf470ad6f09458a2dcd7a6a84a/lodepng.h'
|
||||
[ -f lodepng.o ] || "$CXX" lodepng.cpp -O3 -o lodepng.o -c
|
||||
|
||||
"$CXX" -O3 -DFASTLL_ENABLE_NEON_INTRINSICS -fopenmp \
|
||||
-I. lodepng.o \
|
||||
"${DIR}"/fast_lossless.cc "${DIR}"/fast_lossless_main.cc \
|
||||
-o fast_lossless
|
||||
26
thirdparty/SDL3_image/external/libjxl/experimental/fast_lossless/build.sh
vendored
Executable file
26
thirdparty/SDL3_image/external/libjxl/experimental/fast_lossless/build.sh
vendored
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env bash
|
||||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
set -e
|
||||
|
||||
DIR=$(realpath "$(dirname "$0")")
|
||||
mkdir -p "$DIR"/build
|
||||
cd "$DIR"/build
|
||||
|
||||
# set CXX to clang++ if not set in the environment
|
||||
CXX="${CXX-clang++}"
|
||||
if ! command -v "$CXX" >/dev/null ; then
|
||||
printf >&2 '%s: C++ compiler not found\n' "${0##*/}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
[ -f lodepng.cpp ] || curl -o lodepng.cpp --url 'https://raw.githubusercontent.com/lvandeve/lodepng/8c6a9e30576f07bf470ad6f09458a2dcd7a6a84a/lodepng.cpp'
|
||||
[ -f lodepng.h ] || curl -o lodepng.h --url 'https://raw.githubusercontent.com/lvandeve/lodepng/8c6a9e30576f07bf470ad6f09458a2dcd7a6a84a/lodepng.h'
|
||||
[ -f lodepng.o ] || "$CXX" lodepng.cpp -O3 -mavx2 -o lodepng.o -c
|
||||
|
||||
"$CXX" -O3 -mavx2 -DFASTLL_ENABLE_AVX2_INTRINSICS -fopenmp \
|
||||
-I. lodepng.o \
|
||||
"$DIR"/fast_lossless.cc "$DIR"/fast_lossless_main.cc \
|
||||
-o fast_lossless
|
||||
1362
thirdparty/SDL3_image/external/libjxl/experimental/fast_lossless/fast_lossless.cc
vendored
Normal file
1362
thirdparty/SDL3_image/external/libjxl/experimental/fast_lossless/fast_lossless.cc
vendored
Normal file
File diff suppressed because it is too large
Load diff
14
thirdparty/SDL3_image/external/libjxl/experimental/fast_lossless/fast_lossless.h
vendored
Normal file
14
thirdparty/SDL3_image/external/libjxl/experimental/fast_lossless/fast_lossless.h
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef FAST_LOSSLESS_H
|
||||
#define FAST_LOSSLESS_H
|
||||
#include <stdlib.h>
|
||||
|
||||
size_t FastLosslessEncode(const unsigned char* rgba, size_t width,
|
||||
size_t row_stride, size_t height, size_t nb_chans,
|
||||
size_t bitdepth, int effort, unsigned char** output);
|
||||
|
||||
#endif
|
||||
78
thirdparty/SDL3_image/external/libjxl/experimental/fast_lossless/fast_lossless_main.cc
vendored
Normal file
78
thirdparty/SDL3_image/external/libjxl/experimental/fast_lossless/fast_lossless_main.cc
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#include "fast_lossless.h"
|
||||
#include "lodepng.h"
|
||||
#include "pam-input.h"
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 3) {
|
||||
fprintf(stderr, "Usage: %s in.png out.jxl [effort] [num_reps]\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char* in = argv[1];
|
||||
const char* out = argv[2];
|
||||
int effort = argc >= 4 ? atoi(argv[3]) : 2;
|
||||
size_t num_reps = argc >= 5 ? atoi(argv[4]) : 1;
|
||||
|
||||
if (effort < 0 || effort > 127) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"Effort should be between 0 and 127 (default is 2, more is slower)\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
unsigned char* png;
|
||||
unsigned w, h;
|
||||
size_t nb_chans = 4, bitdepth = 8;
|
||||
|
||||
unsigned error = lodepng_decode32_file(&png, &w, &h, in);
|
||||
|
||||
size_t width = w, height = h;
|
||||
if (error && !DecodePAM(in, &png, &width, &height, &nb_chans, &bitdepth)) {
|
||||
fprintf(stderr, "lodepng error %u: %s\n", error, lodepng_error_text(error));
|
||||
return 1;
|
||||
}
|
||||
|
||||
size_t encoded_size = 0;
|
||||
unsigned char* encoded = nullptr;
|
||||
size_t stride = width * nb_chans * (bitdepth > 8 ? 2 : 1);
|
||||
|
||||
auto start = std::chrono::high_resolution_clock::now();
|
||||
for (size_t _ = 0; _ < num_reps; _++) {
|
||||
free(encoded);
|
||||
encoded_size = FastLosslessEncode(png, width, stride, height, nb_chans,
|
||||
bitdepth, effort, &encoded);
|
||||
}
|
||||
auto stop = std::chrono::high_resolution_clock::now();
|
||||
if (num_reps > 1) {
|
||||
float us =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(stop - start)
|
||||
.count();
|
||||
size_t pixels = size_t{width} * size_t{height} * num_reps;
|
||||
float mps = pixels / us;
|
||||
fprintf(stderr, "%10.3f MP/s\n", mps);
|
||||
fprintf(stderr, "%10.3f bits/pixel\n",
|
||||
encoded_size * 8.0 / float(width) / float(height));
|
||||
}
|
||||
|
||||
FILE* o = fopen(out, "wb");
|
||||
if (!o) {
|
||||
fprintf(stderr, "error opening %s: %s\n", out, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
if (fwrite(encoded, 1, encoded_size, o) != encoded_size) {
|
||||
fprintf(stderr, "error writing to %s: %s\n", out, strerror(errno));
|
||||
}
|
||||
fclose(o);
|
||||
}
|
||||
289
thirdparty/SDL3_image/external/libjxl/experimental/fast_lossless/pam-input.h
vendored
Normal file
289
thirdparty/SDL3_image/external/libjxl/experimental/fast_lossless/pam-input.h
vendored
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
bool error_msg(const char* message) {
|
||||
fprintf(stderr, "%s\n", message);
|
||||
return false;
|
||||
}
|
||||
#define return_on_error(X) \
|
||||
if (!X) return false;
|
||||
|
||||
size_t Log2(uint32_t value) { return 31 - __builtin_clz(value); }
|
||||
|
||||
struct HeaderPNM {
|
||||
size_t xsize;
|
||||
size_t ysize;
|
||||
bool is_gray; // PGM
|
||||
bool has_alpha; // PAM
|
||||
size_t bits_per_sample;
|
||||
};
|
||||
|
||||
class Parser {
|
||||
public:
|
||||
explicit Parser(uint8_t* data, size_t length)
|
||||
: pos_(data), end_(data + length) {}
|
||||
|
||||
// Sets "pos" to the first non-header byte/pixel on success.
|
||||
bool ParseHeader(HeaderPNM* header, const uint8_t** pos) {
|
||||
// codec.cc ensures we have at least two bytes => no range check here.
|
||||
if (pos_[0] != 'P') return false;
|
||||
const uint8_t type = pos_[1];
|
||||
pos_ += 2;
|
||||
|
||||
switch (type) {
|
||||
case '5':
|
||||
header->is_gray = true;
|
||||
return ParseHeaderPNM(header, pos);
|
||||
|
||||
case '6':
|
||||
header->is_gray = false;
|
||||
return ParseHeaderPNM(header, pos);
|
||||
|
||||
case '7':
|
||||
return ParseHeaderPAM(header, pos);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Exposed for testing
|
||||
bool ParseUnsigned(size_t* number) {
|
||||
if (pos_ == end_) return error_msg("PNM: reached end before number");
|
||||
if (!IsDigit(*pos_)) return error_msg("PNM: expected unsigned number");
|
||||
|
||||
*number = 0;
|
||||
while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') {
|
||||
*number *= 10;
|
||||
*number += *pos_ - '0';
|
||||
++pos_;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseSigned(double* number) {
|
||||
if (pos_ == end_) return error_msg("PNM: reached end before signed");
|
||||
|
||||
if (*pos_ != '-' && *pos_ != '+' && !IsDigit(*pos_)) {
|
||||
return error_msg("PNM: expected signed number");
|
||||
}
|
||||
|
||||
// Skip sign
|
||||
const bool is_neg = *pos_ == '-';
|
||||
if (is_neg || *pos_ == '+') {
|
||||
++pos_;
|
||||
if (pos_ == end_) return error_msg("PNM: reached end before digits");
|
||||
}
|
||||
|
||||
// Leading digits
|
||||
*number = 0.0;
|
||||
while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') {
|
||||
*number *= 10;
|
||||
*number += *pos_ - '0';
|
||||
++pos_;
|
||||
}
|
||||
|
||||
// Decimal places?
|
||||
if (pos_ < end_ && *pos_ == '.') {
|
||||
++pos_;
|
||||
double place = 0.1;
|
||||
while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') {
|
||||
*number += (*pos_ - '0') * place;
|
||||
place *= 0.1;
|
||||
++pos_;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_neg) *number = -*number;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
static bool IsDigit(const uint8_t c) { return '0' <= c && c <= '9'; }
|
||||
static bool IsLineBreak(const uint8_t c) { return c == '\r' || c == '\n'; }
|
||||
static bool IsWhitespace(const uint8_t c) {
|
||||
return IsLineBreak(c) || c == '\t' || c == ' ';
|
||||
}
|
||||
|
||||
bool SkipBlank() {
|
||||
if (pos_ == end_) return error_msg("PNM: reached end before blank");
|
||||
const uint8_t c = *pos_;
|
||||
if (c != ' ' && c != '\n') return error_msg("PNM: expected blank");
|
||||
++pos_;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkipSingleWhitespace() {
|
||||
if (pos_ == end_) return error_msg("PNM: reached end before whitespace");
|
||||
if (!IsWhitespace(*pos_)) return error_msg("PNM: expected whitespace");
|
||||
++pos_;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkipWhitespace() {
|
||||
if (pos_ == end_) return error_msg("PNM: reached end before whitespace");
|
||||
if (!IsWhitespace(*pos_) && *pos_ != '#') {
|
||||
return error_msg("PNM: expected whitespace/comment");
|
||||
}
|
||||
|
||||
while (pos_ < end_ && IsWhitespace(*pos_)) {
|
||||
++pos_;
|
||||
}
|
||||
|
||||
// Comment(s)
|
||||
while (pos_ != end_ && *pos_ == '#') {
|
||||
while (pos_ != end_ && !IsLineBreak(*pos_)) {
|
||||
++pos_;
|
||||
}
|
||||
// Newline(s)
|
||||
while (pos_ != end_ && IsLineBreak(*pos_)) pos_++;
|
||||
}
|
||||
|
||||
while (pos_ < end_ && IsWhitespace(*pos_)) {
|
||||
++pos_;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MatchString(const char* keyword) {
|
||||
const uint8_t* ppos = pos_;
|
||||
while (*keyword) {
|
||||
if (ppos >= end_) return error_msg("PAM: unexpected end of input");
|
||||
if (*keyword != *ppos) return false;
|
||||
ppos++;
|
||||
keyword++;
|
||||
}
|
||||
pos_ = ppos;
|
||||
return_on_error(SkipWhitespace());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseHeaderPAM(HeaderPNM* header, const uint8_t** pos) {
|
||||
size_t num_channels = 3;
|
||||
size_t max_val = 255;
|
||||
while (!MatchString("ENDHDR")) {
|
||||
return_on_error(SkipWhitespace());
|
||||
if (MatchString("WIDTH")) {
|
||||
return_on_error(ParseUnsigned(&header->xsize));
|
||||
} else if (MatchString("HEIGHT")) {
|
||||
return_on_error(ParseUnsigned(&header->ysize));
|
||||
} else if (MatchString("DEPTH")) {
|
||||
return_on_error(ParseUnsigned(&num_channels));
|
||||
} else if (MatchString("MAXVAL")) {
|
||||
return_on_error(ParseUnsigned(&max_val));
|
||||
} else if (MatchString("TUPLTYPE")) {
|
||||
if (MatchString("RGB_ALPHA")) {
|
||||
header->has_alpha = true;
|
||||
} else if (MatchString("RGB")) {
|
||||
} else if (MatchString("GRAYSCALE_ALPHA")) {
|
||||
header->has_alpha = true;
|
||||
header->is_gray = true;
|
||||
} else if (MatchString("GRAYSCALE")) {
|
||||
header->is_gray = true;
|
||||
} else if (MatchString("BLACKANDWHITE_ALPHA")) {
|
||||
header->has_alpha = true;
|
||||
header->is_gray = true;
|
||||
max_val = 1;
|
||||
} else if (MatchString("BLACKANDWHITE")) {
|
||||
header->is_gray = true;
|
||||
max_val = 1;
|
||||
} else {
|
||||
return error_msg("PAM: unknown TUPLTYPE");
|
||||
}
|
||||
} else {
|
||||
return error_msg("PAM: unknown header keyword");
|
||||
}
|
||||
}
|
||||
if (num_channels !=
|
||||
(header->has_alpha ? 1 : 0) + (header->is_gray ? 1 : 3)) {
|
||||
return error_msg("PAM: bad DEPTH");
|
||||
}
|
||||
if (max_val == 0 || max_val >= 65536) {
|
||||
return error_msg("PAM: bad MAXVAL");
|
||||
}
|
||||
header->bits_per_sample = Log2(max_val + 1);
|
||||
|
||||
*pos = pos_;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseHeaderPNM(HeaderPNM* header, const uint8_t** pos) {
|
||||
return_on_error(SkipWhitespace());
|
||||
return_on_error(ParseUnsigned(&header->xsize));
|
||||
|
||||
return_on_error(SkipWhitespace());
|
||||
return_on_error(ParseUnsigned(&header->ysize));
|
||||
|
||||
return_on_error(SkipWhitespace());
|
||||
size_t max_val;
|
||||
return_on_error(ParseUnsigned(&max_val));
|
||||
if (max_val == 0 || max_val >= 65536) {
|
||||
return error_msg("PNM: bad MaxVal");
|
||||
}
|
||||
header->bits_per_sample = Log2(max_val + 1);
|
||||
|
||||
return_on_error(SkipSingleWhitespace());
|
||||
|
||||
*pos = pos_;
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint8_t* pos_;
|
||||
const uint8_t* const end_;
|
||||
};
|
||||
|
||||
bool load_file(unsigned char** out, size_t* outsize, const char* filename) {
|
||||
FILE* file;
|
||||
file = fopen(filename, "rb");
|
||||
if (!file) return false;
|
||||
if (fseek(file, 0, SEEK_END) != 0) {
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
*outsize = ftell(file);
|
||||
if (*outsize == LONG_MAX || *outsize < 9 || fseek(file, 0, SEEK_SET)) {
|
||||
fclose(file);
|
||||
return false;
|
||||
}
|
||||
*out = (unsigned char*)malloc(*outsize);
|
||||
if (!(*out)) return false;
|
||||
size_t readsize;
|
||||
readsize = fread(*out, 1, *outsize, file);
|
||||
fclose(file);
|
||||
if (readsize != *outsize) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DecodePAM(const char* filename, uint8_t** buffer, size_t* w, size_t* h,
|
||||
size_t* nb_chans, size_t* bitdepth) {
|
||||
unsigned char* in_file;
|
||||
size_t in_size;
|
||||
if (!load_file(&in_file, &in_size, filename))
|
||||
return error_msg("Could not read input file");
|
||||
Parser parser(in_file, in_size);
|
||||
HeaderPNM header = {};
|
||||
const uint8_t* pos = nullptr;
|
||||
if (!parser.ParseHeader(&header, &pos)) return false;
|
||||
|
||||
if (header.bits_per_sample == 0 || header.bits_per_sample > 12) {
|
||||
return error_msg("PNM: bits_per_sample invalid (can do at most 12-bit)");
|
||||
}
|
||||
*w = header.xsize;
|
||||
*h = header.ysize;
|
||||
*bitdepth = header.bits_per_sample;
|
||||
*nb_chans = (header.is_gray ? 1 : 3) + (header.has_alpha ? 1 : 0);
|
||||
|
||||
size_t pnm_remaining_size = in_file + in_size - pos;
|
||||
size_t buffer_size = *w * *h * *nb_chans * (*bitdepth > 8 ? 2 : 1);
|
||||
if (pnm_remaining_size < buffer_size) {
|
||||
return error_msg("PNM file too small");
|
||||
}
|
||||
*buffer = (uint8_t*)malloc(buffer_size);
|
||||
memcpy(*buffer, pos, buffer_size);
|
||||
return true;
|
||||
}
|
||||
166
thirdparty/SDL3_image/external/libjxl/lib/CMakeLists.txt
vendored
Normal file
166
thirdparty/SDL3_image/external/libjxl/lib/CMakeLists.txt
vendored
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
# Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
#
|
||||
# Use of this source code is governed by a BSD-style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
set(JPEGXL_MAJOR_VERSION 0)
|
||||
set(JPEGXL_MINOR_VERSION 7)
|
||||
set(JPEGXL_PATCH_VERSION 3)
|
||||
set(JPEGXL_LIBRARY_VERSION
|
||||
"${JPEGXL_MAJOR_VERSION}.${JPEGXL_MINOR_VERSION}.${JPEGXL_PATCH_VERSION}")
|
||||
|
||||
# This is the library API/ABI compatibility version. Changing this value makes
|
||||
# the shared library incompatible with previous version. A program linked
|
||||
# against this shared library SOVERSION will not run with an older SOVERSION.
|
||||
# It is important to update this value when making incompatible API/ABI changes
|
||||
# so that programs that depend on libjxl can update their dependencies. Semantic
|
||||
# versioning allows 0.y.z to have incompatible changes in minor versions.
|
||||
set(JPEGXL_SO_MINOR_VERSION 7)
|
||||
if (JPEGXL_MAJOR_VERSION EQUAL 0)
|
||||
set(JPEGXL_LIBRARY_SOVERSION
|
||||
"${JPEGXL_MAJOR_VERSION}.${JPEGXL_SO_MINOR_VERSION}")
|
||||
else()
|
||||
set(JPEGXL_LIBRARY_SOVERSION "${JPEGXL_MAJOR_VERSION}")
|
||||
endif()
|
||||
|
||||
|
||||
# List of warning and feature flags for our library and tests.
|
||||
if (MSVC)
|
||||
set(JPEGXL_INTERNAL_FLAGS
|
||||
# TODO(janwas): add flags
|
||||
)
|
||||
else ()
|
||||
set(JPEGXL_INTERNAL_FLAGS
|
||||
# F_FLAGS
|
||||
-fmerge-all-constants
|
||||
-fno-builtin-fwrite
|
||||
-fno-builtin-fread
|
||||
|
||||
# WARN_FLAGS
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wc++11-compat
|
||||
-Warray-bounds
|
||||
-Wformat-security
|
||||
-Wimplicit-fallthrough
|
||||
-Wno-register # Needed by public headers in lcms
|
||||
-Wno-unused-function
|
||||
-Wno-unused-parameter
|
||||
-Wnon-virtual-dtor
|
||||
-Woverloaded-virtual
|
||||
-Wvla
|
||||
)
|
||||
|
||||
# Warning flags supported by clang.
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
list(APPEND JPEGXL_INTERNAL_FLAGS
|
||||
-Wdeprecated-increment-bool
|
||||
# TODO(deymo): Add -Wextra-semi once we update third_party/highway.
|
||||
# -Wextra-semi
|
||||
-Wfloat-overflow-conversion
|
||||
-Wfloat-zero-conversion
|
||||
-Wfor-loop-analysis
|
||||
-Wgnu-redeclared-enum
|
||||
-Winfinite-recursion
|
||||
-Wliteral-conversion
|
||||
-Wno-c++98-compat
|
||||
-Wno-unused-command-line-argument
|
||||
-Wprivate-header
|
||||
-Wself-assign
|
||||
-Wstring-conversion
|
||||
-Wtautological-overlap-compare
|
||||
-Wthread-safety-analysis
|
||||
-Wundefined-func-template
|
||||
-Wunreachable-code
|
||||
-Wunused-comparison
|
||||
)
|
||||
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 5.0)
|
||||
list(APPEND HWY_FLAGS -Wc++2a-extensions)
|
||||
endif()
|
||||
endif() # Clang
|
||||
|
||||
if (WIN32)
|
||||
list(APPEND JPEGXL_INTERNAL_FLAGS
|
||||
-Wno-cast-align
|
||||
-Wno-double-promotion
|
||||
-Wno-float-equal
|
||||
-Wno-format-nonliteral
|
||||
-Wno-shadow
|
||||
-Wno-sign-conversion
|
||||
-Wno-zero-as-null-pointer-constant
|
||||
)
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
list(APPEND JPEGXL_INTERNAL_FLAGS
|
||||
-Wno-used-but-marked-unused
|
||||
-Wno-unused-template
|
||||
-Wno-unused-member-function
|
||||
-Wno-shadow-field-in-constructor
|
||||
-Wno-language-extension-token
|
||||
-Wno-global-constructors
|
||||
-Wno-c++98-compat-pedantic
|
||||
)
|
||||
endif() # Clang
|
||||
else() # WIN32
|
||||
list(APPEND JPEGXL_INTERNAL_FLAGS
|
||||
-fsized-deallocation
|
||||
-fno-exceptions
|
||||
|
||||
# Language flags
|
||||
-fmath-errno
|
||||
)
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
list(APPEND JPEGXL_INTERNAL_FLAGS
|
||||
-fnew-alignment=8
|
||||
-fno-cxx-exceptions
|
||||
-fno-slp-vectorize
|
||||
-fno-vectorize
|
||||
|
||||
-disable-free
|
||||
-disable-llvm-verifier
|
||||
)
|
||||
endif() # Clang
|
||||
endif() # WIN32
|
||||
|
||||
# Internal flags for coverage builds:
|
||||
if(JPEGXL_ENABLE_COVERAGE)
|
||||
set(JPEGXL_COVERAGE_FLAGS
|
||||
-g -O0 -fprofile-arcs -ftest-coverage
|
||||
-DJXL_ENABLE_ASSERT=0 -DJXL_ENABLE_CHECK=0
|
||||
)
|
||||
endif() # JPEGXL_ENABLE_COVERAGE
|
||||
endif() #!MSVC
|
||||
|
||||
# The jxl library definition.
|
||||
include(jxl.cmake)
|
||||
|
||||
# Other libraries outside the core jxl library.
|
||||
if(JPEGXL_ENABLE_TOOLS)
|
||||
include(jxl_extras.cmake)
|
||||
endif()
|
||||
include(jxl_threads.cmake)
|
||||
|
||||
# Install all the library headers from the source and the generated ones. There
|
||||
# is no distinction on which libraries use which header since it is expected
|
||||
# that all developer libraries are available together at build time.
|
||||
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/jxl
|
||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include/jxl
|
||||
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}")
|
||||
|
||||
# Profiler for libjxl
|
||||
include(jxl_profiler.cmake)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
# Unittests
|
||||
cmake_policy(SET CMP0057 NEW) # https://gitlab.kitware.com/cmake/cmake/issues/18198
|
||||
include(GoogleTest)
|
||||
|
||||
# Tests for the jxl library.
|
||||
include(jxl_tests.cmake)
|
||||
|
||||
# Google benchmark for the jxl library
|
||||
include(jxl_benchmark.cmake)
|
||||
|
||||
endif() # BUILD_TESTING
|
||||
27
thirdparty/SDL3_image/external/libjxl/lib/extras/LICENSE.apngdis
vendored
Normal file
27
thirdparty/SDL3_image/external/libjxl/lib/extras/LICENSE.apngdis
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
APNG Disassembler 2.8
|
||||
|
||||
Deconstructs APNG files into individual frames.
|
||||
|
||||
http://apngdis.sourceforge.net
|
||||
|
||||
Copyright (c) 2010-2015 Max Stepin
|
||||
maxst at users.sourceforge.net
|
||||
|
||||
zlib license
|
||||
------------
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
5
thirdparty/SDL3_image/external/libjxl/lib/extras/README.md
vendored
Normal file
5
thirdparty/SDL3_image/external/libjxl/lib/extras/README.md
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
## JPEG XL "extras"
|
||||
|
||||
The files in this directory do not form part of the library or codec and are
|
||||
only used by tests or specific internal tools that have access to the internals
|
||||
of the library.
|
||||
189
thirdparty/SDL3_image/external/libjxl/lib/extras/codec.cc
vendored
Normal file
189
thirdparty/SDL3_image/external/libjxl/lib/extras/codec.cc
vendored
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/codec.h"
|
||||
|
||||
#include "jxl/decode.h"
|
||||
#include "jxl/types.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/jxl/base/padded_bytes.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
|
||||
#if JPEGXL_ENABLE_APNG
|
||||
#include "lib/extras/enc/apng.h"
|
||||
#endif
|
||||
#if JPEGXL_ENABLE_JPEG
|
||||
#include "lib/extras/enc/jpg.h"
|
||||
#endif
|
||||
#if JPEGXL_ENABLE_EXR
|
||||
#include "lib/extras/enc/exr.h"
|
||||
#endif
|
||||
|
||||
#include "lib/extras/dec/decode.h"
|
||||
#include "lib/extras/enc/pgx.h"
|
||||
#include "lib/extras/enc/pnm.h"
|
||||
#include "lib/extras/packed_image_convert.h"
|
||||
#include "lib/jxl/base/file_io.h"
|
||||
#include "lib/jxl/image_bundle.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace {
|
||||
|
||||
// Any valid encoding is larger (ensures codecs can read the first few bytes)
|
||||
constexpr size_t kMinBytes = 9;
|
||||
|
||||
} // namespace
|
||||
|
||||
Status SetFromBytes(const Span<const uint8_t> bytes,
|
||||
const extras::ColorHints& color_hints, CodecInOut* io,
|
||||
ThreadPool* pool, extras::Codec* orig_codec) {
|
||||
if (bytes.size() < kMinBytes) return JXL_FAILURE("Too few bytes");
|
||||
|
||||
extras::PackedPixelFile ppf;
|
||||
if (extras::DecodeBytes(bytes, color_hints, io->constraints, &ppf,
|
||||
orig_codec)) {
|
||||
return ConvertPackedPixelFileToCodecInOut(ppf, pool, io);
|
||||
}
|
||||
return JXL_FAILURE("Codecs failed to decode");
|
||||
}
|
||||
|
||||
Status SetFromFile(const std::string& pathname,
|
||||
const extras::ColorHints& color_hints, CodecInOut* io,
|
||||
ThreadPool* pool, extras::Codec* orig_codec) {
|
||||
std::vector<uint8_t> encoded;
|
||||
JXL_RETURN_IF_ERROR(ReadFile(pathname, &encoded));
|
||||
JXL_RETURN_IF_ERROR(SetFromBytes(Span<const uint8_t>(encoded), color_hints,
|
||||
io, pool, orig_codec));
|
||||
return true;
|
||||
}
|
||||
|
||||
Status Encode(const CodecInOut& io, const extras::Codec codec,
|
||||
const ColorEncoding& c_desired, size_t bits_per_sample,
|
||||
std::vector<uint8_t>* bytes, ThreadPool* pool) {
|
||||
JXL_CHECK(!io.Main().c_current().ICC().empty());
|
||||
JXL_CHECK(!c_desired.ICC().empty());
|
||||
io.CheckMetadata();
|
||||
if (io.Main().IsJPEG()) {
|
||||
JXL_WARNING("Writing JPEG data as pixels");
|
||||
}
|
||||
JxlPixelFormat format = {
|
||||
0, // num_channels is ignored by the converter
|
||||
bits_per_sample <= 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16, JXL_BIG_ENDIAN,
|
||||
0};
|
||||
const bool floating_point = bits_per_sample > 16;
|
||||
std::unique_ptr<extras::Encoder> encoder;
|
||||
std::ostringstream os;
|
||||
switch (codec) {
|
||||
case extras::Codec::kPNG:
|
||||
#if JPEGXL_ENABLE_APNG
|
||||
encoder = extras::GetAPNGEncoder();
|
||||
break;
|
||||
#else
|
||||
return JXL_FAILURE("JPEG XL was built without (A)PNG support");
|
||||
#endif
|
||||
case extras::Codec::kJPG:
|
||||
#if JPEGXL_ENABLE_JPEG
|
||||
format.data_type = JXL_TYPE_UINT8;
|
||||
encoder = extras::GetJPEGEncoder();
|
||||
os << io.jpeg_quality;
|
||||
encoder->SetOption("q", os.str());
|
||||
break;
|
||||
#else
|
||||
return JXL_FAILURE("JPEG XL was built without JPEG support");
|
||||
#endif
|
||||
case extras::Codec::kPNM:
|
||||
if (io.Main().HasAlpha()) {
|
||||
encoder = extras::GetPAMEncoder();
|
||||
} else if (io.Main().IsGray()) {
|
||||
encoder = extras::GetPGMEncoder();
|
||||
} else if (!floating_point) {
|
||||
encoder = extras::GetPPMEncoder();
|
||||
} else {
|
||||
format.data_type = JXL_TYPE_FLOAT;
|
||||
format.endianness = JXL_NATIVE_ENDIAN;
|
||||
encoder = extras::GetPFMEncoder();
|
||||
}
|
||||
if (!c_desired.IsSRGB()) {
|
||||
JXL_WARNING(
|
||||
"PNM encoder cannot store custom ICC profile; decoder "
|
||||
"will need hint key=color_space to get the same values");
|
||||
}
|
||||
break;
|
||||
case extras::Codec::kPGX:
|
||||
encoder = extras::GetPGXEncoder();
|
||||
break;
|
||||
case extras::Codec::kGIF:
|
||||
return JXL_FAILURE("Encoding to GIF is not implemented");
|
||||
case extras::Codec::kEXR:
|
||||
#if JPEGXL_ENABLE_EXR
|
||||
format.data_type = JXL_TYPE_FLOAT;
|
||||
encoder = extras::GetEXREncoder();
|
||||
break;
|
||||
#else
|
||||
return JXL_FAILURE("JPEG XL was built without OpenEXR support");
|
||||
#endif
|
||||
case extras::Codec::kUnknown:
|
||||
return JXL_FAILURE("Cannot encode using Codec::kUnknown");
|
||||
}
|
||||
|
||||
if (!encoder) {
|
||||
return JXL_FAILURE("Invalid codec.");
|
||||
}
|
||||
|
||||
extras::PackedPixelFile ppf;
|
||||
JXL_RETURN_IF_ERROR(
|
||||
ConvertCodecInOutToPackedPixelFile(io, format, c_desired, pool, &ppf));
|
||||
extras::EncodedImage encoded_image;
|
||||
JXL_RETURN_IF_ERROR(encoder->Encode(ppf, &encoded_image, pool));
|
||||
JXL_ASSERT(encoded_image.bitstreams.size() == 1);
|
||||
*bytes = encoded_image.bitstreams[0];
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Status EncodeToFile(const CodecInOut& io, const ColorEncoding& c_desired,
|
||||
size_t bits_per_sample, const std::string& pathname,
|
||||
ThreadPool* pool) {
|
||||
const std::string extension = Extension(pathname);
|
||||
const extras::Codec codec =
|
||||
extras::CodecFromExtension(extension, &bits_per_sample);
|
||||
|
||||
// Warn about incorrect usage of PGM/PGX/PPM - only the latter supports
|
||||
// color, but CodecFromExtension lumps them all together.
|
||||
if (codec == extras::Codec::kPNM && extension != ".pfm") {
|
||||
if (io.Main().HasAlpha() && extension != ".pam") {
|
||||
JXL_WARNING(
|
||||
"For images with alpha, the filename should end with .pam.\n");
|
||||
} else if (!io.Main().IsGray() && extension == ".pgm") {
|
||||
JXL_WARNING("For color images, the filename should end with .ppm.\n");
|
||||
} else if (io.Main().IsGray() && extension == ".ppm") {
|
||||
JXL_WARNING(
|
||||
"For grayscale images, the filename should not end with .ppm.\n");
|
||||
}
|
||||
if (bits_per_sample > 16) {
|
||||
JXL_WARNING("PPM only supports up to 16 bits per sample");
|
||||
bits_per_sample = 16;
|
||||
}
|
||||
} else if (codec == extras::Codec::kPGX && !io.Main().IsGray()) {
|
||||
JXL_WARNING("Storing color image to PGX - use .ppm extension instead.\n");
|
||||
}
|
||||
if (bits_per_sample > 16 && codec == extras::Codec::kPNG) {
|
||||
JXL_WARNING("PNG only supports up to 16 bits per sample");
|
||||
bits_per_sample = 16;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> encoded;
|
||||
return Encode(io, codec, c_desired, bits_per_sample, &encoded, pool) &&
|
||||
WriteFile(encoded, pathname);
|
||||
}
|
||||
|
||||
Status EncodeToFile(const CodecInOut& io, const std::string& pathname,
|
||||
ThreadPool* pool) {
|
||||
// TODO(lode): need to take the floating_point_sample field into account
|
||||
return EncodeToFile(io, io.metadata.m.color_encoding,
|
||||
io.metadata.m.bit_depth.bits_per_sample, pathname, pool);
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
64
thirdparty/SDL3_image/external/libjxl/lib/extras/codec.h
vendored
Normal file
64
thirdparty/SDL3_image/external/libjxl/lib/extras/codec.h
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_EXTRAS_CODEC_H_
|
||||
#define LIB_EXTRAS_CODEC_H_
|
||||
|
||||
// Facade for image encoders/decoders (PNG, PNM, ...).
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "lib/extras/dec/color_hints.h"
|
||||
#include "lib/extras/dec/decode.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/padded_bytes.h"
|
||||
#include "lib/jxl/base/span.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/codec_in_out.h"
|
||||
#include "lib/jxl/color_encoding_internal.h"
|
||||
#include "lib/jxl/field_encodings.h" // MakeBit
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// Decodes "bytes" and sets io->metadata.m.
|
||||
// color_space_hint may specify the color space, otherwise, defaults to sRGB.
|
||||
Status SetFromBytes(Span<const uint8_t> bytes,
|
||||
const extras::ColorHints& color_hints, CodecInOut* io,
|
||||
ThreadPool* pool = nullptr,
|
||||
extras::Codec* orig_codec = nullptr);
|
||||
// Helper function to use no color_space_hint.
|
||||
JXL_INLINE Status SetFromBytes(const Span<const uint8_t> bytes, CodecInOut* io,
|
||||
ThreadPool* pool = nullptr,
|
||||
extras::Codec* orig_codec = nullptr) {
|
||||
return SetFromBytes(bytes, extras::ColorHints(), io, pool, orig_codec);
|
||||
}
|
||||
|
||||
// Reads from file and calls SetFromBytes.
|
||||
Status SetFromFile(const std::string& pathname,
|
||||
const extras::ColorHints& color_hints, CodecInOut* io,
|
||||
ThreadPool* pool = nullptr,
|
||||
extras::Codec* orig_codec = nullptr);
|
||||
|
||||
// Replaces "bytes" with an encoding of pixels transformed from c_current
|
||||
// color space to c_desired.
|
||||
Status Encode(const CodecInOut& io, extras::Codec codec,
|
||||
const ColorEncoding& c_desired, size_t bits_per_sample,
|
||||
std::vector<uint8_t>* bytes, ThreadPool* pool = nullptr);
|
||||
|
||||
// Deduces codec, calls Encode and writes to file.
|
||||
Status EncodeToFile(const CodecInOut& io, const ColorEncoding& c_desired,
|
||||
size_t bits_per_sample, const std::string& pathname,
|
||||
ThreadPool* pool = nullptr);
|
||||
// Same, but defaults to metadata.original color_encoding and bits_per_sample.
|
||||
Status EncodeToFile(const CodecInOut& io, const std::string& pathname,
|
||||
ThreadPool* pool = nullptr);
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_EXTRAS_CODEC_H_
|
||||
556
thirdparty/SDL3_image/external/libjxl/lib/extras/codec_test.cc
vendored
Normal file
556
thirdparty/SDL3_image/external/libjxl/lib/extras/codec_test.cc
vendored
Normal file
|
|
@ -0,0 +1,556 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/codec.h"
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/extras/dec/pgx.h"
|
||||
#include "lib/extras/dec/pnm.h"
|
||||
#include "lib/extras/enc/encode.h"
|
||||
#include "lib/extras/packed_image_convert.h"
|
||||
#include "lib/jxl/base/printf_macros.h"
|
||||
#include "lib/jxl/base/random.h"
|
||||
#include "lib/jxl/base/thread_pool_internal.h"
|
||||
#include "lib/jxl/color_management.h"
|
||||
#include "lib/jxl/enc_color_management.h"
|
||||
#include "lib/jxl/image.h"
|
||||
#include "lib/jxl/image_bundle.h"
|
||||
#include "lib/jxl/image_test_utils.h"
|
||||
#include "lib/jxl/test_utils.h"
|
||||
#include "lib/jxl/testdata.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
namespace {
|
||||
|
||||
using ::testing::AllOf;
|
||||
using ::testing::Contains;
|
||||
using ::testing::Field;
|
||||
using ::testing::IsEmpty;
|
||||
using ::testing::NotNull;
|
||||
using ::testing::SizeIs;
|
||||
|
||||
std::string ExtensionFromCodec(Codec codec, const bool is_gray,
|
||||
const bool has_alpha,
|
||||
const size_t bits_per_sample) {
|
||||
switch (codec) {
|
||||
case Codec::kJPG:
|
||||
return ".jpg";
|
||||
case Codec::kPGX:
|
||||
return ".pgx";
|
||||
case Codec::kPNG:
|
||||
return ".png";
|
||||
case Codec::kPNM:
|
||||
if (has_alpha) return ".pam";
|
||||
if (is_gray) return ".pgm";
|
||||
return (bits_per_sample == 32) ? ".pfm" : ".ppm";
|
||||
case Codec::kGIF:
|
||||
return ".gif";
|
||||
case Codec::kEXR:
|
||||
return ".exr";
|
||||
case Codec::kUnknown:
|
||||
return std::string();
|
||||
}
|
||||
JXL_UNREACHABLE;
|
||||
return std::string();
|
||||
}
|
||||
|
||||
void VerifySameImage(const PackedImage& im0, size_t bits_per_sample0,
|
||||
const PackedImage& im1, size_t bits_per_sample1,
|
||||
bool lossless = true) {
|
||||
ASSERT_EQ(im0.xsize, im1.xsize);
|
||||
ASSERT_EQ(im0.ysize, im1.ysize);
|
||||
ASSERT_EQ(im0.format.num_channels, im1.format.num_channels);
|
||||
auto get_factor = [](JxlPixelFormat f, size_t bits) -> double {
|
||||
return 1.0 / ((1u << std::min(test::GetPrecision(f.data_type), bits)) - 1);
|
||||
};
|
||||
double factor0 = get_factor(im0.format, bits_per_sample0);
|
||||
double factor1 = get_factor(im1.format, bits_per_sample1);
|
||||
auto pixels0 = static_cast<const uint8_t*>(im0.pixels());
|
||||
auto pixels1 = static_cast<const uint8_t*>(im1.pixels());
|
||||
auto rgba0 =
|
||||
test::ConvertToRGBA32(pixels0, im0.xsize, im0.ysize, im0.format, factor0);
|
||||
auto rgba1 =
|
||||
test::ConvertToRGBA32(pixels1, im1.xsize, im1.ysize, im1.format, factor1);
|
||||
double tolerance =
|
||||
lossless ? 0.5 * std::min(factor0, factor1) : 3.0f / 255.0f;
|
||||
if (bits_per_sample0 == 32 || bits_per_sample1 == 32) {
|
||||
tolerance = 0.5 * std::max(factor0, factor1);
|
||||
}
|
||||
for (size_t y = 0; y < im0.ysize; ++y) {
|
||||
for (size_t x = 0; x < im0.xsize; ++x) {
|
||||
for (size_t c = 0; c < im0.format.num_channels; ++c) {
|
||||
size_t ix = (y * im0.xsize + x) * 4 + c;
|
||||
double val0 = rgba0[ix];
|
||||
double val1 = rgba1[ix];
|
||||
ASSERT_NEAR(val1, val0, tolerance)
|
||||
<< "y = " << y << " x = " << x << " c = " << c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JxlColorEncoding CreateTestColorEncoding(bool is_gray) {
|
||||
JxlColorEncoding c;
|
||||
c.color_space = is_gray ? JXL_COLOR_SPACE_GRAY : JXL_COLOR_SPACE_RGB;
|
||||
c.white_point = JXL_WHITE_POINT_D65;
|
||||
c.primaries = JXL_PRIMARIES_P3;
|
||||
c.rendering_intent = JXL_RENDERING_INTENT_RELATIVE;
|
||||
c.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR;
|
||||
// Roundtrip through internal color encoding to fill in primaries and white
|
||||
// point CIE xy coordinates.
|
||||
ColorEncoding c_internal;
|
||||
JXL_CHECK(ConvertExternalToInternalColorEncoding(c, &c_internal));
|
||||
ConvertInternalToExternalColorEncoding(c_internal, &c);
|
||||
return c;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> GenerateICC(JxlColorEncoding color_encoding) {
|
||||
ColorEncoding c;
|
||||
JXL_CHECK(ConvertExternalToInternalColorEncoding(color_encoding, &c));
|
||||
JXL_CHECK(c.CreateICC());
|
||||
PaddedBytes icc = c.ICC();
|
||||
return std::vector<uint8_t>(icc.begin(), icc.end());
|
||||
}
|
||||
|
||||
void StoreRandomValue(uint8_t* out, Rng* rng, JxlPixelFormat format,
|
||||
size_t bits_per_sample) {
|
||||
uint64_t max_val = (1ull << bits_per_sample) - 1;
|
||||
if (format.data_type == JXL_TYPE_UINT8) {
|
||||
*out = rng->UniformU(0, max_val);
|
||||
} else if (format.data_type == JXL_TYPE_UINT16) {
|
||||
uint32_t val = rng->UniformU(0, max_val);
|
||||
if (format.endianness == JXL_BIG_ENDIAN) {
|
||||
StoreBE16(val, out);
|
||||
} else {
|
||||
StoreLE16(val, out);
|
||||
}
|
||||
} else {
|
||||
ASSERT_EQ(format.data_type, JXL_TYPE_FLOAT);
|
||||
float val = rng->UniformF(0.0, 1.0);
|
||||
uint32_t uval;
|
||||
memcpy(&uval, &val, 4);
|
||||
if (format.endianness == JXL_BIG_ENDIAN) {
|
||||
StoreBE32(uval, out);
|
||||
} else {
|
||||
StoreLE32(uval, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FillPackedImage(size_t bits_per_sample, PackedImage* image) {
|
||||
JxlPixelFormat format = image->format;
|
||||
size_t bytes_per_channel = PackedImage::BitsPerChannel(format.data_type) / 8;
|
||||
uint8_t* out = static_cast<uint8_t*>(image->pixels());
|
||||
size_t stride = image->xsize * format.num_channels * bytes_per_channel;
|
||||
ASSERT_EQ(image->pixels_size, image->ysize * stride);
|
||||
Rng rng(129);
|
||||
for (size_t y = 0; y < image->ysize; ++y) {
|
||||
for (size_t x = 0; x < image->xsize; ++x) {
|
||||
for (size_t c = 0; c < format.num_channels; ++c) {
|
||||
StoreRandomValue(out, &rng, format, bits_per_sample);
|
||||
out += bytes_per_channel;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TestImageParams {
|
||||
Codec codec;
|
||||
size_t xsize;
|
||||
size_t ysize;
|
||||
size_t bits_per_sample;
|
||||
bool is_gray;
|
||||
bool add_alpha;
|
||||
bool big_endian;
|
||||
|
||||
bool ShouldTestRoundtrip() const {
|
||||
if (codec == Codec::kPNG) {
|
||||
return true;
|
||||
} else if (codec == Codec::kPNM) {
|
||||
// TODO(szabadka) Make PNM encoder endianness-aware.
|
||||
return ((bits_per_sample <= 16 && big_endian) ||
|
||||
(bits_per_sample == 32 && !add_alpha && !big_endian));
|
||||
} else if (codec == Codec::kPGX) {
|
||||
return ((bits_per_sample == 8 || bits_per_sample == 16) && is_gray &&
|
||||
!add_alpha);
|
||||
} else if (codec == Codec::kEXR) {
|
||||
#if defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER) || \
|
||||
defined(THREAD_SANITIZER)
|
||||
// OpenEXR 2.3 has a memory leak in IlmThread_2_3::ThreadPool
|
||||
return false;
|
||||
#else
|
||||
return bits_per_sample == 32 && !is_gray;
|
||||
#endif
|
||||
} else if (codec == Codec::kJPG) {
|
||||
return bits_per_sample == 8 && !add_alpha;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
JxlPixelFormat PixelFormat() const {
|
||||
JxlPixelFormat format;
|
||||
format.num_channels = (is_gray ? 1 : 3) + (add_alpha ? 1 : 0);
|
||||
format.data_type = (bits_per_sample == 32 ? JXL_TYPE_FLOAT
|
||||
: bits_per_sample > 8 ? JXL_TYPE_UINT16
|
||||
: JXL_TYPE_UINT8);
|
||||
format.endianness = big_endian ? JXL_BIG_ENDIAN : JXL_LITTLE_ENDIAN;
|
||||
format.align = 0;
|
||||
return format;
|
||||
}
|
||||
|
||||
std::string DebugString() const {
|
||||
std::ostringstream os;
|
||||
os << "bps:" << bits_per_sample << " gr:" << is_gray << " al:" << add_alpha
|
||||
<< " be: " << big_endian;
|
||||
return os.str();
|
||||
}
|
||||
};
|
||||
|
||||
void CreateTestImage(const TestImageParams& params, PackedPixelFile* ppf) {
|
||||
ppf->info.xsize = params.xsize;
|
||||
ppf->info.ysize = params.ysize;
|
||||
ppf->info.bits_per_sample = params.bits_per_sample;
|
||||
ppf->info.exponent_bits_per_sample = params.bits_per_sample == 32 ? 8 : 0;
|
||||
ppf->info.num_color_channels = params.is_gray ? 1 : 3;
|
||||
ppf->info.alpha_bits = params.add_alpha ? params.bits_per_sample : 0;
|
||||
ppf->info.alpha_premultiplied = (params.codec == Codec::kEXR);
|
||||
|
||||
JxlColorEncoding color_encoding = CreateTestColorEncoding(params.is_gray);
|
||||
ppf->icc = GenerateICC(color_encoding);
|
||||
ppf->color_encoding = color_encoding;
|
||||
|
||||
PackedFrame frame(params.xsize, params.ysize, params.PixelFormat());
|
||||
FillPackedImage(params.bits_per_sample, &frame.color);
|
||||
ppf->frames.emplace_back(std::move(frame));
|
||||
}
|
||||
|
||||
// Ensures reading a newly written file leads to the same image pixels.
|
||||
void TestRoundTrip(const TestImageParams& params, ThreadPool* pool) {
|
||||
if (!params.ShouldTestRoundtrip()) return;
|
||||
|
||||
std::string extension = ExtensionFromCodec(
|
||||
params.codec, params.is_gray, params.add_alpha, params.bits_per_sample);
|
||||
printf("Codec %s %s\n", extension.c_str(), params.DebugString().c_str());
|
||||
|
||||
PackedPixelFile ppf_in;
|
||||
CreateTestImage(params, &ppf_in);
|
||||
|
||||
EncodedImage encoded;
|
||||
auto encoder = Encoder::FromExtension(extension);
|
||||
ASSERT_TRUE(encoder.get());
|
||||
ASSERT_TRUE(encoder->Encode(ppf_in, &encoded, pool));
|
||||
ASSERT_EQ(encoded.bitstreams.size(), 1);
|
||||
|
||||
PackedPixelFile ppf_out;
|
||||
ASSERT_TRUE(DecodeBytes(Span<const uint8_t>(encoded.bitstreams[0]),
|
||||
ColorHints(), SizeConstraints(), &ppf_out));
|
||||
|
||||
if (params.codec != Codec::kPNM && params.codec != Codec::kPGX &&
|
||||
params.codec != Codec::kEXR) {
|
||||
EXPECT_EQ(ppf_in.icc, ppf_out.icc);
|
||||
}
|
||||
|
||||
ASSERT_EQ(ppf_out.frames.size(), 1);
|
||||
VerifySameImage(ppf_in.frames[0].color, ppf_in.info.bits_per_sample,
|
||||
ppf_out.frames[0].color, ppf_out.info.bits_per_sample,
|
||||
/*lossless=*/params.codec != Codec::kJPG);
|
||||
}
|
||||
|
||||
TEST(CodecTest, TestRoundTrip) {
|
||||
ThreadPoolInternal pool(12);
|
||||
|
||||
TestImageParams params;
|
||||
params.xsize = 7;
|
||||
params.ysize = 4;
|
||||
|
||||
for (Codec codec : AvailableCodecs()) {
|
||||
for (int bits_per_sample : {4, 8, 10, 12, 16, 32}) {
|
||||
for (bool is_gray : {false, true}) {
|
||||
for (bool add_alpha : {false, true}) {
|
||||
for (bool big_endian : {false, true}) {
|
||||
params.codec = codec;
|
||||
params.bits_per_sample = static_cast<size_t>(bits_per_sample);
|
||||
params.is_gray = is_gray;
|
||||
params.add_alpha = add_alpha;
|
||||
params.big_endian = big_endian;
|
||||
TestRoundTrip(params, &pool);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CodecInOut DecodeRoundtrip(const std::string& pathname, ThreadPool* pool,
|
||||
const ColorHints& color_hints = ColorHints()) {
|
||||
CodecInOut io;
|
||||
const PaddedBytes orig = ReadTestData(pathname);
|
||||
JXL_CHECK(
|
||||
SetFromBytes(Span<const uint8_t>(orig), color_hints, &io, pool, nullptr));
|
||||
const ImageBundle& ib1 = io.Main();
|
||||
|
||||
// Encode/Decode again to make sure Encode carries through all metadata.
|
||||
std::vector<uint8_t> encoded;
|
||||
JXL_CHECK(Encode(io, Codec::kPNG, io.metadata.m.color_encoding,
|
||||
io.metadata.m.bit_depth.bits_per_sample, &encoded, pool));
|
||||
|
||||
CodecInOut io2;
|
||||
JXL_CHECK(SetFromBytes(Span<const uint8_t>(encoded), color_hints, &io2, pool,
|
||||
nullptr));
|
||||
const ImageBundle& ib2 = io2.Main();
|
||||
EXPECT_EQ(Description(ib1.metadata()->color_encoding),
|
||||
Description(ib2.metadata()->color_encoding));
|
||||
EXPECT_EQ(Description(ib1.c_current()), Description(ib2.c_current()));
|
||||
|
||||
size_t bits_per_sample = io2.metadata.m.bit_depth.bits_per_sample;
|
||||
|
||||
// "Same" pixels?
|
||||
double max_l1 = bits_per_sample <= 12 ? 1.3 : 2E-3;
|
||||
double max_rel = bits_per_sample <= 12 ? 6E-3 : 1E-4;
|
||||
if (ib1.metadata()->color_encoding.IsGray()) {
|
||||
max_rel *= 2.0;
|
||||
} else if (ib1.metadata()->color_encoding.primaries != Primaries::kSRGB) {
|
||||
// Need more tolerance for large gamuts (anything but sRGB)
|
||||
max_l1 *= 1.5;
|
||||
max_rel *= 3.0;
|
||||
}
|
||||
VerifyRelativeError(ib1.color(), ib2.color(), max_l1, max_rel);
|
||||
|
||||
// Simulate the encoder removing profile and decoder restoring it.
|
||||
if (!ib2.metadata()->color_encoding.WantICC()) {
|
||||
io2.metadata.m.color_encoding.InternalRemoveICC();
|
||||
EXPECT_TRUE(io2.metadata.m.color_encoding.CreateICC());
|
||||
}
|
||||
|
||||
return io2;
|
||||
}
|
||||
|
||||
#if 0
|
||||
TEST(CodecTest, TestMetadataSRGB) {
|
||||
ThreadPoolInternal pool(12);
|
||||
|
||||
const char* paths[] = {"external/raw.pixls/DJI-FC6310-16bit_srgb8_v4_krita.png",
|
||||
"external/raw.pixls/Google-Pixel2XL-16bit_srgb8_v4_krita.png",
|
||||
"external/raw.pixls/HUAWEI-EVA-L09-16bit_srgb8_dt.png",
|
||||
"external/raw.pixls/Nikon-D300-12bit_srgb8_dt.png",
|
||||
"external/raw.pixls/Sony-DSC-RX1RM2-14bit_srgb8_v4_krita.png"};
|
||||
for (const char* relative_pathname : paths) {
|
||||
const CodecInOut io =
|
||||
DecodeRoundtrip(relative_pathname, Codec::kPNG, &pool);
|
||||
EXPECT_EQ(8, io.metadata.m.bit_depth.bits_per_sample);
|
||||
EXPECT_FALSE(io.metadata.m.bit_depth.floating_point_sample);
|
||||
EXPECT_EQ(0, io.metadata.m.bit_depth.exponent_bits_per_sample);
|
||||
|
||||
EXPECT_EQ(64, io.xsize());
|
||||
EXPECT_EQ(64, io.ysize());
|
||||
EXPECT_FALSE(io.metadata.m.HasAlpha());
|
||||
|
||||
const ColorEncoding& c_original = io.metadata.m.color_encoding;
|
||||
EXPECT_FALSE(c_original.ICC().empty());
|
||||
EXPECT_EQ(ColorSpace::kRGB, c_original.GetColorSpace());
|
||||
EXPECT_EQ(WhitePoint::kD65, c_original.white_point);
|
||||
EXPECT_EQ(Primaries::kSRGB, c_original.primaries);
|
||||
EXPECT_TRUE(c_original.tf.IsSRGB());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CodecTest, TestMetadataLinear) {
|
||||
ThreadPoolInternal pool(12);
|
||||
|
||||
const char* paths[3] = {
|
||||
"external/raw.pixls/Google-Pixel2XL-16bit_acescg_g1_v4_krita.png",
|
||||
"external/raw.pixls/HUAWEI-EVA-L09-16bit_709_g1_dt.png",
|
||||
"external/raw.pixls/Nikon-D300-12bit_2020_g1_dt.png",
|
||||
};
|
||||
const WhitePoint white_points[3] = {WhitePoint::kCustom, WhitePoint::kD65,
|
||||
WhitePoint::kD65};
|
||||
const Primaries primaries[3] = {Primaries::kCustom, Primaries::kSRGB,
|
||||
Primaries::k2100};
|
||||
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
const CodecInOut io = DecodeRoundtrip(paths[i], Codec::kPNG, &pool);
|
||||
EXPECT_EQ(16, io.metadata.m.bit_depth.bits_per_sample);
|
||||
EXPECT_FALSE(io.metadata.m.bit_depth.floating_point_sample);
|
||||
EXPECT_EQ(0, io.metadata.m.bit_depth.exponent_bits_per_sample);
|
||||
|
||||
EXPECT_EQ(64, io.xsize());
|
||||
EXPECT_EQ(64, io.ysize());
|
||||
EXPECT_FALSE(io.metadata.m.HasAlpha());
|
||||
|
||||
const ColorEncoding& c_original = io.metadata.m.color_encoding;
|
||||
EXPECT_FALSE(c_original.ICC().empty());
|
||||
EXPECT_EQ(ColorSpace::kRGB, c_original.GetColorSpace());
|
||||
EXPECT_EQ(white_points[i], c_original.white_point);
|
||||
EXPECT_EQ(primaries[i], c_original.primaries);
|
||||
EXPECT_TRUE(c_original.tf.IsLinear());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CodecTest, TestMetadataICC) {
|
||||
ThreadPoolInternal pool(12);
|
||||
|
||||
const char* paths[] = {
|
||||
"external/raw.pixls/DJI-FC6310-16bit_709_v4_krita.png",
|
||||
"external/raw.pixls/Sony-DSC-RX1RM2-14bit_709_v4_krita.png",
|
||||
};
|
||||
for (const char* relative_pathname : paths) {
|
||||
const CodecInOut io =
|
||||
DecodeRoundtrip(relative_pathname, Codec::kPNG, &pool);
|
||||
EXPECT_GE(16, io.metadata.m.bit_depth.bits_per_sample);
|
||||
EXPECT_LE(14, io.metadata.m.bit_depth.bits_per_sample);
|
||||
|
||||
EXPECT_EQ(64, io.xsize());
|
||||
EXPECT_EQ(64, io.ysize());
|
||||
EXPECT_FALSE(io.metadata.m.HasAlpha());
|
||||
|
||||
const ColorEncoding& c_original = io.metadata.m.color_encoding;
|
||||
EXPECT_FALSE(c_original.ICC().empty());
|
||||
EXPECT_EQ(RenderingIntent::kPerceptual, c_original.rendering_intent);
|
||||
EXPECT_EQ(ColorSpace::kRGB, c_original.GetColorSpace());
|
||||
EXPECT_EQ(WhitePoint::kD65, c_original.white_point);
|
||||
EXPECT_EQ(Primaries::kSRGB, c_original.primaries);
|
||||
EXPECT_EQ(TransferFunction::k709, c_original.tf.GetTransferFunction());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CodecTest, Testexternal/pngsuite) {
|
||||
ThreadPoolInternal pool(12);
|
||||
|
||||
// Ensure we can load PNG with text, japanese UTF-8, compressed text.
|
||||
(void)DecodeRoundtrip("external/pngsuite/ct1n0g04.png", Codec::kPNG, &pool);
|
||||
(void)DecodeRoundtrip("external/pngsuite/ctjn0g04.png", Codec::kPNG, &pool);
|
||||
(void)DecodeRoundtrip("external/pngsuite/ctzn0g04.png", Codec::kPNG, &pool);
|
||||
|
||||
// Extract gAMA
|
||||
const CodecInOut b1 =
|
||||
DecodeRoundtrip("external/pngsuite/g10n3p04.png", Codec::kPNG, &pool);
|
||||
EXPECT_TRUE(b1.metadata.color_encoding.tf.IsLinear());
|
||||
|
||||
// Extract cHRM
|
||||
const CodecInOut b_p =
|
||||
DecodeRoundtrip("external/pngsuite/ccwn2c08.png", Codec::kPNG, &pool);
|
||||
EXPECT_EQ(Primaries::kSRGB, b_p.metadata.color_encoding.primaries);
|
||||
EXPECT_EQ(WhitePoint::kD65, b_p.metadata.color_encoding.white_point);
|
||||
|
||||
// Extract EXIF from (new-style) dedicated chunk
|
||||
const CodecInOut b_exif =
|
||||
DecodeRoundtrip("external/pngsuite/exif2c08.png", Codec::kPNG, &pool);
|
||||
EXPECT_EQ(978, b_exif.blobs.exif.size());
|
||||
}
|
||||
#endif
|
||||
|
||||
void VerifyWideGamutMetadata(const std::string& relative_pathname,
|
||||
const Primaries primaries, ThreadPool* pool) {
|
||||
const CodecInOut io = DecodeRoundtrip(relative_pathname, pool);
|
||||
|
||||
EXPECT_EQ(8u, io.metadata.m.bit_depth.bits_per_sample);
|
||||
EXPECT_FALSE(io.metadata.m.bit_depth.floating_point_sample);
|
||||
EXPECT_EQ(0u, io.metadata.m.bit_depth.exponent_bits_per_sample);
|
||||
|
||||
const ColorEncoding& c_original = io.metadata.m.color_encoding;
|
||||
EXPECT_FALSE(c_original.ICC().empty());
|
||||
EXPECT_EQ(RenderingIntent::kAbsolute, c_original.rendering_intent);
|
||||
EXPECT_EQ(ColorSpace::kRGB, c_original.GetColorSpace());
|
||||
EXPECT_EQ(WhitePoint::kD65, c_original.white_point);
|
||||
EXPECT_EQ(primaries, c_original.primaries);
|
||||
}
|
||||
|
||||
TEST(CodecTest, TestWideGamut) {
|
||||
ThreadPoolInternal pool(12);
|
||||
// VerifyWideGamutMetadata("external/wide-gamut-tests/P3-sRGB-color-bars.png",
|
||||
// Primaries::kP3, &pool);
|
||||
VerifyWideGamutMetadata("external/wide-gamut-tests/P3-sRGB-color-ring.png",
|
||||
Primaries::kP3, &pool);
|
||||
// VerifyWideGamutMetadata("external/wide-gamut-tests/R2020-sRGB-color-bars.png",
|
||||
// Primaries::k2100, &pool);
|
||||
// VerifyWideGamutMetadata("external/wide-gamut-tests/R2020-sRGB-color-ring.png",
|
||||
// Primaries::k2100, &pool);
|
||||
}
|
||||
|
||||
TEST(CodecTest, TestPNM) { TestCodecPNM(); }
|
||||
|
||||
TEST(CodecTest, FormatNegotiation) {
|
||||
const std::vector<JxlPixelFormat> accepted_formats = {
|
||||
{/*num_channels=*/4,
|
||||
/*data_type=*/JXL_TYPE_UINT16,
|
||||
/*endianness=*/JXL_NATIVE_ENDIAN,
|
||||
/*align=*/0},
|
||||
{/*num_channels=*/3,
|
||||
/*data_type=*/JXL_TYPE_UINT8,
|
||||
/*endianness=*/JXL_NATIVE_ENDIAN,
|
||||
/*align=*/0},
|
||||
{/*num_channels=*/3,
|
||||
/*data_type=*/JXL_TYPE_UINT16,
|
||||
/*endianness=*/JXL_NATIVE_ENDIAN,
|
||||
/*align=*/0},
|
||||
{/*num_channels=*/1,
|
||||
/*data_type=*/JXL_TYPE_UINT8,
|
||||
/*endianness=*/JXL_NATIVE_ENDIAN,
|
||||
/*align=*/0},
|
||||
};
|
||||
|
||||
JxlBasicInfo info;
|
||||
JxlEncoderInitBasicInfo(&info);
|
||||
info.bits_per_sample = 12;
|
||||
info.num_color_channels = 2;
|
||||
|
||||
JxlPixelFormat format;
|
||||
EXPECT_FALSE(SelectFormat(accepted_formats, info, &format));
|
||||
|
||||
info.num_color_channels = 3;
|
||||
ASSERT_TRUE(SelectFormat(accepted_formats, info, &format));
|
||||
EXPECT_EQ(format.num_channels, info.num_color_channels);
|
||||
// 16 is the smallest accepted format that can accommodate the 12-bit data.
|
||||
EXPECT_EQ(format.data_type, JXL_TYPE_UINT16);
|
||||
}
|
||||
|
||||
TEST(CodecTest, EncodeToPNG) {
|
||||
ThreadPool* const pool = nullptr;
|
||||
|
||||
std::unique_ptr<Encoder> png_encoder = Encoder::FromExtension(".png");
|
||||
ASSERT_THAT(png_encoder, NotNull());
|
||||
|
||||
const PaddedBytes original_png =
|
||||
ReadTestData("external/wesaturate/500px/tmshre_riaphotographs_srgb8.png");
|
||||
PackedPixelFile ppf;
|
||||
ASSERT_TRUE(extras::DecodeBytes(Span<const uint8_t>(original_png),
|
||||
ColorHints(), SizeConstraints(), &ppf));
|
||||
|
||||
const JxlPixelFormat& format = ppf.frames.front().color.format;
|
||||
ASSERT_THAT(
|
||||
png_encoder->AcceptedFormats(),
|
||||
Contains(AllOf(Field(&JxlPixelFormat::num_channels, format.num_channels),
|
||||
Field(&JxlPixelFormat::data_type, format.data_type),
|
||||
Field(&JxlPixelFormat::endianness, format.endianness))));
|
||||
EncodedImage encoded_png;
|
||||
ASSERT_TRUE(png_encoder->Encode(ppf, &encoded_png, pool));
|
||||
EXPECT_THAT(encoded_png.icc, IsEmpty());
|
||||
ASSERT_THAT(encoded_png.bitstreams, SizeIs(1));
|
||||
|
||||
PackedPixelFile decoded_ppf;
|
||||
ASSERT_TRUE(
|
||||
extras::DecodeBytes(Span<const uint8_t>(encoded_png.bitstreams.front()),
|
||||
ColorHints(), SizeConstraints(), &decoded_ppf));
|
||||
|
||||
ASSERT_EQ(decoded_ppf.info.bits_per_sample, ppf.info.bits_per_sample);
|
||||
ASSERT_EQ(decoded_ppf.frames.size(), 1);
|
||||
VerifySameImage(ppf.frames[0].color, ppf.info.bits_per_sample,
|
||||
decoded_ppf.frames[0].color,
|
||||
decoded_ppf.info.bits_per_sample);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
797
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/apng.cc
vendored
Normal file
797
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/apng.cc
vendored
Normal file
|
|
@ -0,0 +1,797 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/dec/apng.h"
|
||||
|
||||
// Parts of this code are taken from apngdis, which has the following license:
|
||||
/* APNG Disassembler 2.8
|
||||
*
|
||||
* Deconstructs APNG files into individual frames.
|
||||
*
|
||||
* http://apngdis.sourceforge.net
|
||||
*
|
||||
* Copyright (c) 2010-2015 Max Stepin
|
||||
* maxst at users.sourceforge.net
|
||||
*
|
||||
* zlib license
|
||||
* ------------
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty. In no event will the authors be held liable for any damages
|
||||
* arising from the use of this software.
|
||||
*
|
||||
* Permission is granted to anyone to use this software for any purpose,
|
||||
* including commercial applications, and to alter it and redistribute it
|
||||
* freely, subject to the following restrictions:
|
||||
*
|
||||
* 1. The origin of this software must not be misrepresented; you must not
|
||||
* claim that you wrote the original software. If you use this software
|
||||
* in a product, an acknowledgment in the product documentation would be
|
||||
* appreciated but is not required.
|
||||
* 2. Altered source versions must be plainly marked as such, and must not be
|
||||
* misrepresented as being the original software.
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/codestream_header.h"
|
||||
#include "jxl/encode.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/base/printf_macros.h"
|
||||
#include "lib/jxl/base/scope_guard.h"
|
||||
#include "lib/jxl/common.h"
|
||||
#include "lib/jxl/sanitizers.h"
|
||||
#include "png.h" /* original (unpatched) libpng is ok */
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
namespace {
|
||||
|
||||
/* hIST chunk tail is not proccesed properly; skip this chunk completely;
|
||||
see https://github.com/glennrp/libpng/pull/413 */
|
||||
const png_byte kIgnoredPngChunks[] = {
|
||||
104, 73, 83, 84, '\0' /* hIST */
|
||||
};
|
||||
|
||||
// Returns floating-point value from the PNG encoding (times 10^5).
|
||||
static double F64FromU32(const uint32_t x) {
|
||||
return static_cast<int32_t>(x) * 1E-5;
|
||||
}
|
||||
|
||||
Status DecodeSRGB(const unsigned char* payload, const size_t payload_size,
|
||||
JxlColorEncoding* color_encoding) {
|
||||
if (payload_size != 1) return JXL_FAILURE("Wrong sRGB size");
|
||||
// (PNG uses the same values as ICC.)
|
||||
if (payload[0] >= 4) return JXL_FAILURE("Invalid Rendering Intent");
|
||||
color_encoding->rendering_intent =
|
||||
static_cast<JxlRenderingIntent>(payload[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
Status DecodeGAMA(const unsigned char* payload, const size_t payload_size,
|
||||
JxlColorEncoding* color_encoding) {
|
||||
if (payload_size != 4) return JXL_FAILURE("Wrong gAMA size");
|
||||
color_encoding->transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
|
||||
color_encoding->gamma = F64FromU32(LoadBE32(payload));
|
||||
return true;
|
||||
}
|
||||
|
||||
Status DecodeCHRM(const unsigned char* payload, const size_t payload_size,
|
||||
JxlColorEncoding* color_encoding) {
|
||||
if (payload_size != 32) return JXL_FAILURE("Wrong cHRM size");
|
||||
|
||||
color_encoding->white_point = JXL_WHITE_POINT_CUSTOM;
|
||||
color_encoding->white_point_xy[0] = F64FromU32(LoadBE32(payload + 0));
|
||||
color_encoding->white_point_xy[1] = F64FromU32(LoadBE32(payload + 4));
|
||||
|
||||
color_encoding->primaries = JXL_PRIMARIES_CUSTOM;
|
||||
color_encoding->primaries_red_xy[0] = F64FromU32(LoadBE32(payload + 8));
|
||||
color_encoding->primaries_red_xy[1] = F64FromU32(LoadBE32(payload + 12));
|
||||
color_encoding->primaries_green_xy[0] = F64FromU32(LoadBE32(payload + 16));
|
||||
color_encoding->primaries_green_xy[1] = F64FromU32(LoadBE32(payload + 20));
|
||||
color_encoding->primaries_blue_xy[0] = F64FromU32(LoadBE32(payload + 24));
|
||||
color_encoding->primaries_blue_xy[1] = F64FromU32(LoadBE32(payload + 28));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Retrieves XMP and EXIF/IPTC from itext and text.
|
||||
class BlobsReaderPNG {
|
||||
public:
|
||||
static Status Decode(const png_text_struct& info, PackedMetadata* metadata) {
|
||||
// We trust these are properly null-terminated by libpng.
|
||||
const char* key = info.key;
|
||||
const char* value = info.text;
|
||||
if (strstr(key, "XML:com.adobe.xmp")) {
|
||||
metadata->xmp.resize(strlen(value)); // safe, see above
|
||||
memcpy(metadata->xmp.data(), value, metadata->xmp.size());
|
||||
}
|
||||
|
||||
std::string type;
|
||||
std::vector<uint8_t> bytes;
|
||||
|
||||
// Handle text chunks annotated with key "Raw profile type ####", with
|
||||
// #### a type, which may contain metadata.
|
||||
const char* kKey = "Raw profile type ";
|
||||
if (strncmp(key, kKey, strlen(kKey)) != 0) return false;
|
||||
|
||||
if (!MaybeDecodeBase16(key, value, &type, &bytes)) {
|
||||
JXL_WARNING("Couldn't parse 'Raw format type' text chunk");
|
||||
return false;
|
||||
}
|
||||
if (type == "exif") {
|
||||
if (!metadata->exif.empty()) {
|
||||
JXL_WARNING("overwriting EXIF (%" PRIuS " bytes) with base16 (%" PRIuS
|
||||
" bytes)",
|
||||
metadata->exif.size(), bytes.size());
|
||||
}
|
||||
metadata->exif = std::move(bytes);
|
||||
} else if (type == "iptc") {
|
||||
// TODO (jon): Deal with IPTC in some way
|
||||
} else if (type == "8bim") {
|
||||
// TODO (jon): Deal with 8bim in some way
|
||||
} else if (type == "xmp") {
|
||||
if (!metadata->xmp.empty()) {
|
||||
JXL_WARNING("overwriting XMP (%" PRIuS " bytes) with base16 (%" PRIuS
|
||||
" bytes)",
|
||||
metadata->xmp.size(), bytes.size());
|
||||
}
|
||||
metadata->xmp = std::move(bytes);
|
||||
} else {
|
||||
JXL_WARNING("Unknown type in 'Raw format type' text chunk: %s: %" PRIuS
|
||||
" bytes",
|
||||
type.c_str(), bytes.size());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
// Returns false if invalid.
|
||||
static JXL_INLINE Status DecodeNibble(const char c,
|
||||
uint32_t* JXL_RESTRICT nibble) {
|
||||
if ('a' <= c && c <= 'f') {
|
||||
*nibble = 10 + c - 'a';
|
||||
} else if ('0' <= c && c <= '9') {
|
||||
*nibble = c - '0';
|
||||
} else {
|
||||
*nibble = 0;
|
||||
return JXL_FAILURE("Invalid metadata nibble");
|
||||
}
|
||||
JXL_ASSERT(*nibble < 16);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns false if invalid.
|
||||
static JXL_INLINE Status DecodeDecimal(const char** pos, const char* end,
|
||||
uint32_t* JXL_RESTRICT value) {
|
||||
size_t len = 0;
|
||||
*value = 0;
|
||||
while (*pos < end) {
|
||||
char next = **pos;
|
||||
if (next >= '0' && next <= '9') {
|
||||
*value = (*value * 10) + static_cast<uint32_t>(next - '0');
|
||||
len++;
|
||||
if (len > 8) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Do not consume terminator (non-decimal digit).
|
||||
break;
|
||||
}
|
||||
(*pos)++;
|
||||
}
|
||||
if (len == 0 || len > 8) {
|
||||
return JXL_FAILURE("Failed to parse decimal");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parses a PNG text chunk with key of the form "Raw profile type ####", with
|
||||
// #### a type.
|
||||
// Returns whether it could successfully parse the content.
|
||||
// We trust key and encoded are null-terminated because they come from
|
||||
// libpng.
|
||||
static Status MaybeDecodeBase16(const char* key, const char* encoded,
|
||||
std::string* type,
|
||||
std::vector<uint8_t>* bytes) {
|
||||
const char* encoded_end = encoded + strlen(encoded);
|
||||
|
||||
const char* kKey = "Raw profile type ";
|
||||
if (strncmp(key, kKey, strlen(kKey)) != 0) return false;
|
||||
*type = key + strlen(kKey);
|
||||
const size_t kMaxTypeLen = 20;
|
||||
if (type->length() > kMaxTypeLen) return false; // Type too long
|
||||
|
||||
// Header: freeform string and number of bytes
|
||||
// Expected format is:
|
||||
// \n
|
||||
// profile name/description\n
|
||||
// 40\n (the number of bytes after hex-decoding)
|
||||
// 01234566789abcdef....\n (72 bytes per line max).
|
||||
// 012345667\n (last line)
|
||||
const char* pos = encoded;
|
||||
|
||||
if (*(pos++) != '\n') return false;
|
||||
while (pos < encoded_end && *pos != '\n') {
|
||||
pos++;
|
||||
}
|
||||
if (pos == encoded_end) return false;
|
||||
// We parsed so far a \n, some number of non \n characters and are now
|
||||
// pointing at a \n.
|
||||
if (*(pos++) != '\n') return false;
|
||||
uint32_t bytes_to_decode = 0;
|
||||
JXL_RETURN_IF_ERROR(DecodeDecimal(&pos, encoded_end, &bytes_to_decode));
|
||||
|
||||
// We need 2*bytes for the hex values plus 1 byte every 36 values,
|
||||
// plus terminal \n for length.
|
||||
const unsigned long needed_bytes =
|
||||
bytes_to_decode * 2 + 1 + DivCeil(bytes_to_decode, 36);
|
||||
if (needed_bytes != static_cast<size_t>(encoded_end - pos)) {
|
||||
return JXL_FAILURE("Not enough bytes to parse %d bytes in hex",
|
||||
bytes_to_decode);
|
||||
}
|
||||
JXL_ASSERT(bytes->empty());
|
||||
bytes->reserve(bytes_to_decode);
|
||||
|
||||
// Encoding: base16 with newline after 72 chars.
|
||||
// pos points to the \n before the first line of hex values.
|
||||
for (size_t i = 0; i < bytes_to_decode; ++i) {
|
||||
if (i % 36 == 0) {
|
||||
if (pos + 1 >= encoded_end) return false; // Truncated base16 1
|
||||
if (*pos != '\n') return false; // Expected newline
|
||||
++pos;
|
||||
}
|
||||
|
||||
if (pos + 2 >= encoded_end) return false; // Truncated base16 2;
|
||||
uint32_t nibble0, nibble1;
|
||||
JXL_RETURN_IF_ERROR(DecodeNibble(pos[0], &nibble0));
|
||||
JXL_RETURN_IF_ERROR(DecodeNibble(pos[1], &nibble1));
|
||||
bytes->push_back(static_cast<uint8_t>((nibble0 << 4) + nibble1));
|
||||
pos += 2;
|
||||
}
|
||||
if (pos + 1 != encoded_end) return false; // Too many encoded bytes
|
||||
if (pos[0] != '\n') return false; // Incorrect metadata terminator
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr bool isAbc(char c) {
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
|
||||
}
|
||||
|
||||
constexpr uint32_t kId_IHDR = 0x52444849;
|
||||
constexpr uint32_t kId_acTL = 0x4C546361;
|
||||
constexpr uint32_t kId_fcTL = 0x4C546366;
|
||||
constexpr uint32_t kId_IDAT = 0x54414449;
|
||||
constexpr uint32_t kId_fdAT = 0x54416466;
|
||||
constexpr uint32_t kId_IEND = 0x444E4549;
|
||||
constexpr uint32_t kId_iCCP = 0x50434369;
|
||||
constexpr uint32_t kId_sRGB = 0x42475273;
|
||||
constexpr uint32_t kId_gAMA = 0x414D4167;
|
||||
constexpr uint32_t kId_cHRM = 0x4D524863;
|
||||
constexpr uint32_t kId_eXIf = 0x66495865;
|
||||
|
||||
struct APNGFrame {
|
||||
std::vector<uint8_t> pixels;
|
||||
std::vector<uint8_t*> rows;
|
||||
unsigned int w, h, delay_num, delay_den;
|
||||
};
|
||||
|
||||
struct Reader {
|
||||
const uint8_t* next;
|
||||
const uint8_t* last;
|
||||
bool Read(void* data, size_t len) {
|
||||
size_t cap = last - next;
|
||||
size_t to_copy = std::min(cap, len);
|
||||
memcpy(data, next, to_copy);
|
||||
next += to_copy;
|
||||
return (len == to_copy);
|
||||
}
|
||||
bool Eof() { return next == last; }
|
||||
};
|
||||
|
||||
const unsigned long cMaxPNGSize = 1000000UL;
|
||||
const size_t kMaxPNGChunkSize = 1lu << 30; // 1 GB
|
||||
|
||||
void info_fn(png_structp png_ptr, png_infop info_ptr) {
|
||||
png_set_expand(png_ptr);
|
||||
png_set_palette_to_rgb(png_ptr);
|
||||
png_set_tRNS_to_alpha(png_ptr);
|
||||
(void)png_set_interlace_handling(png_ptr);
|
||||
png_read_update_info(png_ptr, info_ptr);
|
||||
}
|
||||
|
||||
void row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num,
|
||||
int pass) {
|
||||
APNGFrame* frame = (APNGFrame*)png_get_progressive_ptr(png_ptr);
|
||||
JXL_CHECK(frame);
|
||||
JXL_CHECK(row_num < frame->rows.size());
|
||||
JXL_CHECK(frame->rows[row_num] < frame->pixels.data() + frame->pixels.size());
|
||||
png_progressive_combine_row(png_ptr, frame->rows[row_num], new_row);
|
||||
}
|
||||
|
||||
inline unsigned int read_chunk(Reader* r, std::vector<uint8_t>* pChunk) {
|
||||
unsigned char len[4];
|
||||
if (r->Read(&len, 4)) {
|
||||
const auto size = png_get_uint_32(len);
|
||||
// Check first, to avoid overflow.
|
||||
if (size > kMaxPNGChunkSize) {
|
||||
JXL_WARNING("APNG chunk size is too big");
|
||||
return 0;
|
||||
}
|
||||
pChunk->resize(size + 12);
|
||||
memcpy(pChunk->data(), len, 4);
|
||||
if (r->Read(pChunk->data() + 4, pChunk->size() - 4)) {
|
||||
return LoadLE32(pChunk->data() + 4);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int processing_start(png_structp& png_ptr, png_infop& info_ptr, void* frame_ptr,
|
||||
bool hasInfo, std::vector<uint8_t>& chunkIHDR,
|
||||
std::vector<std::vector<uint8_t>>& chunksInfo) {
|
||||
unsigned char header[8] = {137, 80, 78, 71, 13, 10, 26, 10};
|
||||
|
||||
png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
|
||||
info_ptr = png_create_info_struct(png_ptr);
|
||||
if (!png_ptr || !info_ptr) return 1;
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr))) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
png_set_keep_unknown_chunks(png_ptr, 1, kIgnoredPngChunks,
|
||||
(int)sizeof(kIgnoredPngChunks) / 5);
|
||||
|
||||
png_set_crc_action(png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE);
|
||||
png_set_progressive_read_fn(png_ptr, frame_ptr, info_fn, row_fn, NULL);
|
||||
|
||||
png_process_data(png_ptr, info_ptr, header, 8);
|
||||
png_process_data(png_ptr, info_ptr, chunkIHDR.data(), chunkIHDR.size());
|
||||
|
||||
if (hasInfo) {
|
||||
for (unsigned int i = 0; i < chunksInfo.size(); i++) {
|
||||
png_process_data(png_ptr, info_ptr, chunksInfo[i].data(),
|
||||
chunksInfo[i].size());
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int processing_data(png_structp png_ptr, png_infop info_ptr, unsigned char* p,
|
||||
unsigned int size) {
|
||||
if (!png_ptr || !info_ptr) return 1;
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr))) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
png_process_data(png_ptr, info_ptr, p, size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int processing_finish(png_structp png_ptr, png_infop info_ptr,
|
||||
PackedMetadata* metadata) {
|
||||
unsigned char footer[12] = {0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130};
|
||||
|
||||
if (!png_ptr || !info_ptr) return 1;
|
||||
|
||||
if (setjmp(png_jmpbuf(png_ptr))) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
png_process_data(png_ptr, info_ptr, footer, 12);
|
||||
// before destroying: check if we encountered any metadata chunks
|
||||
png_textp text_ptr;
|
||||
int num_text;
|
||||
png_get_text(png_ptr, info_ptr, &text_ptr, &num_text);
|
||||
for (int i = 0; i < num_text; i++) {
|
||||
(void)BlobsReaderPNG::Decode(text_ptr[i], metadata);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Status DecodeImageAPNG(const Span<const uint8_t> bytes,
|
||||
const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints,
|
||||
PackedPixelFile* ppf) {
|
||||
Reader r;
|
||||
unsigned int id, j, w, h, w0, h0, x0, y0;
|
||||
unsigned int delay_num, delay_den, dop, bop, rowbytes, imagesize;
|
||||
unsigned char sig[8];
|
||||
png_structp png_ptr = nullptr;
|
||||
png_infop info_ptr = nullptr;
|
||||
std::vector<uint8_t> chunk;
|
||||
std::vector<uint8_t> chunkIHDR;
|
||||
std::vector<std::vector<uint8_t>> chunksInfo;
|
||||
bool isAnimated = false;
|
||||
bool hasInfo = false;
|
||||
APNGFrame frameRaw = {};
|
||||
uint32_t num_channels;
|
||||
JxlPixelFormat format;
|
||||
unsigned int bytes_per_pixel = 0;
|
||||
|
||||
struct FrameInfo {
|
||||
PackedImage data;
|
||||
uint32_t duration;
|
||||
size_t x0, xsize;
|
||||
size_t y0, ysize;
|
||||
uint32_t dispose_op;
|
||||
uint32_t blend_op;
|
||||
};
|
||||
|
||||
std::vector<FrameInfo> frames;
|
||||
|
||||
// Make sure png memory is released in any case.
|
||||
auto scope_guard = MakeScopeGuard([&]() {
|
||||
png_destroy_read_struct(&png_ptr, &info_ptr, 0);
|
||||
// Just in case. Not all versions on libpng wipe-out the pointers.
|
||||
png_ptr = nullptr;
|
||||
info_ptr = nullptr;
|
||||
});
|
||||
|
||||
r = {bytes.data(), bytes.data() + bytes.size()};
|
||||
// Not a PNG => not an error
|
||||
unsigned char png_signature[8] = {137, 80, 78, 71, 13, 10, 26, 10};
|
||||
if (!r.Read(sig, 8) || memcmp(sig, png_signature, 8) != 0) {
|
||||
return false;
|
||||
}
|
||||
id = read_chunk(&r, &chunkIHDR);
|
||||
|
||||
ppf->info.exponent_bits_per_sample = 0;
|
||||
ppf->info.alpha_exponent_bits = 0;
|
||||
ppf->info.orientation = JXL_ORIENT_IDENTITY;
|
||||
|
||||
ppf->frames.clear();
|
||||
|
||||
bool have_color = false, have_srgb = false;
|
||||
bool errorstate = true;
|
||||
if (id == kId_IHDR && chunkIHDR.size() == 25) {
|
||||
x0 = 0;
|
||||
y0 = 0;
|
||||
delay_num = 1;
|
||||
delay_den = 10;
|
||||
dop = 0;
|
||||
bop = 0;
|
||||
|
||||
w0 = w = png_get_uint_32(chunkIHDR.data() + 8);
|
||||
h0 = h = png_get_uint_32(chunkIHDR.data() + 12);
|
||||
if (w > cMaxPNGSize || h > cMaxPNGSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// default settings in case e.g. only gAMA is given
|
||||
ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB;
|
||||
ppf->color_encoding.white_point = JXL_WHITE_POINT_D65;
|
||||
ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB;
|
||||
ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
|
||||
|
||||
if (!processing_start(png_ptr, info_ptr, (void*)&frameRaw, hasInfo,
|
||||
chunkIHDR, chunksInfo)) {
|
||||
while (!r.Eof()) {
|
||||
id = read_chunk(&r, &chunk);
|
||||
if (!id) break;
|
||||
|
||||
if (id == kId_acTL && !hasInfo && !isAnimated) {
|
||||
isAnimated = true;
|
||||
ppf->info.have_animation = true;
|
||||
ppf->info.animation.tps_numerator = 1000;
|
||||
ppf->info.animation.tps_denominator = 1;
|
||||
} else if (id == kId_IEND ||
|
||||
(id == kId_fcTL && (!hasInfo || isAnimated))) {
|
||||
if (hasInfo) {
|
||||
if (!processing_finish(png_ptr, info_ptr, &ppf->metadata)) {
|
||||
// Allocates the frame buffer.
|
||||
uint32_t duration = delay_num * 1000 / delay_den;
|
||||
frames.push_back(FrameInfo{PackedImage(w0, h0, format), duration,
|
||||
x0, w0, y0, h0, dop, bop});
|
||||
auto& frame = frames.back().data;
|
||||
for (size_t y = 0; y < h0; ++y) {
|
||||
memcpy(static_cast<uint8_t*>(frame.pixels()) + frame.stride * y,
|
||||
frameRaw.rows[y], bytes_per_pixel * w0);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (id == kId_IEND) {
|
||||
errorstate = false;
|
||||
break;
|
||||
}
|
||||
if (chunk.size() < 34) {
|
||||
return JXL_FAILURE("Received a chunk that is too small (%" PRIuS
|
||||
"B)",
|
||||
chunk.size());
|
||||
}
|
||||
// At this point the old frame is done. Let's start a new one.
|
||||
w0 = png_get_uint_32(chunk.data() + 12);
|
||||
h0 = png_get_uint_32(chunk.data() + 16);
|
||||
x0 = png_get_uint_32(chunk.data() + 20);
|
||||
y0 = png_get_uint_32(chunk.data() + 24);
|
||||
delay_num = png_get_uint_16(chunk.data() + 28);
|
||||
delay_den = png_get_uint_16(chunk.data() + 30);
|
||||
dop = chunk[32];
|
||||
bop = chunk[33];
|
||||
|
||||
if (!delay_den) delay_den = 100;
|
||||
|
||||
if (w0 > cMaxPNGSize || h0 > cMaxPNGSize || x0 > cMaxPNGSize ||
|
||||
y0 > cMaxPNGSize || x0 + w0 > w || y0 + h0 > h || dop > 2 ||
|
||||
bop > 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (hasInfo) {
|
||||
memcpy(chunkIHDR.data() + 8, chunk.data() + 12, 8);
|
||||
if (processing_start(png_ptr, info_ptr, (void*)&frameRaw, hasInfo,
|
||||
chunkIHDR, chunksInfo)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (id == kId_IDAT) {
|
||||
// First IDAT chunk means we now have all header info
|
||||
hasInfo = true;
|
||||
JXL_CHECK(w == png_get_image_width(png_ptr, info_ptr));
|
||||
JXL_CHECK(h == png_get_image_height(png_ptr, info_ptr));
|
||||
int colortype = png_get_color_type(png_ptr, info_ptr);
|
||||
ppf->info.bits_per_sample = png_get_bit_depth(png_ptr, info_ptr);
|
||||
png_color_8p sigbits = NULL;
|
||||
png_get_sBIT(png_ptr, info_ptr, &sigbits);
|
||||
if (colortype & 1) {
|
||||
// palette will actually be 8-bit regardless of the index bitdepth
|
||||
ppf->info.bits_per_sample = 8;
|
||||
}
|
||||
if (colortype & 2) {
|
||||
ppf->info.num_color_channels = 3;
|
||||
ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB;
|
||||
if (sigbits && sigbits->red == sigbits->green &&
|
||||
sigbits->green == sigbits->blue)
|
||||
ppf->info.bits_per_sample = sigbits->red;
|
||||
} else {
|
||||
ppf->info.num_color_channels = 1;
|
||||
ppf->color_encoding.color_space = JXL_COLOR_SPACE_GRAY;
|
||||
if (sigbits) ppf->info.bits_per_sample = sigbits->gray;
|
||||
}
|
||||
if (colortype & 4 ||
|
||||
png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) {
|
||||
ppf->info.alpha_bits = ppf->info.bits_per_sample;
|
||||
if (sigbits) {
|
||||
if (sigbits->alpha &&
|
||||
sigbits->alpha != ppf->info.bits_per_sample) {
|
||||
return JXL_FAILURE("Unsupported alpha bit-depth");
|
||||
}
|
||||
ppf->info.alpha_bits = sigbits->alpha;
|
||||
}
|
||||
} else {
|
||||
ppf->info.alpha_bits = 0;
|
||||
}
|
||||
ppf->color_encoding.color_space =
|
||||
(ppf->info.num_color_channels == 1 ? JXL_COLOR_SPACE_GRAY
|
||||
: JXL_COLOR_SPACE_RGB);
|
||||
ppf->info.xsize = w;
|
||||
ppf->info.ysize = h;
|
||||
JXL_RETURN_IF_ERROR(VerifyDimensions(&constraints, w, h));
|
||||
num_channels =
|
||||
ppf->info.num_color_channels + (ppf->info.alpha_bits ? 1 : 0);
|
||||
format = {
|
||||
/*num_channels=*/num_channels,
|
||||
/*data_type=*/ppf->info.bits_per_sample > 8 ? JXL_TYPE_UINT16
|
||||
: JXL_TYPE_UINT8,
|
||||
/*endianness=*/JXL_BIG_ENDIAN,
|
||||
/*align=*/0,
|
||||
};
|
||||
bytes_per_pixel =
|
||||
num_channels * (format.data_type == JXL_TYPE_UINT16 ? 2 : 1);
|
||||
rowbytes = w * bytes_per_pixel;
|
||||
imagesize = h * rowbytes;
|
||||
frameRaw.pixels.resize(imagesize);
|
||||
frameRaw.rows.resize(h);
|
||||
for (j = 0; j < h; j++)
|
||||
frameRaw.rows[j] = frameRaw.pixels.data() + j * rowbytes;
|
||||
|
||||
if (processing_data(png_ptr, info_ptr, chunk.data(), chunk.size())) {
|
||||
break;
|
||||
}
|
||||
} else if (id == kId_fdAT && isAnimated) {
|
||||
png_save_uint_32(chunk.data() + 4, chunk.size() - 16);
|
||||
memcpy(chunk.data() + 8, "IDAT", 4);
|
||||
if (processing_data(png_ptr, info_ptr, chunk.data() + 4,
|
||||
chunk.size() - 4)) {
|
||||
break;
|
||||
}
|
||||
} else if (id == kId_iCCP) {
|
||||
if (processing_data(png_ptr, info_ptr, chunk.data(), chunk.size())) {
|
||||
JXL_WARNING("Corrupt iCCP chunk");
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO(jon): catch special case of PQ and synthesize color encoding
|
||||
// in that case
|
||||
int compression_type;
|
||||
png_bytep profile;
|
||||
png_charp name;
|
||||
png_uint_32 proflen = 0;
|
||||
auto ok = png_get_iCCP(png_ptr, info_ptr, &name, &compression_type,
|
||||
&profile, &proflen);
|
||||
if (ok && proflen) {
|
||||
ppf->icc.assign(profile, profile + proflen);
|
||||
have_color = true;
|
||||
} else {
|
||||
// TODO(eustas): JXL_WARNING?
|
||||
}
|
||||
} else if (id == kId_sRGB) {
|
||||
JXL_RETURN_IF_ERROR(DecodeSRGB(chunk.data() + 8, chunk.size() - 12,
|
||||
&ppf->color_encoding));
|
||||
have_srgb = true;
|
||||
have_color = true;
|
||||
} else if (id == kId_gAMA) {
|
||||
JXL_RETURN_IF_ERROR(DecodeGAMA(chunk.data() + 8, chunk.size() - 12,
|
||||
&ppf->color_encoding));
|
||||
have_color = true;
|
||||
} else if (id == kId_cHRM) {
|
||||
JXL_RETURN_IF_ERROR(DecodeCHRM(chunk.data() + 8, chunk.size() - 12,
|
||||
&ppf->color_encoding));
|
||||
have_color = true;
|
||||
} else if (id == kId_eXIf) {
|
||||
ppf->metadata.exif.resize(chunk.size() - 12);
|
||||
memcpy(ppf->metadata.exif.data(), chunk.data() + 8,
|
||||
chunk.size() - 12);
|
||||
} else if (!isAbc(chunk[4]) || !isAbc(chunk[5]) || !isAbc(chunk[6]) ||
|
||||
!isAbc(chunk[7])) {
|
||||
break;
|
||||
} else {
|
||||
if (processing_data(png_ptr, info_ptr, chunk.data(), chunk.size())) {
|
||||
break;
|
||||
}
|
||||
if (!hasInfo) {
|
||||
chunksInfo.push_back(chunk);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (have_srgb) {
|
||||
ppf->color_encoding.white_point = JXL_WHITE_POINT_D65;
|
||||
ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB;
|
||||
ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
|
||||
ppf->color_encoding.rendering_intent = JXL_RENDERING_INTENT_PERCEPTUAL;
|
||||
}
|
||||
JXL_RETURN_IF_ERROR(ApplyColorHints(
|
||||
color_hints, have_color, ppf->info.num_color_channels == 1, ppf));
|
||||
}
|
||||
|
||||
if (errorstate) return false;
|
||||
|
||||
bool has_nontrivial_background = false;
|
||||
bool previous_frame_should_be_cleared = false;
|
||||
enum {
|
||||
DISPOSE_OP_NONE = 0,
|
||||
DISPOSE_OP_BACKGROUND = 1,
|
||||
DISPOSE_OP_PREVIOUS = 2,
|
||||
};
|
||||
enum {
|
||||
BLEND_OP_SOURCE = 0,
|
||||
BLEND_OP_OVER = 1,
|
||||
};
|
||||
for (size_t i = 0; i < frames.size(); i++) {
|
||||
auto& frame = frames[i];
|
||||
JXL_ASSERT(frame.data.xsize == frame.xsize);
|
||||
JXL_ASSERT(frame.data.ysize == frame.ysize);
|
||||
|
||||
// Before encountering a DISPOSE_OP_NONE frame, the canvas is filled with 0,
|
||||
// so DISPOSE_OP_BACKGROUND and DISPOSE_OP_PREVIOUS are equivalent.
|
||||
if (frame.dispose_op == DISPOSE_OP_NONE) {
|
||||
has_nontrivial_background = true;
|
||||
}
|
||||
bool should_blend = frame.blend_op == BLEND_OP_OVER;
|
||||
bool use_for_next_frame =
|
||||
has_nontrivial_background && frame.dispose_op != DISPOSE_OP_PREVIOUS;
|
||||
size_t x0 = frame.x0;
|
||||
size_t y0 = frame.y0;
|
||||
size_t xsize = frame.data.xsize;
|
||||
size_t ysize = frame.data.ysize;
|
||||
if (previous_frame_should_be_cleared) {
|
||||
size_t xs = frame.data.xsize;
|
||||
size_t ys = frame.data.ysize;
|
||||
size_t px0 = frames[i - 1].x0;
|
||||
size_t py0 = frames[i - 1].y0;
|
||||
size_t pxs = frames[i - 1].xsize;
|
||||
size_t pys = frames[i - 1].ysize;
|
||||
if (px0 >= x0 && py0 >= y0 && px0 + pxs <= x0 + xs &&
|
||||
py0 + pys <= y0 + ys && frame.blend_op == BLEND_OP_SOURCE &&
|
||||
use_for_next_frame) {
|
||||
// If the previous frame is entirely contained in the current frame and
|
||||
// we are using BLEND_OP_SOURCE, nothing special needs to be done.
|
||||
ppf->frames.emplace_back(std::move(frame.data));
|
||||
} else if (px0 == x0 && py0 == y0 && px0 + pxs == x0 + xs &&
|
||||
py0 + pys == y0 + ys && use_for_next_frame) {
|
||||
// If the new frame has the same size as the old one, but we are
|
||||
// blending, we can instead just not blend.
|
||||
should_blend = false;
|
||||
ppf->frames.emplace_back(std::move(frame.data));
|
||||
} else if (px0 <= x0 && py0 <= y0 && px0 + pxs >= x0 + xs &&
|
||||
py0 + pys >= y0 + ys && use_for_next_frame) {
|
||||
// If the new frame is contained within the old frame, we can pad the
|
||||
// new frame with zeros and not blend.
|
||||
PackedImage new_data(pxs, pys, frame.data.format);
|
||||
memset(new_data.pixels(), 0, new_data.pixels_size);
|
||||
for (size_t y = 0; y < ys; y++) {
|
||||
size_t bytes_per_pixel =
|
||||
PackedImage::BitsPerChannel(new_data.format.data_type) *
|
||||
new_data.format.num_channels / 8;
|
||||
memcpy(static_cast<uint8_t*>(new_data.pixels()) +
|
||||
new_data.stride * (y + y0 - py0) +
|
||||
bytes_per_pixel * (x0 - px0),
|
||||
static_cast<const uint8_t*>(frame.data.pixels()) +
|
||||
frame.data.stride * y,
|
||||
xs * bytes_per_pixel);
|
||||
}
|
||||
|
||||
x0 = px0;
|
||||
y0 = py0;
|
||||
xsize = pxs;
|
||||
ysize = pys;
|
||||
should_blend = false;
|
||||
ppf->frames.emplace_back(std::move(new_data));
|
||||
} else {
|
||||
// If all else fails, insert a dummy blank frame with kReplace.
|
||||
PackedImage blank(pxs, pys, frame.data.format);
|
||||
memset(blank.pixels(), 0, blank.pixels_size);
|
||||
ppf->frames.emplace_back(std::move(blank));
|
||||
auto& pframe = ppf->frames.back();
|
||||
pframe.frame_info.layer_info.crop_x0 = px0;
|
||||
pframe.frame_info.layer_info.crop_y0 = py0;
|
||||
pframe.frame_info.layer_info.xsize = frame.xsize;
|
||||
pframe.frame_info.layer_info.ysize = frame.ysize;
|
||||
pframe.frame_info.duration = 0;
|
||||
pframe.frame_info.layer_info.have_crop = 0;
|
||||
pframe.frame_info.layer_info.blend_info.blendmode = JXL_BLEND_REPLACE;
|
||||
pframe.frame_info.layer_info.blend_info.source = 0;
|
||||
pframe.frame_info.layer_info.save_as_reference = 1;
|
||||
ppf->frames.emplace_back(std::move(frame.data));
|
||||
}
|
||||
} else {
|
||||
ppf->frames.emplace_back(std::move(frame.data));
|
||||
}
|
||||
|
||||
auto& pframe = ppf->frames.back();
|
||||
pframe.frame_info.layer_info.crop_x0 = x0;
|
||||
pframe.frame_info.layer_info.crop_y0 = y0;
|
||||
pframe.frame_info.layer_info.xsize = xsize;
|
||||
pframe.frame_info.layer_info.ysize = ysize;
|
||||
pframe.frame_info.duration = frame.duration;
|
||||
pframe.frame_info.layer_info.blend_info.blendmode =
|
||||
should_blend ? JXL_BLEND_BLEND : JXL_BLEND_REPLACE;
|
||||
bool is_full_size = x0 == 0 && y0 == 0 && xsize == ppf->info.xsize &&
|
||||
ysize == ppf->info.ysize;
|
||||
pframe.frame_info.layer_info.have_crop = is_full_size ? 0 : 1;
|
||||
pframe.frame_info.layer_info.blend_info.source = should_blend ? 1 : 0;
|
||||
pframe.frame_info.layer_info.blend_info.alpha = 0;
|
||||
pframe.frame_info.layer_info.save_as_reference = use_for_next_frame ? 1 : 0;
|
||||
|
||||
previous_frame_should_be_cleared =
|
||||
has_nontrivial_background && frame.dispose_op == DISPOSE_OP_BACKGROUND;
|
||||
}
|
||||
if (ppf->frames.empty()) return JXL_FAILURE("No frames decoded");
|
||||
ppf->frames.back().frame_info.is_last = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
32
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/apng.h
vendored
Normal file
32
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/apng.h
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_EXTRAS_DEC_APNG_H_
|
||||
#define LIB_EXTRAS_DEC_APNG_H_
|
||||
|
||||
// Decodes APNG images in memory.
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "lib/extras/dec/color_hints.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/padded_bytes.h"
|
||||
#include "lib/jxl/base/span.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/codec_in_out.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
// Decodes `bytes` into `ppf`.
|
||||
Status DecodeImageAPNG(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints,
|
||||
PackedPixelFile* ppf);
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_EXTRAS_DEC_APNG_H_
|
||||
218
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/color_description.cc
vendored
Normal file
218
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/color_description.cc
vendored
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/dec/color_description.h"
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace jxl {
|
||||
|
||||
namespace {
|
||||
|
||||
template <typename T>
|
||||
struct EnumName {
|
||||
const char* name;
|
||||
T value;
|
||||
};
|
||||
|
||||
const EnumName<JxlColorSpace> kJxlColorSpaceNames[] = {
|
||||
{"RGB", JXL_COLOR_SPACE_RGB},
|
||||
{"Gra", JXL_COLOR_SPACE_GRAY},
|
||||
{"XYB", JXL_COLOR_SPACE_XYB},
|
||||
{"CS?", JXL_COLOR_SPACE_UNKNOWN},
|
||||
};
|
||||
|
||||
const EnumName<JxlWhitePoint> kJxlWhitePointNames[] = {
|
||||
{"D65", JXL_WHITE_POINT_D65},
|
||||
{"Cst", JXL_WHITE_POINT_CUSTOM},
|
||||
{"EER", JXL_WHITE_POINT_E},
|
||||
{"DCI", JXL_WHITE_POINT_DCI},
|
||||
};
|
||||
|
||||
const EnumName<JxlPrimaries> kJxlPrimariesNames[] = {
|
||||
{"SRG", JXL_PRIMARIES_SRGB},
|
||||
{"Cst", JXL_PRIMARIES_CUSTOM},
|
||||
{"202", JXL_PRIMARIES_2100},
|
||||
{"DCI", JXL_PRIMARIES_P3},
|
||||
};
|
||||
|
||||
const EnumName<JxlTransferFunction> kJxlTransferFunctionNames[] = {
|
||||
{"709", JXL_TRANSFER_FUNCTION_709},
|
||||
{"TF?", JXL_TRANSFER_FUNCTION_UNKNOWN},
|
||||
{"Lin", JXL_TRANSFER_FUNCTION_LINEAR},
|
||||
{"SRG", JXL_TRANSFER_FUNCTION_SRGB},
|
||||
{"PeQ", JXL_TRANSFER_FUNCTION_PQ},
|
||||
{"DCI", JXL_TRANSFER_FUNCTION_DCI},
|
||||
{"HLG", JXL_TRANSFER_FUNCTION_HLG},
|
||||
{"", JXL_TRANSFER_FUNCTION_GAMMA},
|
||||
};
|
||||
|
||||
const EnumName<JxlRenderingIntent> kJxlRenderingIntentNames[] = {
|
||||
{"Per", JXL_RENDERING_INTENT_PERCEPTUAL},
|
||||
{"Rel", JXL_RENDERING_INTENT_RELATIVE},
|
||||
{"Sat", JXL_RENDERING_INTENT_SATURATION},
|
||||
{"Abs", JXL_RENDERING_INTENT_ABSOLUTE},
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
Status ParseEnum(const std::string& token, const EnumName<T>* enum_values,
|
||||
size_t enum_len, T* value) {
|
||||
for (size_t i = 0; i < enum_len; i++) {
|
||||
if (enum_values[i].name == token) {
|
||||
*value = enum_values[i].value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#define ARRAYSIZE(X) (sizeof(X) / sizeof((X)[0]))
|
||||
#define PARSE_ENUM(type, token, value) \
|
||||
ParseEnum<type>(token, k##type##Names, ARRAYSIZE(k##type##Names), value)
|
||||
|
||||
class Tokenizer {
|
||||
public:
|
||||
Tokenizer(const std::string* input, char separator)
|
||||
: input_(input), separator_(separator) {}
|
||||
|
||||
Status Next(std::string* next) {
|
||||
const size_t end = input_->find(separator_, start_);
|
||||
if (end == std::string::npos) {
|
||||
*next = input_->substr(start_); // rest of string
|
||||
} else {
|
||||
*next = input_->substr(start_, end - start_);
|
||||
}
|
||||
if (next->empty()) return JXL_FAILURE("Missing token");
|
||||
start_ = end + 1;
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
const std::string* const input_; // not owned
|
||||
const char separator_;
|
||||
size_t start_ = 0; // of next token
|
||||
};
|
||||
|
||||
Status ParseDouble(const std::string& num, double* d) {
|
||||
char* end;
|
||||
errno = 0;
|
||||
*d = strtod(num.c_str(), &end);
|
||||
if (*d == 0.0 && end == num.c_str()) {
|
||||
return JXL_FAILURE("Invalid double: %s", num.c_str());
|
||||
}
|
||||
if (std::isnan(*d)) {
|
||||
return JXL_FAILURE("Invalid double: %s", num.c_str());
|
||||
}
|
||||
if (errno == ERANGE) {
|
||||
return JXL_FAILURE("Double out of range: %s", num.c_str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Status ParseDouble(Tokenizer* tokenizer, double* d) {
|
||||
std::string num;
|
||||
JXL_RETURN_IF_ERROR(tokenizer->Next(&num));
|
||||
return ParseDouble(num, d);
|
||||
}
|
||||
|
||||
Status ParseColorSpace(Tokenizer* tokenizer, JxlColorEncoding* c) {
|
||||
std::string str;
|
||||
JXL_RETURN_IF_ERROR(tokenizer->Next(&str));
|
||||
JxlColorSpace cs;
|
||||
if (PARSE_ENUM(JxlColorSpace, str, &cs)) {
|
||||
c->color_space = cs;
|
||||
return true;
|
||||
}
|
||||
|
||||
return JXL_FAILURE("Unknown ColorSpace %s", str.c_str());
|
||||
}
|
||||
|
||||
Status ParseWhitePoint(Tokenizer* tokenizer, JxlColorEncoding* c) {
|
||||
if (c->color_space == JXL_COLOR_SPACE_XYB) {
|
||||
// Implicit white point.
|
||||
c->white_point = JXL_WHITE_POINT_D65;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string str;
|
||||
JXL_RETURN_IF_ERROR(tokenizer->Next(&str));
|
||||
if (PARSE_ENUM(JxlWhitePoint, str, &c->white_point)) return true;
|
||||
|
||||
Tokenizer xy_tokenizer(&str, ';');
|
||||
c->white_point = JXL_WHITE_POINT_CUSTOM;
|
||||
JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->white_point_xy + 0));
|
||||
JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->white_point_xy + 1));
|
||||
return true;
|
||||
}
|
||||
|
||||
Status ParsePrimaries(Tokenizer* tokenizer, JxlColorEncoding* c) {
|
||||
if (c->color_space == JXL_COLOR_SPACE_GRAY ||
|
||||
c->color_space == JXL_COLOR_SPACE_XYB) {
|
||||
// No primaries case.
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string str;
|
||||
JXL_RETURN_IF_ERROR(tokenizer->Next(&str));
|
||||
if (PARSE_ENUM(JxlPrimaries, str, &c->primaries)) return true;
|
||||
|
||||
Tokenizer xy_tokenizer(&str, ';');
|
||||
JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->primaries_red_xy + 0));
|
||||
JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->primaries_red_xy + 1));
|
||||
JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->primaries_green_xy + 0));
|
||||
JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->primaries_green_xy + 1));
|
||||
JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->primaries_blue_xy + 0));
|
||||
JXL_RETURN_IF_ERROR(ParseDouble(&xy_tokenizer, c->primaries_blue_xy + 1));
|
||||
c->primaries = JXL_PRIMARIES_CUSTOM;
|
||||
|
||||
return JXL_FAILURE("Invalid primaries %s", str.c_str());
|
||||
}
|
||||
|
||||
Status ParseRenderingIntent(Tokenizer* tokenizer, JxlColorEncoding* c) {
|
||||
std::string str;
|
||||
JXL_RETURN_IF_ERROR(tokenizer->Next(&str));
|
||||
if (PARSE_ENUM(JxlRenderingIntent, str, &c->rendering_intent)) return true;
|
||||
|
||||
return JXL_FAILURE("Invalid RenderingIntent %s\n", str.c_str());
|
||||
}
|
||||
|
||||
Status ParseTransferFunction(Tokenizer* tokenizer, JxlColorEncoding* c) {
|
||||
if (c->color_space == JXL_COLOR_SPACE_XYB) {
|
||||
// Implicit TF.
|
||||
c->transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
|
||||
c->gamma = 1 / 3.;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string str;
|
||||
JXL_RETURN_IF_ERROR(tokenizer->Next(&str));
|
||||
if (PARSE_ENUM(JxlTransferFunction, str, &c->transfer_function)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (str[0] == 'g') {
|
||||
JXL_RETURN_IF_ERROR(ParseDouble(str.substr(1), &c->gamma));
|
||||
c->transfer_function = JXL_TRANSFER_FUNCTION_GAMMA;
|
||||
return true;
|
||||
}
|
||||
|
||||
return JXL_FAILURE("Invalid gamma %s", str.c_str());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Status ParseDescription(const std::string& description, JxlColorEncoding* c) {
|
||||
*c = {};
|
||||
Tokenizer tokenizer(&description, '_');
|
||||
JXL_RETURN_IF_ERROR(ParseColorSpace(&tokenizer, c));
|
||||
JXL_RETURN_IF_ERROR(ParseWhitePoint(&tokenizer, c));
|
||||
JXL_RETURN_IF_ERROR(ParsePrimaries(&tokenizer, c));
|
||||
JXL_RETURN_IF_ERROR(ParseRenderingIntent(&tokenizer, c));
|
||||
JXL_RETURN_IF_ERROR(ParseTransferFunction(&tokenizer, c));
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
22
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/color_description.h
vendored
Normal file
22
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/color_description.h
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_EXTRAS_COLOR_DESCRIPTION_H_
|
||||
#define LIB_EXTRAS_COLOR_DESCRIPTION_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "jxl/color_encoding.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// Parse the color description into a JxlColorEncoding "RGB_D65_SRG_Rel_Lin".
|
||||
Status ParseDescription(const std::string& description,
|
||||
JxlColorEncoding* JXL_RESTRICT c);
|
||||
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_EXTRAS_COLOR_DESCRIPTION_H_
|
||||
38
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/color_description_test.cc
vendored
Normal file
38
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/color_description_test.cc
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/dec/color_description.h"
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "lib/jxl/color_encoding_internal.h"
|
||||
#include "lib/jxl/test_utils.h"
|
||||
|
||||
namespace jxl {
|
||||
|
||||
// Verify ParseDescription(Description) yields the same ColorEncoding
|
||||
TEST(ColorDescriptionTest, RoundTripAll) {
|
||||
for (const auto& cdesc : test::AllEncodings()) {
|
||||
const ColorEncoding c_original = test::ColorEncodingFromDescriptor(cdesc);
|
||||
const std::string description = Description(c_original);
|
||||
printf("%s\n", description.c_str());
|
||||
|
||||
JxlColorEncoding c_external = {};
|
||||
EXPECT_TRUE(ParseDescription(description, &c_external));
|
||||
ColorEncoding c_internal;
|
||||
EXPECT_TRUE(
|
||||
ConvertExternalToInternalColorEncoding(c_external, &c_internal));
|
||||
EXPECT_TRUE(c_original.SameColorEncoding(c_internal))
|
||||
<< "Where c_original=" << c_original
|
||||
<< " and c_internal=" << c_internal;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(ColorDescriptionTest, NanGamma) {
|
||||
const std::string description = "Gra_2_Per_gnan";
|
||||
JxlColorEncoding c;
|
||||
EXPECT_FALSE(ParseDescription(description, &c));
|
||||
}
|
||||
|
||||
} // namespace jxl
|
||||
66
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/color_hints.cc
vendored
Normal file
66
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/color_hints.cc
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/dec/color_hints.h"
|
||||
|
||||
#include "jxl/encode.h"
|
||||
#include "lib/extras/dec/color_description.h"
|
||||
#include "lib/jxl/base/file_io.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
Status ApplyColorHints(const ColorHints& color_hints,
|
||||
const bool color_already_set, const bool is_gray,
|
||||
PackedPixelFile* ppf) {
|
||||
if (color_already_set) {
|
||||
return color_hints.Foreach(
|
||||
[](const std::string& key, const std::string& /*value*/) {
|
||||
JXL_WARNING("Decoder ignoring %s hint", key.c_str());
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
bool got_color_space = false;
|
||||
|
||||
JXL_RETURN_IF_ERROR(color_hints.Foreach(
|
||||
[is_gray, ppf, &got_color_space](const std::string& key,
|
||||
const std::string& value) -> Status {
|
||||
if (key == "color_space") {
|
||||
JxlColorEncoding c_original_external;
|
||||
if (!ParseDescription(value, &c_original_external)) {
|
||||
return JXL_FAILURE("Failed to apply color_space");
|
||||
}
|
||||
ppf->color_encoding = c_original_external;
|
||||
|
||||
if (is_gray !=
|
||||
(ppf->color_encoding.color_space == JXL_COLOR_SPACE_GRAY)) {
|
||||
return JXL_FAILURE("mismatch between file and color_space hint");
|
||||
}
|
||||
|
||||
got_color_space = true;
|
||||
} else if (key == "icc_pathname") {
|
||||
JXL_RETURN_IF_ERROR(ReadFile(value, &ppf->icc));
|
||||
got_color_space = true;
|
||||
} else {
|
||||
JXL_WARNING("Ignoring %s hint", key.c_str());
|
||||
}
|
||||
return true;
|
||||
}));
|
||||
|
||||
if (!got_color_space) {
|
||||
JXL_WARNING("No color_space/icc_pathname given, assuming sRGB");
|
||||
ppf->color_encoding.color_space =
|
||||
is_gray ? JXL_COLOR_SPACE_GRAY : JXL_COLOR_SPACE_RGB;
|
||||
ppf->color_encoding.white_point = JXL_WHITE_POINT_D65;
|
||||
ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB;
|
||||
ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
72
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/color_hints.h
vendored
Normal file
72
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/color_hints.h
vendored
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_EXTRAS_COLOR_HINTS_H_
|
||||
#define LIB_EXTRAS_COLOR_HINTS_H_
|
||||
|
||||
// Not all the formats implemented in the extras lib support bundling color
|
||||
// information into the file, and those that support it may not have it.
|
||||
// To allow attaching color information to those file formats the caller can
|
||||
// define these color hints.
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
class ColorHints {
|
||||
public:
|
||||
// key=color_space, value=Description(c/pp): specify the ColorEncoding of
|
||||
// the pixels for decoding. Otherwise, if the codec did not obtain an ICC
|
||||
// profile from the image, assume sRGB.
|
||||
//
|
||||
// Strings are taken from the command line, so avoid spaces for convenience.
|
||||
void Add(const std::string& key, const std::string& value) {
|
||||
kv_.emplace_back(key, value);
|
||||
}
|
||||
|
||||
// Calls `func(key, value)` for each key/value in the order they were added,
|
||||
// returning false immediately if `func` returns false.
|
||||
template <class Func>
|
||||
Status Foreach(const Func& func) const {
|
||||
for (const KeyValue& kv : kv_) {
|
||||
Status ok = func(kv.key, kv.value);
|
||||
if (!ok) {
|
||||
return JXL_FAILURE("ColorHints::Foreach returned false");
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
// Splitting into key/value avoids parsing in each codec.
|
||||
struct KeyValue {
|
||||
KeyValue(std::string key, std::string value)
|
||||
: key(std::move(key)), value(std::move(value)) {}
|
||||
|
||||
std::string key;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
std::vector<KeyValue> kv_;
|
||||
};
|
||||
|
||||
// Apply the color hints to the decoded image in PackedPixelFile if any.
|
||||
// color_already_set tells whether the color encoding was already set, in which
|
||||
// case the hints are ignored if any hint is passed.
|
||||
Status ApplyColorHints(const ColorHints& color_hints, bool color_already_set,
|
||||
bool is_gray, PackedPixelFile* ppf);
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_EXTRAS_COLOR_HINTS_H_
|
||||
128
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/decode.cc
vendored
Normal file
128
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/decode.cc
vendored
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/dec/decode.h"
|
||||
|
||||
#include <locale>
|
||||
|
||||
#if JPEGXL_ENABLE_APNG
|
||||
#include "lib/extras/dec/apng.h"
|
||||
#endif
|
||||
#if JPEGXL_ENABLE_EXR
|
||||
#include "lib/extras/dec/exr.h"
|
||||
#endif
|
||||
#if JPEGXL_ENABLE_GIF
|
||||
#include "lib/extras/dec/gif.h"
|
||||
#endif
|
||||
#if JPEGXL_ENABLE_JPEG
|
||||
#include "lib/extras/dec/jpg.h"
|
||||
#endif
|
||||
#include "lib/extras/dec/pgx.h"
|
||||
#include "lib/extras/dec/pnm.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
namespace {
|
||||
|
||||
// Any valid encoding is larger (ensures codecs can read the first few bytes)
|
||||
constexpr size_t kMinBytes = 9;
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<Codec> AvailableCodecs() {
|
||||
std::vector<Codec> out;
|
||||
#if JPEGXL_ENABLE_APNG
|
||||
out.push_back(Codec::kPNG);
|
||||
#endif
|
||||
#if JPEGXL_ENABLE_EXR
|
||||
out.push_back(Codec::kEXR);
|
||||
#endif
|
||||
#if JPEGXL_ENABLE_GIF
|
||||
out.push_back(Codec::kGIF);
|
||||
#endif
|
||||
#if JPEGXL_ENABLE_JPEG
|
||||
out.push_back(Codec::kJPG);
|
||||
#endif
|
||||
out.push_back(Codec::kPGX);
|
||||
out.push_back(Codec::kPNM);
|
||||
return out;
|
||||
}
|
||||
|
||||
Codec CodecFromExtension(std::string extension,
|
||||
size_t* JXL_RESTRICT bits_per_sample) {
|
||||
std::transform(
|
||||
extension.begin(), extension.end(), extension.begin(),
|
||||
[](char c) { return std::tolower(c, std::locale::classic()); });
|
||||
if (extension == ".png") return Codec::kPNG;
|
||||
|
||||
if (extension == ".jpg") return Codec::kJPG;
|
||||
if (extension == ".jpeg") return Codec::kJPG;
|
||||
|
||||
if (extension == ".pgx") return Codec::kPGX;
|
||||
|
||||
if (extension == ".pam") return Codec::kPNM;
|
||||
if (extension == ".pnm") return Codec::kPNM;
|
||||
if (extension == ".pgm") return Codec::kPNM;
|
||||
if (extension == ".ppm") return Codec::kPNM;
|
||||
if (extension == ".pfm") {
|
||||
if (bits_per_sample != nullptr) *bits_per_sample = 32;
|
||||
return Codec::kPNM;
|
||||
}
|
||||
|
||||
if (extension == ".gif") return Codec::kGIF;
|
||||
|
||||
if (extension == ".exr") return Codec::kEXR;
|
||||
|
||||
return Codec::kUnknown;
|
||||
}
|
||||
|
||||
Status DecodeBytes(const Span<const uint8_t> bytes,
|
||||
const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints,
|
||||
extras::PackedPixelFile* ppf, Codec* orig_codec) {
|
||||
if (bytes.size() < kMinBytes) return JXL_FAILURE("Too few bytes");
|
||||
|
||||
*ppf = extras::PackedPixelFile();
|
||||
|
||||
// Default values when not set by decoders.
|
||||
ppf->info.uses_original_profile = true;
|
||||
ppf->info.orientation = JXL_ORIENT_IDENTITY;
|
||||
|
||||
Codec codec;
|
||||
#if JPEGXL_ENABLE_APNG
|
||||
if (DecodeImageAPNG(bytes, color_hints, constraints, ppf)) {
|
||||
codec = Codec::kPNG;
|
||||
} else
|
||||
#endif
|
||||
if (DecodeImagePGX(bytes, color_hints, constraints, ppf)) {
|
||||
codec = Codec::kPGX;
|
||||
} else if (DecodeImagePNM(bytes, color_hints, constraints, ppf)) {
|
||||
codec = Codec::kPNM;
|
||||
}
|
||||
#if JPEGXL_ENABLE_GIF
|
||||
else if (DecodeImageGIF(bytes, color_hints, constraints, ppf)) {
|
||||
codec = Codec::kGIF;
|
||||
}
|
||||
#endif
|
||||
#if JPEGXL_ENABLE_JPEG
|
||||
else if (DecodeImageJPG(bytes, color_hints, constraints, ppf)) {
|
||||
codec = Codec::kJPG;
|
||||
}
|
||||
#endif
|
||||
#if JPEGXL_ENABLE_EXR
|
||||
else if (DecodeImageEXR(bytes, color_hints, constraints, ppf)) {
|
||||
codec = Codec::kEXR;
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
return JXL_FAILURE("Codecs failed to decode");
|
||||
}
|
||||
if (orig_codec) *orig_codec = codec;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
52
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/decode.h
vendored
Normal file
52
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/decode.h
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_EXTRAS_DEC_DECODE_H_
|
||||
#define LIB_EXTRAS_DEC_DECODE_H_
|
||||
|
||||
// Facade for image decoders (PNG, PNM, ...).
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/extras/dec/color_hints.h"
|
||||
#include "lib/jxl/base/span.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/codec_in_out.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
// Codecs supported by CodecInOut::Encode.
|
||||
enum class Codec : uint32_t {
|
||||
kUnknown, // for CodecFromExtension
|
||||
kPNG,
|
||||
kPNM,
|
||||
kPGX,
|
||||
kJPG,
|
||||
kGIF,
|
||||
kEXR
|
||||
};
|
||||
|
||||
std::vector<Codec> AvailableCodecs();
|
||||
|
||||
// If and only if extension is ".pfm", *bits_per_sample is updated to 32 so
|
||||
// that Encode() would encode to PFM instead of PPM.
|
||||
Codec CodecFromExtension(std::string extension,
|
||||
size_t* JXL_RESTRICT bits_per_sample = nullptr);
|
||||
|
||||
// Decodes "bytes" info *ppf.
|
||||
// color_space_hint may specify the color space, otherwise, defaults to sRGB.
|
||||
Status DecodeBytes(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints,
|
||||
extras::PackedPixelFile* ppf, Codec* orig_codec = nullptr);
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_EXTRAS_DEC_DECODE_H_
|
||||
184
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/exr.cc
vendored
Normal file
184
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/exr.cc
vendored
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/dec/exr.h"
|
||||
|
||||
#include <ImfChromaticitiesAttribute.h>
|
||||
#include <ImfIO.h>
|
||||
#include <ImfRgbaFile.h>
|
||||
#include <ImfStandardAttributes.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
namespace {
|
||||
|
||||
namespace OpenEXR = OPENEXR_IMF_NAMESPACE;
|
||||
namespace Imath = IMATH_NAMESPACE;
|
||||
|
||||
// OpenEXR::Int64 is deprecated in favor of using uint64_t directly, but using
|
||||
// uint64_t as recommended causes build failures with previous OpenEXR versions
|
||||
// on macOS, where the definition for OpenEXR::Int64 was actually not equivalent
|
||||
// to uint64_t. This alternative should work in all cases.
|
||||
using ExrInt64 = decltype(std::declval<OpenEXR::IStream>().tellg());
|
||||
|
||||
constexpr int kExrBitsPerSample = 16;
|
||||
constexpr int kExrAlphaBits = 16;
|
||||
|
||||
class InMemoryIStream : public OpenEXR::IStream {
|
||||
public:
|
||||
// The data pointed to by `bytes` must outlive the InMemoryIStream.
|
||||
explicit InMemoryIStream(const Span<const uint8_t> bytes)
|
||||
: IStream(/*fileName=*/""), bytes_(bytes) {}
|
||||
|
||||
bool isMemoryMapped() const override { return true; }
|
||||
char* readMemoryMapped(const int n) override {
|
||||
JXL_ASSERT(pos_ + n <= bytes_.size());
|
||||
char* const result =
|
||||
const_cast<char*>(reinterpret_cast<const char*>(bytes_.data() + pos_));
|
||||
pos_ += n;
|
||||
return result;
|
||||
}
|
||||
bool read(char c[], const int n) override {
|
||||
std::copy_n(readMemoryMapped(n), n, c);
|
||||
return pos_ < bytes_.size();
|
||||
}
|
||||
|
||||
ExrInt64 tellg() override { return pos_; }
|
||||
void seekg(const ExrInt64 pos) override {
|
||||
JXL_ASSERT(pos + 1 <= bytes_.size());
|
||||
pos_ = pos;
|
||||
}
|
||||
|
||||
private:
|
||||
const Span<const uint8_t> bytes_;
|
||||
size_t pos_ = 0;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints,
|
||||
PackedPixelFile* ppf) {
|
||||
InMemoryIStream is(bytes);
|
||||
|
||||
#ifdef __EXCEPTIONS
|
||||
std::unique_ptr<OpenEXR::RgbaInputFile> input_ptr;
|
||||
try {
|
||||
input_ptr.reset(new OpenEXR::RgbaInputFile(is));
|
||||
} catch (...) {
|
||||
return JXL_FAILURE("OpenEXR failed to parse input");
|
||||
}
|
||||
OpenEXR::RgbaInputFile& input = *input_ptr;
|
||||
#else
|
||||
OpenEXR::RgbaInputFile input(is);
|
||||
#endif
|
||||
|
||||
if ((input.channels() & OpenEXR::RgbaChannels::WRITE_RGB) !=
|
||||
OpenEXR::RgbaChannels::WRITE_RGB) {
|
||||
return JXL_FAILURE("only RGB OpenEXR files are supported");
|
||||
}
|
||||
const bool has_alpha = (input.channels() & OpenEXR::RgbaChannels::WRITE_A) ==
|
||||
OpenEXR::RgbaChannels::WRITE_A;
|
||||
|
||||
const float intensity_target = OpenEXR::hasWhiteLuminance(input.header())
|
||||
? OpenEXR::whiteLuminance(input.header())
|
||||
: kDefaultIntensityTarget;
|
||||
|
||||
auto image_size = input.displayWindow().size();
|
||||
// Size is computed as max - min, but both bounds are inclusive.
|
||||
++image_size.x;
|
||||
++image_size.y;
|
||||
|
||||
ppf->info.xsize = image_size.x;
|
||||
ppf->info.ysize = image_size.y;
|
||||
ppf->info.num_color_channels = 3;
|
||||
|
||||
const JxlDataType data_type =
|
||||
kExrBitsPerSample == 16 ? JXL_TYPE_FLOAT16 : JXL_TYPE_FLOAT;
|
||||
const JxlPixelFormat format{
|
||||
/*num_channels=*/3u + (has_alpha ? 1u : 0u),
|
||||
/*data_type=*/data_type,
|
||||
/*endianness=*/JXL_NATIVE_ENDIAN,
|
||||
/*align=*/0,
|
||||
};
|
||||
ppf->frames.clear();
|
||||
// Allocates the frame buffer.
|
||||
ppf->frames.emplace_back(image_size.x, image_size.y, format);
|
||||
const auto& frame = ppf->frames.back();
|
||||
|
||||
const int row_size = input.dataWindow().size().x + 1;
|
||||
// Number of rows to read at a time.
|
||||
// https://www.openexr.com/documentation/ReadingAndWritingImageFiles.pdf
|
||||
// recommends reading the whole file at once.
|
||||
const int y_chunk_size = input.displayWindow().size().y + 1;
|
||||
std::vector<OpenEXR::Rgba> input_rows(row_size * y_chunk_size);
|
||||
for (int start_y =
|
||||
std::max(input.dataWindow().min.y, input.displayWindow().min.y);
|
||||
start_y <=
|
||||
std::min(input.dataWindow().max.y, input.displayWindow().max.y);
|
||||
start_y += y_chunk_size) {
|
||||
// Inclusive.
|
||||
const int end_y = std::min(
|
||||
start_y + y_chunk_size - 1,
|
||||
std::min(input.dataWindow().max.y, input.displayWindow().max.y));
|
||||
input.setFrameBuffer(
|
||||
input_rows.data() - input.dataWindow().min.x - start_y * row_size,
|
||||
/*xStride=*/1, /*yStride=*/row_size);
|
||||
input.readPixels(start_y, end_y);
|
||||
for (int exr_y = start_y; exr_y <= end_y; ++exr_y) {
|
||||
const int image_y = exr_y - input.displayWindow().min.y;
|
||||
const OpenEXR::Rgba* const JXL_RESTRICT input_row =
|
||||
&input_rows[(exr_y - start_y) * row_size];
|
||||
uint8_t* row = static_cast<uint8_t*>(frame.color.pixels()) +
|
||||
frame.color.stride * image_y;
|
||||
const uint32_t pixel_size =
|
||||
(3 + (has_alpha ? 1 : 0)) * kExrBitsPerSample / 8;
|
||||
for (int exr_x =
|
||||
std::max(input.dataWindow().min.x, input.displayWindow().min.x);
|
||||
exr_x <=
|
||||
std::min(input.dataWindow().max.x, input.displayWindow().max.x);
|
||||
++exr_x) {
|
||||
const int image_x = exr_x - input.displayWindow().min.x;
|
||||
memcpy(row + image_x * pixel_size,
|
||||
input_row + (exr_x - input.dataWindow().min.x), pixel_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_LINEAR;
|
||||
ppf->color_encoding.color_space = JXL_COLOR_SPACE_RGB;
|
||||
ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB;
|
||||
ppf->color_encoding.white_point = JXL_WHITE_POINT_D65;
|
||||
if (OpenEXR::hasChromaticities(input.header())) {
|
||||
ppf->color_encoding.primaries = JXL_PRIMARIES_CUSTOM;
|
||||
ppf->color_encoding.white_point = JXL_WHITE_POINT_CUSTOM;
|
||||
const auto& chromaticities = OpenEXR::chromaticities(input.header());
|
||||
ppf->color_encoding.primaries_red_xy[0] = chromaticities.red.x;
|
||||
ppf->color_encoding.primaries_red_xy[1] = chromaticities.red.y;
|
||||
ppf->color_encoding.primaries_green_xy[0] = chromaticities.green.x;
|
||||
ppf->color_encoding.primaries_green_xy[1] = chromaticities.green.y;
|
||||
ppf->color_encoding.primaries_blue_xy[0] = chromaticities.blue.x;
|
||||
ppf->color_encoding.primaries_blue_xy[1] = chromaticities.blue.y;
|
||||
ppf->color_encoding.white_point_xy[0] = chromaticities.white.x;
|
||||
ppf->color_encoding.white_point_xy[1] = chromaticities.white.y;
|
||||
}
|
||||
|
||||
// EXR uses binary16 or binary32 floating point format.
|
||||
ppf->info.bits_per_sample = kExrBitsPerSample;
|
||||
ppf->info.exponent_bits_per_sample = kExrBitsPerSample == 16 ? 5 : 8;
|
||||
if (has_alpha) {
|
||||
ppf->info.alpha_bits = kExrAlphaBits;
|
||||
ppf->info.alpha_exponent_bits = ppf->info.exponent_bits_per_sample;
|
||||
ppf->info.alpha_premultiplied = true;
|
||||
}
|
||||
ppf->info.intensity_target = intensity_target;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
29
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/exr.h
vendored
Normal file
29
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/exr.h
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_EXTRAS_DEC_EXR_H_
|
||||
#define LIB_EXTRAS_DEC_EXR_H_
|
||||
|
||||
// Decodes OpenEXR images in memory.
|
||||
|
||||
#include "lib/extras/dec/color_hints.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/padded_bytes.h"
|
||||
#include "lib/jxl/base/span.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/codec_in_out.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
// Decodes `bytes` into `ppf`. color_hints are ignored.
|
||||
Status DecodeImageEXR(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints, PackedPixelFile* ppf);
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_EXTRAS_DEC_EXR_H_
|
||||
414
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/gif.cc
vendored
Normal file
414
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/gif.cc
vendored
Normal file
|
|
@ -0,0 +1,414 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/dec/gif.h"
|
||||
|
||||
#include <gif_lib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/codestream_header.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
#include "lib/jxl/sanitizers.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
namespace {
|
||||
|
||||
struct ReadState {
|
||||
Span<const uint8_t> bytes;
|
||||
};
|
||||
|
||||
struct DGifCloser {
|
||||
void operator()(GifFileType* const ptr) const { DGifCloseFile(ptr, nullptr); }
|
||||
};
|
||||
using GifUniquePtr = std::unique_ptr<GifFileType, DGifCloser>;
|
||||
|
||||
struct PackedRgba {
|
||||
uint8_t r, g, b, a;
|
||||
};
|
||||
|
||||
struct PackedRgb {
|
||||
uint8_t r, g, b;
|
||||
};
|
||||
|
||||
// Gif does not support partial transparency, so this considers any nonzero
|
||||
// alpha channel value as opaque.
|
||||
bool AllOpaque(const PackedImage& color) {
|
||||
for (size_t y = 0; y < color.ysize; ++y) {
|
||||
const PackedRgba* const JXL_RESTRICT row =
|
||||
static_cast<const PackedRgba*>(color.pixels()) + y * color.xsize;
|
||||
for (size_t x = 0; x < color.xsize; ++x) {
|
||||
if (row[x].a == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ensure_have_alpha(PackedFrame* frame) {
|
||||
if (!frame->extra_channels.empty()) return;
|
||||
const JxlPixelFormat alpha_format{
|
||||
/*num_channels=*/1u,
|
||||
/*data_type=*/JXL_TYPE_UINT8,
|
||||
/*endianness=*/JXL_NATIVE_ENDIAN,
|
||||
/*align=*/0,
|
||||
};
|
||||
frame->extra_channels.emplace_back(frame->color.xsize, frame->color.ysize,
|
||||
alpha_format);
|
||||
// We need to set opaque-by-default.
|
||||
std::fill_n(static_cast<uint8_t*>(frame->extra_channels[0].pixels()),
|
||||
frame->color.xsize * frame->color.ysize, 255u);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Status DecodeImageGIF(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints,
|
||||
PackedPixelFile* ppf) {
|
||||
int error = GIF_OK;
|
||||
ReadState state = {bytes};
|
||||
const auto ReadFromSpan = [](GifFileType* const gif, GifByteType* const bytes,
|
||||
int n) {
|
||||
ReadState* const state = reinterpret_cast<ReadState*>(gif->UserData);
|
||||
// giflib API requires the input size `n` to be signed int.
|
||||
if (static_cast<size_t>(n) > state->bytes.size()) {
|
||||
n = state->bytes.size();
|
||||
}
|
||||
memcpy(bytes, state->bytes.data(), n);
|
||||
state->bytes.remove_prefix(n);
|
||||
return n;
|
||||
};
|
||||
GifUniquePtr gif(DGifOpen(&state, ReadFromSpan, &error));
|
||||
if (gif == nullptr) {
|
||||
if (error == D_GIF_ERR_NOT_GIF_FILE) {
|
||||
// Not an error.
|
||||
return false;
|
||||
} else {
|
||||
return JXL_FAILURE("Failed to read GIF: %s", GifErrorString(error));
|
||||
}
|
||||
}
|
||||
error = DGifSlurp(gif.get());
|
||||
if (error != GIF_OK) {
|
||||
return JXL_FAILURE("Failed to read GIF: %s", GifErrorString(gif->Error));
|
||||
}
|
||||
|
||||
msan::UnpoisonMemory(gif.get(), sizeof(*gif));
|
||||
if (gif->SColorMap) {
|
||||
msan::UnpoisonMemory(gif->SColorMap, sizeof(*gif->SColorMap));
|
||||
msan::UnpoisonMemory(
|
||||
gif->SColorMap->Colors,
|
||||
sizeof(*gif->SColorMap->Colors) * gif->SColorMap->ColorCount);
|
||||
}
|
||||
msan::UnpoisonMemory(gif->SavedImages,
|
||||
sizeof(*gif->SavedImages) * gif->ImageCount);
|
||||
|
||||
JXL_RETURN_IF_ERROR(
|
||||
VerifyDimensions<uint32_t>(&constraints, gif->SWidth, gif->SHeight));
|
||||
uint64_t total_pixel_count =
|
||||
static_cast<uint64_t>(gif->SWidth) * gif->SHeight;
|
||||
for (int i = 0; i < gif->ImageCount; ++i) {
|
||||
const SavedImage& image = gif->SavedImages[i];
|
||||
uint32_t w = image.ImageDesc.Width;
|
||||
uint32_t h = image.ImageDesc.Height;
|
||||
JXL_RETURN_IF_ERROR(VerifyDimensions<uint32_t>(&constraints, w, h));
|
||||
uint64_t pixel_count = static_cast<uint64_t>(w) * h;
|
||||
if (total_pixel_count + pixel_count < total_pixel_count) {
|
||||
return JXL_FAILURE("Image too big");
|
||||
}
|
||||
total_pixel_count += pixel_count;
|
||||
if (total_pixel_count > constraints.dec_max_pixels) {
|
||||
return JXL_FAILURE("Image too big");
|
||||
}
|
||||
}
|
||||
|
||||
if (!gif->SColorMap) {
|
||||
for (int i = 0; i < gif->ImageCount; ++i) {
|
||||
if (!gif->SavedImages[i].ImageDesc.ColorMap) {
|
||||
return JXL_FAILURE("Missing GIF color map");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (gif->ImageCount > 1) {
|
||||
ppf->info.have_animation = true;
|
||||
// Delays in GIF are specified in 100ths of a second.
|
||||
ppf->info.animation.tps_numerator = 100;
|
||||
ppf->info.animation.tps_denominator = 1;
|
||||
}
|
||||
|
||||
ppf->frames.clear();
|
||||
ppf->frames.reserve(gif->ImageCount);
|
||||
|
||||
ppf->info.xsize = gif->SWidth;
|
||||
ppf->info.ysize = gif->SHeight;
|
||||
ppf->info.bits_per_sample = 8;
|
||||
ppf->info.exponent_bits_per_sample = 0;
|
||||
// alpha_bits is later set to 8 if we find a frame with transparent pixels.
|
||||
ppf->info.alpha_bits = 0;
|
||||
ppf->info.alpha_exponent_bits = 0;
|
||||
JXL_RETURN_IF_ERROR(ApplyColorHints(color_hints, /*color_already_set=*/false,
|
||||
/*is_gray=*/false, ppf));
|
||||
|
||||
ppf->info.num_color_channels = 3;
|
||||
|
||||
// Pixel format for the 'canvas' onto which we paint
|
||||
// the (potentially individually cropped) GIF frames
|
||||
// of an animation.
|
||||
const JxlPixelFormat canvas_format{
|
||||
/*num_channels=*/4u,
|
||||
/*data_type=*/JXL_TYPE_UINT8,
|
||||
/*endianness=*/JXL_NATIVE_ENDIAN,
|
||||
/*align=*/0,
|
||||
};
|
||||
|
||||
// Pixel format for the JXL PackedFrame that goes into the
|
||||
// PackedPixelFile. Here, we use 3 color channels, and provide
|
||||
// the alpha channel as an extra_channel wherever it is used.
|
||||
const JxlPixelFormat packed_frame_format{
|
||||
/*num_channels=*/3u,
|
||||
/*data_type=*/JXL_TYPE_UINT8,
|
||||
/*endianness=*/JXL_NATIVE_ENDIAN,
|
||||
/*align=*/0,
|
||||
};
|
||||
|
||||
GifColorType background_color;
|
||||
if (gif->SColorMap == nullptr ||
|
||||
gif->SBackGroundColor >= gif->SColorMap->ColorCount) {
|
||||
background_color = {0, 0, 0};
|
||||
} else {
|
||||
background_color = gif->SColorMap->Colors[gif->SBackGroundColor];
|
||||
}
|
||||
const PackedRgba background_rgba{background_color.Red, background_color.Green,
|
||||
background_color.Blue, 0};
|
||||
PackedFrame canvas(gif->SWidth, gif->SHeight, canvas_format);
|
||||
std::fill_n(static_cast<PackedRgba*>(canvas.color.pixels()),
|
||||
canvas.color.xsize * canvas.color.ysize, background_rgba);
|
||||
Rect canvas_rect{0, 0, canvas.color.xsize, canvas.color.ysize};
|
||||
|
||||
Rect previous_rect_if_restore_to_background;
|
||||
|
||||
bool replace = true;
|
||||
bool last_base_was_none = true;
|
||||
for (int i = 0; i < gif->ImageCount; ++i) {
|
||||
const SavedImage& image = gif->SavedImages[i];
|
||||
msan::UnpoisonMemory(image.RasterBits, sizeof(*image.RasterBits) *
|
||||
image.ImageDesc.Width *
|
||||
image.ImageDesc.Height);
|
||||
const Rect image_rect(image.ImageDesc.Left, image.ImageDesc.Top,
|
||||
image.ImageDesc.Width, image.ImageDesc.Height);
|
||||
|
||||
Rect total_rect;
|
||||
if (previous_rect_if_restore_to_background.xsize() != 0 ||
|
||||
previous_rect_if_restore_to_background.ysize() != 0) {
|
||||
const size_t xbegin = std::min(
|
||||
image_rect.x0(), previous_rect_if_restore_to_background.x0());
|
||||
const size_t ybegin = std::min(
|
||||
image_rect.y0(), previous_rect_if_restore_to_background.y0());
|
||||
const size_t xend =
|
||||
std::max(image_rect.x0() + image_rect.xsize(),
|
||||
previous_rect_if_restore_to_background.x0() +
|
||||
previous_rect_if_restore_to_background.xsize());
|
||||
const size_t yend =
|
||||
std::max(image_rect.y0() + image_rect.ysize(),
|
||||
previous_rect_if_restore_to_background.y0() +
|
||||
previous_rect_if_restore_to_background.ysize());
|
||||
total_rect = Rect(xbegin, ybegin, xend - xbegin, yend - ybegin);
|
||||
previous_rect_if_restore_to_background = Rect();
|
||||
replace = true;
|
||||
} else {
|
||||
total_rect = image_rect;
|
||||
replace = false;
|
||||
}
|
||||
if (!image_rect.IsInside(canvas_rect)) {
|
||||
return JXL_FAILURE("GIF frame extends outside of the canvas");
|
||||
}
|
||||
|
||||
// Allocates the frame buffer.
|
||||
ppf->frames.emplace_back(total_rect.xsize(), total_rect.ysize(),
|
||||
packed_frame_format);
|
||||
PackedFrame* frame = &ppf->frames.back();
|
||||
|
||||
// We cannot tell right from the start whether there will be a
|
||||
// need for an alpha channel. This is discovered only as soon as
|
||||
// we see a transparent pixel. We hence initialize alpha lazily.
|
||||
auto set_pixel_alpha = [&frame](size_t x, size_t y, uint8_t a) {
|
||||
// If we do not have an alpha-channel and a==255 (fully opaque),
|
||||
// we can skip setting this pixel-value and rely on
|
||||
// "no alpha channel = no transparency".
|
||||
if (a == 255 && !frame->extra_channels.empty()) return;
|
||||
ensure_have_alpha(frame);
|
||||
static_cast<uint8_t*>(
|
||||
frame->extra_channels[0].pixels())[y * frame->color.xsize + x] = a;
|
||||
};
|
||||
|
||||
const ColorMapObject* const color_map =
|
||||
image.ImageDesc.ColorMap ? image.ImageDesc.ColorMap : gif->SColorMap;
|
||||
JXL_CHECK(color_map);
|
||||
msan::UnpoisonMemory(color_map, sizeof(*color_map));
|
||||
msan::UnpoisonMemory(color_map->Colors,
|
||||
sizeof(*color_map->Colors) * color_map->ColorCount);
|
||||
GraphicsControlBlock gcb;
|
||||
DGifSavedExtensionToGCB(gif.get(), i, &gcb);
|
||||
msan::UnpoisonMemory(&gcb, sizeof(gcb));
|
||||
bool is_full_size = total_rect.x0() == 0 && total_rect.y0() == 0 &&
|
||||
total_rect.xsize() == canvas.color.xsize &&
|
||||
total_rect.ysize() == canvas.color.ysize;
|
||||
if (ppf->info.have_animation) {
|
||||
frame->frame_info.duration = gcb.DelayTime;
|
||||
frame->frame_info.layer_info.have_crop = static_cast<int>(!is_full_size);
|
||||
frame->frame_info.layer_info.crop_x0 = total_rect.x0();
|
||||
frame->frame_info.layer_info.crop_y0 = total_rect.y0();
|
||||
frame->frame_info.layer_info.xsize = frame->color.xsize;
|
||||
frame->frame_info.layer_info.ysize = frame->color.ysize;
|
||||
if (last_base_was_none) {
|
||||
replace = true;
|
||||
}
|
||||
frame->frame_info.layer_info.blend_info.blendmode =
|
||||
replace ? JXL_BLEND_REPLACE : JXL_BLEND_BLEND;
|
||||
// We always only reference at most the last frame
|
||||
frame->frame_info.layer_info.blend_info.source =
|
||||
last_base_was_none ? 0u : 1u;
|
||||
frame->frame_info.layer_info.blend_info.clamp = 1;
|
||||
frame->frame_info.layer_info.blend_info.alpha = 0;
|
||||
// TODO(veluca): this could in principle be implemented.
|
||||
if (last_base_was_none &&
|
||||
(total_rect.x0() != 0 || total_rect.y0() != 0 ||
|
||||
total_rect.xsize() != canvas.color.xsize ||
|
||||
total_rect.ysize() != canvas.color.ysize || !replace)) {
|
||||
return JXL_FAILURE(
|
||||
"GIF with dispose-to-0 is not supported for non-full or "
|
||||
"blended frames");
|
||||
}
|
||||
switch (gcb.DisposalMode) {
|
||||
case DISPOSE_DO_NOT:
|
||||
case DISPOSE_BACKGROUND:
|
||||
frame->frame_info.layer_info.save_as_reference = 1u;
|
||||
last_base_was_none = false;
|
||||
break;
|
||||
case DISPOSE_PREVIOUS:
|
||||
frame->frame_info.layer_info.save_as_reference = 0u;
|
||||
break;
|
||||
default:
|
||||
frame->frame_info.layer_info.save_as_reference = 0u;
|
||||
last_base_was_none = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the canvas by creating a copy first.
|
||||
PackedImage new_canvas_image(canvas.color.xsize, canvas.color.ysize,
|
||||
canvas.color.format);
|
||||
memcpy(new_canvas_image.pixels(), canvas.color.pixels(),
|
||||
new_canvas_image.pixels_size);
|
||||
for (size_t y = 0, byte_index = 0; y < image_rect.ysize(); ++y) {
|
||||
// Assumes format.align == 0. row points to the beginning of the y row in
|
||||
// the image_rect.
|
||||
PackedRgba* row = static_cast<PackedRgba*>(new_canvas_image.pixels()) +
|
||||
(y + image_rect.y0()) * new_canvas_image.xsize +
|
||||
image_rect.x0();
|
||||
for (size_t x = 0; x < image_rect.xsize(); ++x, ++byte_index) {
|
||||
const GifByteType byte = image.RasterBits[byte_index];
|
||||
if (byte >= color_map->ColorCount) {
|
||||
return JXL_FAILURE("GIF color is out of bounds");
|
||||
}
|
||||
|
||||
if (byte == gcb.TransparentColor) continue;
|
||||
GifColorType color = color_map->Colors[byte];
|
||||
row[x].r = color.Red;
|
||||
row[x].g = color.Green;
|
||||
row[x].b = color.Blue;
|
||||
row[x].a = 255;
|
||||
}
|
||||
}
|
||||
const PackedImage& sub_frame_image = frame->color;
|
||||
if (replace) {
|
||||
// Copy from the new canvas image to the subframe
|
||||
for (size_t y = 0; y < total_rect.ysize(); ++y) {
|
||||
const PackedRgba* row_in =
|
||||
static_cast<const PackedRgba*>(new_canvas_image.pixels()) +
|
||||
(y + total_rect.y0()) * new_canvas_image.xsize + total_rect.x0();
|
||||
PackedRgb* row_out = static_cast<PackedRgb*>(sub_frame_image.pixels()) +
|
||||
y * sub_frame_image.xsize;
|
||||
for (size_t x = 0; x < sub_frame_image.xsize; ++x) {
|
||||
row_out[x].r = row_in[x].r;
|
||||
row_out[x].g = row_in[x].g;
|
||||
row_out[x].b = row_in[x].b;
|
||||
set_pixel_alpha(x, y, row_in[x].a);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (size_t y = 0, byte_index = 0; y < image_rect.ysize(); ++y) {
|
||||
// Assumes format.align == 0
|
||||
PackedRgb* row = static_cast<PackedRgb*>(sub_frame_image.pixels()) +
|
||||
y * sub_frame_image.xsize;
|
||||
for (size_t x = 0; x < image_rect.xsize(); ++x, ++byte_index) {
|
||||
const GifByteType byte = image.RasterBits[byte_index];
|
||||
if (byte > color_map->ColorCount) {
|
||||
return JXL_FAILURE("GIF color is out of bounds");
|
||||
}
|
||||
if (byte == gcb.TransparentColor) {
|
||||
row[x].r = 0;
|
||||
row[x].g = 0;
|
||||
row[x].b = 0;
|
||||
set_pixel_alpha(x, y, 0);
|
||||
continue;
|
||||
}
|
||||
GifColorType color = color_map->Colors[byte];
|
||||
row[x].r = color.Red;
|
||||
row[x].g = color.Green;
|
||||
row[x].b = color.Blue;
|
||||
set_pixel_alpha(x, y, 255);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!frame->extra_channels.empty()) {
|
||||
ppf->info.alpha_bits = 8;
|
||||
}
|
||||
|
||||
switch (gcb.DisposalMode) {
|
||||
case DISPOSE_DO_NOT:
|
||||
canvas.color = std::move(new_canvas_image);
|
||||
break;
|
||||
|
||||
case DISPOSE_BACKGROUND:
|
||||
std::fill_n(static_cast<PackedRgba*>(canvas.color.pixels()),
|
||||
canvas.color.xsize * canvas.color.ysize, background_rgba);
|
||||
previous_rect_if_restore_to_background = image_rect;
|
||||
break;
|
||||
|
||||
case DISPOSE_PREVIOUS:
|
||||
break;
|
||||
|
||||
case DISPOSAL_UNSPECIFIED:
|
||||
default:
|
||||
std::fill_n(static_cast<PackedRgba*>(canvas.color.pixels()),
|
||||
canvas.color.xsize * canvas.color.ysize, background_rgba);
|
||||
}
|
||||
}
|
||||
// Finally, if any frame has an alpha-channel, every frame will need
|
||||
// to have an alpha-channel.
|
||||
bool seen_alpha = false;
|
||||
for (const PackedFrame& frame : ppf->frames) {
|
||||
if (!frame.extra_channels.empty()) {
|
||||
seen_alpha = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (seen_alpha) {
|
||||
for (PackedFrame& frame : ppf->frames) {
|
||||
ensure_have_alpha(&frame);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
30
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/gif.h
vendored
Normal file
30
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/gif.h
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_EXTRAS_DEC_GIF_H_
|
||||
#define LIB_EXTRAS_DEC_GIF_H_
|
||||
|
||||
// Decodes GIF images in memory.
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "lib/extras/dec/color_hints.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/span.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/codec_in_out.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
// Decodes `bytes` into `ppf`. color_hints are ignored.
|
||||
Status DecodeImageGIF(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints, PackedPixelFile* ppf);
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_EXTRAS_DEC_GIF_H_
|
||||
289
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/jpg.cc
vendored
Normal file
289
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/jpg.cc
vendored
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/dec/jpg.h"
|
||||
|
||||
#include <jpeglib.h>
|
||||
#include <setjmp.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/sanitizers.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr unsigned char kICCSignature[12] = {
|
||||
0x49, 0x43, 0x43, 0x5F, 0x50, 0x52, 0x4F, 0x46, 0x49, 0x4C, 0x45, 0x00};
|
||||
constexpr int kICCMarker = JPEG_APP0 + 2;
|
||||
|
||||
constexpr unsigned char kExifSignature[6] = {0x45, 0x78, 0x69,
|
||||
0x66, 0x00, 0x00};
|
||||
constexpr int kExifMarker = JPEG_APP0 + 1;
|
||||
|
||||
static inline bool IsJPG(const Span<const uint8_t> bytes) {
|
||||
if (bytes.size() < 2) return false;
|
||||
if (bytes[0] != 0xFF || bytes[1] != 0xD8) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MarkerIsICC(const jpeg_saved_marker_ptr marker) {
|
||||
return marker->marker == kICCMarker &&
|
||||
marker->data_length >= sizeof kICCSignature + 2 &&
|
||||
std::equal(std::begin(kICCSignature), std::end(kICCSignature),
|
||||
marker->data);
|
||||
}
|
||||
bool MarkerIsExif(const jpeg_saved_marker_ptr marker) {
|
||||
return marker->marker == kExifMarker &&
|
||||
marker->data_length >= sizeof kExifSignature + 2 &&
|
||||
std::equal(std::begin(kExifSignature), std::end(kExifSignature),
|
||||
marker->data);
|
||||
}
|
||||
|
||||
Status ReadICCProfile(jpeg_decompress_struct* const cinfo,
|
||||
std::vector<uint8_t>* const icc) {
|
||||
constexpr size_t kICCSignatureSize = sizeof kICCSignature;
|
||||
// ICC signature + uint8_t index + uint8_t max_index.
|
||||
constexpr size_t kICCHeadSize = kICCSignatureSize + 2;
|
||||
// Markers are 1-indexed, and we keep them that way in this vector to get a
|
||||
// convenient 0 at the front for when we compute the offsets later.
|
||||
std::vector<size_t> marker_lengths;
|
||||
int num_markers = 0;
|
||||
int seen_markers_count = 0;
|
||||
bool has_num_markers = false;
|
||||
for (jpeg_saved_marker_ptr marker = cinfo->marker_list; marker != nullptr;
|
||||
marker = marker->next) {
|
||||
// marker is initialized by libjpeg, which we are not instrumenting with
|
||||
// msan.
|
||||
msan::UnpoisonMemory(marker, sizeof(*marker));
|
||||
msan::UnpoisonMemory(marker->data, marker->data_length);
|
||||
if (!MarkerIsICC(marker)) continue;
|
||||
|
||||
const int current_marker = marker->data[kICCSignatureSize];
|
||||
if (current_marker == 0) {
|
||||
return JXL_FAILURE("inconsistent JPEG ICC marker numbering");
|
||||
}
|
||||
const int current_num_markers = marker->data[kICCSignatureSize + 1];
|
||||
if (current_marker > current_num_markers) {
|
||||
return JXL_FAILURE("inconsistent JPEG ICC marker numbering");
|
||||
}
|
||||
if (has_num_markers) {
|
||||
if (current_num_markers != num_markers) {
|
||||
return JXL_FAILURE("inconsistent numbers of JPEG ICC markers");
|
||||
}
|
||||
} else {
|
||||
num_markers = current_num_markers;
|
||||
has_num_markers = true;
|
||||
marker_lengths.resize(num_markers + 1);
|
||||
}
|
||||
|
||||
size_t marker_length = marker->data_length - kICCHeadSize;
|
||||
|
||||
if (marker_length == 0) {
|
||||
// NB: if we allow empty chunks, then the next check is incorrect.
|
||||
return JXL_FAILURE("Empty ICC chunk");
|
||||
}
|
||||
|
||||
if (marker_lengths[current_marker] != 0) {
|
||||
return JXL_FAILURE("duplicate JPEG ICC marker number");
|
||||
}
|
||||
marker_lengths[current_marker] = marker_length;
|
||||
seen_markers_count++;
|
||||
}
|
||||
|
||||
if (marker_lengths.empty()) {
|
||||
// Not an error.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (seen_markers_count != num_markers) {
|
||||
JXL_DASSERT(has_num_markers);
|
||||
return JXL_FAILURE("Incomplete set of ICC chunks");
|
||||
}
|
||||
|
||||
std::vector<size_t> offsets = std::move(marker_lengths);
|
||||
std::partial_sum(offsets.begin(), offsets.end(), offsets.begin());
|
||||
icc->resize(offsets.back());
|
||||
|
||||
for (jpeg_saved_marker_ptr marker = cinfo->marker_list; marker != nullptr;
|
||||
marker = marker->next) {
|
||||
if (!MarkerIsICC(marker)) continue;
|
||||
const uint8_t* first = marker->data + kICCHeadSize;
|
||||
uint8_t current_marker = marker->data[kICCSignatureSize];
|
||||
size_t offset = offsets[current_marker - 1];
|
||||
size_t marker_length = offsets[current_marker] - offset;
|
||||
std::copy_n(first, marker_length, icc->data() + offset);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ReadExif(jpeg_decompress_struct* const cinfo,
|
||||
std::vector<uint8_t>* const exif) {
|
||||
constexpr size_t kExifSignatureSize = sizeof kExifSignature;
|
||||
for (jpeg_saved_marker_ptr marker = cinfo->marker_list; marker != nullptr;
|
||||
marker = marker->next) {
|
||||
// marker is initialized by libjpeg, which we are not instrumenting with
|
||||
// msan.
|
||||
msan::UnpoisonMemory(marker, sizeof(*marker));
|
||||
msan::UnpoisonMemory(marker->data, marker->data_length);
|
||||
if (!MarkerIsExif(marker)) continue;
|
||||
size_t marker_length = marker->data_length - kExifSignatureSize;
|
||||
exif->resize(marker_length);
|
||||
std::copy_n(marker->data + kExifSignatureSize, marker_length, exif->data());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void MyErrorExit(j_common_ptr cinfo) {
|
||||
jmp_buf* env = static_cast<jmp_buf*>(cinfo->client_data);
|
||||
(*cinfo->err->output_message)(cinfo);
|
||||
jpeg_destroy_decompress(reinterpret_cast<j_decompress_ptr>(cinfo));
|
||||
longjmp(*env, 1);
|
||||
}
|
||||
|
||||
void MyOutputMessage(j_common_ptr cinfo) {
|
||||
#if JXL_DEBUG_WARNING == 1
|
||||
char buf[JMSG_LENGTH_MAX + 1];
|
||||
(*cinfo->err->format_message)(cinfo, buf);
|
||||
buf[JMSG_LENGTH_MAX] = 0;
|
||||
JXL_WARNING("%s", buf);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Status DecodeImageJPG(const Span<const uint8_t> bytes,
|
||||
const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints,
|
||||
PackedPixelFile* ppf) {
|
||||
// Don't do anything for non-JPEG files (no need to report an error)
|
||||
if (!IsJPG(bytes)) return false;
|
||||
|
||||
// TODO(veluca): use JPEGData also for pixels?
|
||||
|
||||
// We need to declare all the non-trivial destructor local variables before
|
||||
// the call to setjmp().
|
||||
std::unique_ptr<JSAMPLE[]> row;
|
||||
|
||||
const auto try_catch_block = [&]() -> bool {
|
||||
jpeg_decompress_struct cinfo;
|
||||
// cinfo is initialized by libjpeg, which we are not instrumenting with
|
||||
// msan, therefore we need to initialize cinfo here.
|
||||
msan::UnpoisonMemory(&cinfo, sizeof(cinfo));
|
||||
// Setup error handling in jpeg library so we can deal with broken jpegs in
|
||||
// the fuzzer.
|
||||
jpeg_error_mgr jerr;
|
||||
jmp_buf env;
|
||||
cinfo.err = jpeg_std_error(&jerr);
|
||||
jerr.error_exit = &MyErrorExit;
|
||||
jerr.output_message = &MyOutputMessage;
|
||||
if (setjmp(env)) {
|
||||
return false;
|
||||
}
|
||||
cinfo.client_data = static_cast<void*>(&env);
|
||||
|
||||
jpeg_create_decompress(&cinfo);
|
||||
jpeg_mem_src(&cinfo, reinterpret_cast<const unsigned char*>(bytes.data()),
|
||||
bytes.size());
|
||||
jpeg_save_markers(&cinfo, kICCMarker, 0xFFFF);
|
||||
jpeg_save_markers(&cinfo, kExifMarker, 0xFFFF);
|
||||
const auto failure = [&cinfo](const char* str) -> Status {
|
||||
jpeg_abort_decompress(&cinfo);
|
||||
jpeg_destroy_decompress(&cinfo);
|
||||
return JXL_FAILURE("%s", str);
|
||||
};
|
||||
int read_header_result = jpeg_read_header(&cinfo, TRUE);
|
||||
// TODO(eustas): what about JPEG_HEADER_TABLES_ONLY?
|
||||
if (read_header_result == JPEG_SUSPENDED) {
|
||||
return failure("truncated JPEG input");
|
||||
}
|
||||
if (!VerifyDimensions(&constraints, cinfo.image_width,
|
||||
cinfo.image_height)) {
|
||||
return failure("image too big");
|
||||
}
|
||||
// Might cause CPU-zip bomb.
|
||||
if (cinfo.arith_code) {
|
||||
return failure("arithmetic code JPEGs are not supported");
|
||||
}
|
||||
int nbcomp = cinfo.num_components;
|
||||
if (nbcomp != 1 && nbcomp != 3) {
|
||||
return failure("unsupported number of components in JPEG");
|
||||
}
|
||||
if (!ReadICCProfile(&cinfo, &ppf->icc)) {
|
||||
ppf->icc.clear();
|
||||
// Default to SRGB
|
||||
// Actually, (cinfo.output_components == nbcomp) will be checked after
|
||||
// `jpeg_start_decompress`.
|
||||
ppf->color_encoding.color_space =
|
||||
(nbcomp == 1) ? JXL_COLOR_SPACE_GRAY : JXL_COLOR_SPACE_RGB;
|
||||
ppf->color_encoding.white_point = JXL_WHITE_POINT_D65;
|
||||
ppf->color_encoding.primaries = JXL_PRIMARIES_SRGB;
|
||||
ppf->color_encoding.transfer_function = JXL_TRANSFER_FUNCTION_SRGB;
|
||||
ppf->color_encoding.rendering_intent = JXL_RENDERING_INTENT_PERCEPTUAL;
|
||||
}
|
||||
ReadExif(&cinfo, &ppf->metadata.exif);
|
||||
if (!ApplyColorHints(color_hints, /*color_already_set=*/true,
|
||||
/*is_gray=*/false, ppf)) {
|
||||
return failure("ApplyColorHints failed");
|
||||
}
|
||||
|
||||
ppf->info.xsize = cinfo.image_width;
|
||||
ppf->info.ysize = cinfo.image_height;
|
||||
// Original data is uint, so exponent_bits_per_sample = 0.
|
||||
ppf->info.bits_per_sample = BITS_IN_JSAMPLE;
|
||||
JXL_ASSERT(BITS_IN_JSAMPLE == 8 || BITS_IN_JSAMPLE == 16);
|
||||
ppf->info.exponent_bits_per_sample = 0;
|
||||
ppf->info.uses_original_profile = true;
|
||||
|
||||
// No alpha in JPG
|
||||
ppf->info.alpha_bits = 0;
|
||||
ppf->info.alpha_exponent_bits = 0;
|
||||
|
||||
ppf->info.num_color_channels = nbcomp;
|
||||
ppf->info.orientation = JXL_ORIENT_IDENTITY;
|
||||
|
||||
jpeg_start_decompress(&cinfo);
|
||||
JXL_ASSERT(cinfo.output_components == nbcomp);
|
||||
|
||||
const JxlPixelFormat format{
|
||||
/*num_channels=*/static_cast<uint32_t>(nbcomp),
|
||||
/*data_type=*/BITS_IN_JSAMPLE == 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16,
|
||||
/*endianness=*/JXL_NATIVE_ENDIAN,
|
||||
/*align=*/0,
|
||||
};
|
||||
ppf->frames.clear();
|
||||
// Allocates the frame buffer.
|
||||
ppf->frames.emplace_back(cinfo.image_width, cinfo.image_height, format);
|
||||
const auto& frame = ppf->frames.back();
|
||||
JXL_ASSERT(sizeof(JSAMPLE) * cinfo.output_components * cinfo.image_width <=
|
||||
frame.color.stride);
|
||||
|
||||
for (size_t y = 0; y < cinfo.image_height; ++y) {
|
||||
JSAMPROW rows[] = {reinterpret_cast<JSAMPLE*>(
|
||||
static_cast<uint8_t*>(frame.color.pixels()) +
|
||||
frame.color.stride * y)};
|
||||
jpeg_read_scanlines(&cinfo, rows, 1);
|
||||
msan::UnpoisonMemory(rows[0], sizeof(JSAMPLE) * cinfo.output_components *
|
||||
cinfo.image_width);
|
||||
}
|
||||
|
||||
jpeg_finish_decompress(&cinfo);
|
||||
jpeg_destroy_decompress(&cinfo);
|
||||
return true;
|
||||
};
|
||||
|
||||
return try_catch_block();
|
||||
}
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
33
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/jpg.h
vendored
Normal file
33
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/jpg.h
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_EXTRAS_DEC_JPG_H_
|
||||
#define LIB_EXTRAS_DEC_JPG_H_
|
||||
|
||||
// Decodes JPG pixels and metadata in memory.
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "lib/extras/codec.h"
|
||||
#include "lib/extras/dec/color_hints.h"
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/padded_bytes.h"
|
||||
#include "lib/jxl/base/span.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/codec_in_out.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
// Decodes `bytes` into `ppf`. color_hints are ignored.
|
||||
// `elapsed_deinterleave`, if non-null, will be set to the time (in seconds)
|
||||
// that it took to deinterleave the raw JSAMPLEs to planar floats.
|
||||
Status DecodeImageJPG(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints, PackedPixelFile* ppf);
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_EXTRAS_DEC_JPG_H_
|
||||
480
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/jxl.cc
vendored
Normal file
480
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/jxl.cc
vendored
Normal file
|
|
@ -0,0 +1,480 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/dec/jxl.h"
|
||||
|
||||
#include "jxl/decode.h"
|
||||
#include "jxl/decode_cxx.h"
|
||||
#include "jxl/types.h"
|
||||
#include "lib/extras/dec/color_description.h"
|
||||
#include "lib/extras/enc/encode.h"
|
||||
#include "lib/jxl/base/printf_macros.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
namespace {
|
||||
|
||||
struct BoxProcessor {
|
||||
BoxProcessor(JxlDecoder* dec) : dec_(dec) { Reset(); }
|
||||
|
||||
void InitializeOutput(std::vector<uint8_t>* out) {
|
||||
box_data_ = out;
|
||||
AddMoreOutput();
|
||||
}
|
||||
|
||||
bool AddMoreOutput() {
|
||||
Flush();
|
||||
static const size_t kBoxOutputChunkSize = 1 << 16;
|
||||
box_data_->resize(box_data_->size() + kBoxOutputChunkSize);
|
||||
next_out_ = box_data_->data() + total_size_;
|
||||
avail_out_ = box_data_->size() - total_size_;
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderSetBoxBuffer(dec_, next_out_, avail_out_)) {
|
||||
fprintf(stderr, "JxlDecoderSetBoxBuffer failed\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void FinalizeOutput() {
|
||||
if (box_data_ == nullptr) return;
|
||||
Flush();
|
||||
box_data_->resize(total_size_);
|
||||
Reset();
|
||||
}
|
||||
|
||||
private:
|
||||
JxlDecoder* dec_;
|
||||
std::vector<uint8_t>* box_data_;
|
||||
uint8_t* next_out_;
|
||||
size_t avail_out_;
|
||||
size_t total_size_;
|
||||
|
||||
void Reset() {
|
||||
box_data_ = nullptr;
|
||||
next_out_ = nullptr;
|
||||
avail_out_ = 0;
|
||||
total_size_ = 0;
|
||||
}
|
||||
void Flush() {
|
||||
if (box_data_ == nullptr) return;
|
||||
size_t remaining = JxlDecoderReleaseBoxBuffer(dec_);
|
||||
size_t bytes_written = avail_out_ - remaining;
|
||||
next_out_ += bytes_written;
|
||||
avail_out_ -= bytes_written;
|
||||
total_size_ += bytes_written;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
|
||||
const JXLDecompressParams& dparams, size_t* decoded_bytes,
|
||||
PackedPixelFile* ppf, std::vector<uint8_t>* jpeg_bytes) {
|
||||
auto decoder = JxlDecoderMake(/*memory_manager=*/nullptr);
|
||||
JxlDecoder* dec = decoder.get();
|
||||
ppf->frames.clear();
|
||||
|
||||
if (dparams.runner_opaque != nullptr &&
|
||||
JXL_DEC_SUCCESS != JxlDecoderSetParallelRunner(dec, dparams.runner,
|
||||
dparams.runner_opaque)) {
|
||||
fprintf(stderr, "JxlEncoderSetParallelRunner failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
JxlPixelFormat format;
|
||||
std::vector<JxlPixelFormat> accepted_formats = dparams.accepted_formats;
|
||||
if (accepted_formats.empty()) {
|
||||
for (const uint32_t num_channels : {1, 2, 3, 4}) {
|
||||
accepted_formats.push_back(
|
||||
{num_channels, JXL_TYPE_FLOAT, JXL_LITTLE_ENDIAN, /*align=*/0});
|
||||
}
|
||||
}
|
||||
JxlColorEncoding color_encoding;
|
||||
size_t num_color_channels = 0;
|
||||
if (!dparams.color_space.empty()) {
|
||||
if (!jxl::ParseDescription(dparams.color_space, &color_encoding)) {
|
||||
fprintf(stderr, "Failed to parse color space %s.\n",
|
||||
dparams.color_space.c_str());
|
||||
return false;
|
||||
}
|
||||
num_color_channels =
|
||||
color_encoding.color_space == JXL_COLOR_SPACE_GRAY ? 1 : 3;
|
||||
}
|
||||
|
||||
bool can_reconstruct_jpeg = false;
|
||||
std::vector<uint8_t> jpeg_data_chunk;
|
||||
if (jpeg_bytes != nullptr) {
|
||||
jpeg_data_chunk.resize(16384);
|
||||
jpeg_bytes->resize(0);
|
||||
}
|
||||
|
||||
int events = (JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE);
|
||||
|
||||
bool max_passes_defined =
|
||||
(dparams.max_passes < std::numeric_limits<uint32_t>::max());
|
||||
if (max_passes_defined || dparams.max_downsampling > 1) {
|
||||
events |= JXL_DEC_FRAME_PROGRESSION;
|
||||
if (max_passes_defined) {
|
||||
JxlDecoderSetProgressiveDetail(dec, JxlProgressiveDetail::kPasses);
|
||||
} else {
|
||||
JxlDecoderSetProgressiveDetail(dec, JxlProgressiveDetail::kLastPasses);
|
||||
}
|
||||
}
|
||||
if (jpeg_bytes != nullptr) {
|
||||
events |= JXL_DEC_JPEG_RECONSTRUCTION;
|
||||
} else {
|
||||
events |= (JXL_DEC_COLOR_ENCODING | JXL_DEC_FRAME | JXL_DEC_PREVIEW_IMAGE |
|
||||
JXL_DEC_BOX);
|
||||
}
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderSubscribeEvents(dec, events)) {
|
||||
fprintf(stderr, "JxlDecoderSubscribeEvents failed\n");
|
||||
return false;
|
||||
}
|
||||
if (jpeg_bytes == nullptr) {
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderSetRenderSpotcolors(dec, dparams.render_spotcolors)) {
|
||||
fprintf(stderr, "JxlDecoderSetRenderSpotColors failed\n");
|
||||
return false;
|
||||
}
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderSetKeepOrientation(dec, dparams.keep_orientation)) {
|
||||
fprintf(stderr, "JxlDecoderSetKeepOrientation failed\n");
|
||||
return false;
|
||||
}
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderSetUnpremultiplyAlpha(dec, dparams.unpremultiply_alpha)) {
|
||||
fprintf(stderr, "JxlDecoderSetUnpremultiplyAlpha failed\n");
|
||||
return false;
|
||||
}
|
||||
if (dparams.display_nits > 0 &&
|
||||
JXL_DEC_SUCCESS !=
|
||||
JxlDecoderSetDesiredIntensityTarget(dec, dparams.display_nits)) {
|
||||
fprintf(stderr, "Decoder failed to set desired intensity target\n");
|
||||
return false;
|
||||
}
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderSetDecompressBoxes(dec, JXL_TRUE)) {
|
||||
fprintf(stderr, "JxlDecoderSetDecompressBoxes failed\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderSetInput(dec, bytes, bytes_size)) {
|
||||
fprintf(stderr, "Decoder failed to set input\n");
|
||||
return false;
|
||||
}
|
||||
uint32_t progression_index = 0;
|
||||
bool codestream_done = false;
|
||||
BoxProcessor boxes(dec);
|
||||
for (;;) {
|
||||
JxlDecoderStatus status = JxlDecoderProcessInput(dec);
|
||||
if (status == JXL_DEC_ERROR) {
|
||||
fprintf(stderr, "Failed to decode image\n");
|
||||
return false;
|
||||
} else if (status == JXL_DEC_NEED_MORE_INPUT) {
|
||||
if (codestream_done) {
|
||||
break;
|
||||
}
|
||||
if (dparams.allow_partial_input) {
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderFlushImage(dec)) {
|
||||
fprintf(stderr,
|
||||
"Input file is truncated and there is no preview "
|
||||
"available yet.\n");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
fprintf(stderr,
|
||||
"Input file is truncated and allow_partial_input was disabled.");
|
||||
return false;
|
||||
} else if (status == JXL_DEC_BOX) {
|
||||
boxes.FinalizeOutput();
|
||||
JxlBoxType box_type;
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderGetBoxType(dec, box_type, JXL_TRUE)) {
|
||||
fprintf(stderr, "JxlDecoderGetBoxType failed\n");
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t>* box_data = nullptr;
|
||||
if (memcmp(box_type, "Exif", 4) == 0) {
|
||||
box_data = &ppf->metadata.exif;
|
||||
} else if (memcmp(box_type, "iptc", 4) == 0) {
|
||||
box_data = &ppf->metadata.iptc;
|
||||
} else if (memcmp(box_type, "jumb", 4) == 0) {
|
||||
box_data = &ppf->metadata.jumbf;
|
||||
} else if (memcmp(box_type, "xml ", 4) == 0) {
|
||||
box_data = &ppf->metadata.xmp;
|
||||
}
|
||||
if (box_data) {
|
||||
boxes.InitializeOutput(box_data);
|
||||
}
|
||||
} else if (status == JXL_DEC_BOX_NEED_MORE_OUTPUT) {
|
||||
boxes.AddMoreOutput();
|
||||
} else if (status == JXL_DEC_JPEG_RECONSTRUCTION) {
|
||||
can_reconstruct_jpeg = true;
|
||||
// Decoding to JPEG.
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderSetJPEGBuffer(dec,
|
||||
jpeg_data_chunk.data(),
|
||||
jpeg_data_chunk.size())) {
|
||||
fprintf(stderr, "Decoder failed to set JPEG Buffer\n");
|
||||
return false;
|
||||
}
|
||||
} else if (status == JXL_DEC_JPEG_NEED_MORE_OUTPUT) {
|
||||
// Decoded a chunk to JPEG.
|
||||
size_t used_jpeg_output =
|
||||
jpeg_data_chunk.size() - JxlDecoderReleaseJPEGBuffer(dec);
|
||||
jpeg_bytes->insert(jpeg_bytes->end(), jpeg_data_chunk.data(),
|
||||
jpeg_data_chunk.data() + used_jpeg_output);
|
||||
if (used_jpeg_output == 0) {
|
||||
// Chunk is too small.
|
||||
jpeg_data_chunk.resize(jpeg_data_chunk.size() * 2);
|
||||
}
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderSetJPEGBuffer(dec,
|
||||
jpeg_data_chunk.data(),
|
||||
jpeg_data_chunk.size())) {
|
||||
fprintf(stderr, "Decoder failed to set JPEG Buffer\n");
|
||||
return false;
|
||||
}
|
||||
} else if (status == JXL_DEC_BASIC_INFO) {
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderGetBasicInfo(dec, &ppf->info)) {
|
||||
fprintf(stderr, "JxlDecoderGetBasicInfo failed\n");
|
||||
return false;
|
||||
}
|
||||
if (num_color_channels != 0) {
|
||||
// Mark the change in number of color channels due to the requested
|
||||
// color space.
|
||||
ppf->info.num_color_channels = num_color_channels;
|
||||
}
|
||||
// Select format according to accepted formats.
|
||||
if (!jxl::extras::SelectFormat(accepted_formats, ppf->info, &format)) {
|
||||
fprintf(stderr, "SelectFormat failed\n");
|
||||
return false;
|
||||
}
|
||||
bool have_alpha = (format.num_channels == 2 || format.num_channels == 4);
|
||||
if (!have_alpha) {
|
||||
// Mark in the basic info that alpha channel was dropped.
|
||||
ppf->info.alpha_bits = 0;
|
||||
} else if (dparams.unpremultiply_alpha) {
|
||||
// Mark in the basic info that alpha was unpremultiplied.
|
||||
ppf->info.alpha_premultiplied = false;
|
||||
}
|
||||
bool alpha_found = false;
|
||||
for (uint32_t i = 0; i < ppf->info.num_extra_channels; ++i) {
|
||||
JxlExtraChannelInfo eci;
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderGetExtraChannelInfo(dec, i, &eci)) {
|
||||
fprintf(stderr, "JxlDecoderGetExtraChannelInfo failed\n");
|
||||
return false;
|
||||
}
|
||||
if (eci.type == JXL_CHANNEL_ALPHA && have_alpha && !alpha_found) {
|
||||
// Skip the first alpha channels because it is already present in the
|
||||
// interleaved image.
|
||||
alpha_found = true;
|
||||
continue;
|
||||
}
|
||||
std::string name(eci.name_length + 1, 0);
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderGetExtraChannelName(dec, i, &name[0], name.size())) {
|
||||
fprintf(stderr, "JxlDecoderGetExtraChannelName failed\n");
|
||||
return false;
|
||||
}
|
||||
name.resize(eci.name_length);
|
||||
ppf->extra_channels_info.push_back({eci, i, name});
|
||||
}
|
||||
} else if (status == JXL_DEC_COLOR_ENCODING) {
|
||||
if (!dparams.color_space.empty()) {
|
||||
if (ppf->info.uses_original_profile) {
|
||||
fprintf(stderr,
|
||||
"Warning: --color_space ignored because the image is "
|
||||
"not XYB encoded.\n");
|
||||
} else {
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderSetPreferredColorProfile(dec, &color_encoding)) {
|
||||
fprintf(stderr, "Failed to set color space.\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
size_t icc_size = 0;
|
||||
JxlColorProfileTarget target = JXL_COLOR_PROFILE_TARGET_DATA;
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderGetICCProfileSize(dec, nullptr, target, &icc_size)) {
|
||||
fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n");
|
||||
}
|
||||
if (icc_size != 0) {
|
||||
ppf->icc.resize(icc_size);
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderGetColorAsICCProfile(dec, nullptr, target,
|
||||
ppf->icc.data(), icc_size)) {
|
||||
fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderGetColorAsEncodedProfile(
|
||||
dec, nullptr, target, &ppf->color_encoding)) {
|
||||
ppf->color_encoding.color_space = JXL_COLOR_SPACE_UNKNOWN;
|
||||
}
|
||||
icc_size = 0;
|
||||
target = JXL_COLOR_PROFILE_TARGET_ORIGINAL;
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderGetICCProfileSize(dec, nullptr, target, &icc_size)) {
|
||||
fprintf(stderr, "JxlDecoderGetICCProfileSize failed\n");
|
||||
}
|
||||
if (icc_size != 0) {
|
||||
ppf->orig_icc.resize(icc_size);
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderGetColorAsICCProfile(dec, nullptr, target,
|
||||
ppf->orig_icc.data(), icc_size)) {
|
||||
fprintf(stderr, "JxlDecoderGetColorAsICCProfile failed\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (status == JXL_DEC_FRAME) {
|
||||
jxl::extras::PackedFrame frame(ppf->info.xsize, ppf->info.ysize, format);
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderGetFrameHeader(dec, &frame.frame_info)) {
|
||||
fprintf(stderr, "JxlDecoderGetFrameHeader failed\n");
|
||||
return false;
|
||||
}
|
||||
frame.name.resize(frame.frame_info.name_length + 1, 0);
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderGetFrameName(dec, &frame.name[0], frame.name.size())) {
|
||||
fprintf(stderr, "JxlDecoderGetFrameName failed\n");
|
||||
return false;
|
||||
}
|
||||
frame.name.resize(frame.frame_info.name_length);
|
||||
ppf->frames.emplace_back(std::move(frame));
|
||||
progression_index = 0;
|
||||
} else if (status == JXL_DEC_FRAME_PROGRESSION) {
|
||||
size_t downsampling = JxlDecoderGetIntendedDownsamplingRatio(dec);
|
||||
if ((max_passes_defined && progression_index >= dparams.max_passes) ||
|
||||
(!max_passes_defined && downsampling <= dparams.max_downsampling)) {
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderFlushImage(dec)) {
|
||||
fprintf(stderr, "JxlDecoderFlushImage failed\n");
|
||||
return false;
|
||||
}
|
||||
if (ppf->frames.back().frame_info.is_last) {
|
||||
break;
|
||||
}
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderSkipCurrentFrame(dec)) {
|
||||
fprintf(stderr, "JxlDecoderSkipCurrentFrame failed\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
++progression_index;
|
||||
} else if (status == JXL_DEC_NEED_PREVIEW_OUT_BUFFER) {
|
||||
size_t buffer_size;
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderPreviewOutBufferSize(dec, &format, &buffer_size)) {
|
||||
fprintf(stderr, "JxlDecoderPreviewOutBufferSize failed\n");
|
||||
return false;
|
||||
}
|
||||
ppf->preview_frame = std::unique_ptr<jxl::extras::PackedFrame>(
|
||||
new jxl::extras::PackedFrame(ppf->info.preview.xsize,
|
||||
ppf->info.preview.ysize, format));
|
||||
if (buffer_size != ppf->preview_frame->color.pixels_size) {
|
||||
fprintf(stderr, "Invalid out buffer size %" PRIuS " %" PRIuS "\n",
|
||||
buffer_size, ppf->preview_frame->color.pixels_size);
|
||||
return false;
|
||||
}
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderSetPreviewOutBuffer(
|
||||
dec, &format, ppf->preview_frame->color.pixels(), buffer_size)) {
|
||||
fprintf(stderr, "JxlDecoderSetPreviewOutBuffer failed\n");
|
||||
return false;
|
||||
}
|
||||
} else if (status == JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
|
||||
if (jpeg_bytes != nullptr) {
|
||||
break;
|
||||
}
|
||||
size_t buffer_size;
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderImageOutBufferSize(dec, &format, &buffer_size)) {
|
||||
fprintf(stderr, "JxlDecoderImageOutBufferSize failed\n");
|
||||
return false;
|
||||
}
|
||||
jxl::extras::PackedFrame& frame = ppf->frames.back();
|
||||
if (buffer_size != frame.color.pixels_size) {
|
||||
fprintf(stderr, "Invalid out buffer size %" PRIuS " %" PRIuS "\n",
|
||||
buffer_size, frame.color.pixels_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (dparams.use_image_callback) {
|
||||
auto callback = [](void* opaque, size_t x, size_t y, size_t num_pixels,
|
||||
const void* pixels) {
|
||||
auto* ppf = reinterpret_cast<jxl::extras::PackedPixelFile*>(opaque);
|
||||
jxl::extras::PackedImage& color = ppf->frames.back().color;
|
||||
uint8_t* pixels_buffer = reinterpret_cast<uint8_t*>(color.pixels());
|
||||
size_t sample_size = color.pixel_stride();
|
||||
memcpy(pixels_buffer + (color.stride * y + sample_size * x), pixels,
|
||||
num_pixels * sample_size);
|
||||
};
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderSetImageOutCallback(dec, &format, callback, ppf)) {
|
||||
fprintf(stderr, "JxlDecoderSetImageOutCallback failed\n");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderSetImageOutBuffer(dec, &format,
|
||||
frame.color.pixels(),
|
||||
buffer_size)) {
|
||||
fprintf(stderr, "JxlDecoderSetImageOutBuffer failed\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
JxlPixelFormat ec_format = format;
|
||||
ec_format.num_channels = 1;
|
||||
for (const auto& eci : ppf->extra_channels_info) {
|
||||
frame.extra_channels.emplace_back(jxl::extras::PackedImage(
|
||||
ppf->info.xsize, ppf->info.ysize, ec_format));
|
||||
auto& ec = frame.extra_channels.back();
|
||||
size_t buffer_size;
|
||||
if (JXL_DEC_SUCCESS != JxlDecoderExtraChannelBufferSize(
|
||||
dec, &ec_format, &buffer_size, eci.index)) {
|
||||
fprintf(stderr, "JxlDecoderExtraChannelBufferSize failed\n");
|
||||
return false;
|
||||
}
|
||||
if (buffer_size != ec.pixels_size) {
|
||||
fprintf(stderr,
|
||||
"Invalid extra channel buffer size"
|
||||
" %" PRIuS " %" PRIuS "\n",
|
||||
buffer_size, ec.pixels_size);
|
||||
return false;
|
||||
}
|
||||
if (JXL_DEC_SUCCESS !=
|
||||
JxlDecoderSetExtraChannelBuffer(dec, &ec_format, ec.pixels(),
|
||||
buffer_size, eci.index)) {
|
||||
fprintf(stderr, "JxlDecoderSetExtraChannelBuffer failed\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (status == JXL_DEC_SUCCESS) {
|
||||
// Decoding finished successfully.
|
||||
break;
|
||||
} else if (status == JXL_DEC_PREVIEW_IMAGE) {
|
||||
// Nothing to do.
|
||||
} else if (status == JXL_DEC_FULL_IMAGE) {
|
||||
if (jpeg_bytes != nullptr || ppf->frames.back().frame_info.is_last) {
|
||||
codestream_done = true;
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Error: unexpected status: %d\n",
|
||||
static_cast<int>(status));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
boxes.FinalizeOutput();
|
||||
if (jpeg_bytes != nullptr) {
|
||||
if (!can_reconstruct_jpeg) return false;
|
||||
size_t used_jpeg_output =
|
||||
jpeg_data_chunk.size() - JxlDecoderReleaseJPEGBuffer(dec);
|
||||
jpeg_bytes->insert(jpeg_bytes->end(), jpeg_data_chunk.data(),
|
||||
jpeg_data_chunk.data() + used_jpeg_output);
|
||||
}
|
||||
if (decoded_bytes) {
|
||||
*decoded_bytes = bytes_size - JxlDecoderReleaseInput(dec);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
66
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/jxl.h
vendored
Normal file
66
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/jxl.h
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_EXTRAS_DEC_JXL_H_
|
||||
#define LIB_EXTRAS_DEC_JXL_H_
|
||||
|
||||
// Decodes JPEG XL images in memory.
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "jxl/parallel_runner.h"
|
||||
#include "jxl/types.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
struct JXLDecompressParams {
|
||||
// If empty, little endian float formats will be accepted.
|
||||
std::vector<JxlPixelFormat> accepted_formats;
|
||||
|
||||
// Requested output color space description.
|
||||
std::string color_space;
|
||||
// If set, performs tone mapping to this intensity target luminance.
|
||||
float display_nits = 0.0;
|
||||
// Whether spot colors are rendered on the image.
|
||||
bool render_spotcolors = true;
|
||||
// Whether to keep or undo the orientation given in the header.
|
||||
bool keep_orientation = false;
|
||||
|
||||
// If runner_opaque is set, the decoder uses this parallel runner.
|
||||
JxlParallelRunner runner;
|
||||
void* runner_opaque = nullptr;
|
||||
|
||||
// Whether truncated input should be treated as an error.
|
||||
bool allow_partial_input = false;
|
||||
|
||||
// How many passes to decode at most. By default, decode everything.
|
||||
uint32_t max_passes = std::numeric_limits<uint32_t>::max();
|
||||
|
||||
// Alternatively, one can specify the maximum tolerable downscaling factor
|
||||
// with respect to the full size of the image. By default, nothing less than
|
||||
// the full size is requested.
|
||||
size_t max_downsampling = 1;
|
||||
|
||||
// Whether to use the image callback or the image buffer to get the output.
|
||||
bool use_image_callback = true;
|
||||
// Whether to unpremultiply colors for associated alpha channels.
|
||||
bool unpremultiply_alpha = false;
|
||||
};
|
||||
|
||||
bool DecodeImageJXL(const uint8_t* bytes, size_t bytes_size,
|
||||
const JXLDecompressParams& dparams, size_t* decoded_bytes,
|
||||
PackedPixelFile* ppf,
|
||||
std::vector<uint8_t>* jpeg_bytes = nullptr);
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_EXTRAS_DEC_JXL_H_
|
||||
202
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/pgx.cc
vendored
Normal file
202
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/pgx.cc
vendored
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#include "lib/extras/dec/pgx.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "lib/jxl/base/bits.h"
|
||||
#include "lib/jxl/base/compiler_specific.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
namespace {
|
||||
|
||||
struct HeaderPGX {
|
||||
// NOTE: PGX is always grayscale
|
||||
size_t xsize;
|
||||
size_t ysize;
|
||||
size_t bits_per_sample;
|
||||
bool big_endian;
|
||||
bool is_signed;
|
||||
};
|
||||
|
||||
class Parser {
|
||||
public:
|
||||
explicit Parser(const Span<const uint8_t> bytes)
|
||||
: pos_(bytes.data()), end_(pos_ + bytes.size()) {}
|
||||
|
||||
// Sets "pos" to the first non-header byte/pixel on success.
|
||||
Status ParseHeader(HeaderPGX* header, const uint8_t** pos) {
|
||||
// codec.cc ensures we have at least two bytes => no range check here.
|
||||
if (pos_[0] != 'P' || pos_[1] != 'G') return false;
|
||||
pos_ += 2;
|
||||
return ParseHeaderPGX(header, pos);
|
||||
}
|
||||
|
||||
// Exposed for testing
|
||||
Status ParseUnsigned(size_t* number) {
|
||||
if (pos_ == end_) return JXL_FAILURE("PGX: reached end before number");
|
||||
if (!IsDigit(*pos_)) return JXL_FAILURE("PGX: expected unsigned number");
|
||||
|
||||
*number = 0;
|
||||
while (pos_ < end_ && *pos_ >= '0' && *pos_ <= '9') {
|
||||
*number *= 10;
|
||||
*number += *pos_ - '0';
|
||||
++pos_;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
static bool IsDigit(const uint8_t c) { return '0' <= c && c <= '9'; }
|
||||
static bool IsLineBreak(const uint8_t c) { return c == '\r' || c == '\n'; }
|
||||
static bool IsWhitespace(const uint8_t c) {
|
||||
return IsLineBreak(c) || c == '\t' || c == ' ';
|
||||
}
|
||||
|
||||
Status SkipSpace() {
|
||||
if (pos_ == end_) return JXL_FAILURE("PGX: reached end before space");
|
||||
const uint8_t c = *pos_;
|
||||
if (c != ' ') return JXL_FAILURE("PGX: expected space");
|
||||
++pos_;
|
||||
return true;
|
||||
}
|
||||
|
||||
Status SkipLineBreak() {
|
||||
if (pos_ == end_) return JXL_FAILURE("PGX: reached end before line break");
|
||||
// Line break can be either "\n" (0a) or "\r\n" (0d 0a).
|
||||
if (*pos_ == '\n') {
|
||||
pos_++;
|
||||
return true;
|
||||
} else if (*pos_ == '\r' && pos_ + 1 != end_ && *(pos_ + 1) == '\n') {
|
||||
pos_ += 2;
|
||||
return true;
|
||||
}
|
||||
return JXL_FAILURE("PGX: expected line break");
|
||||
}
|
||||
|
||||
Status SkipSingleWhitespace() {
|
||||
if (pos_ == end_) return JXL_FAILURE("PGX: reached end before whitespace");
|
||||
if (!IsWhitespace(*pos_)) return JXL_FAILURE("PGX: expected whitespace");
|
||||
++pos_;
|
||||
return true;
|
||||
}
|
||||
|
||||
Status ParseHeaderPGX(HeaderPGX* header, const uint8_t** pos) {
|
||||
JXL_RETURN_IF_ERROR(SkipSpace());
|
||||
if (pos_ + 2 > end_) return JXL_FAILURE("PGX: header too small");
|
||||
if (*pos_ == 'M' && *(pos_ + 1) == 'L') {
|
||||
header->big_endian = true;
|
||||
} else if (*pos_ == 'L' && *(pos_ + 1) == 'M') {
|
||||
header->big_endian = false;
|
||||
} else {
|
||||
return JXL_FAILURE("PGX: invalid endianness");
|
||||
}
|
||||
pos_ += 2;
|
||||
JXL_RETURN_IF_ERROR(SkipSpace());
|
||||
if (pos_ == end_) return JXL_FAILURE("PGX: header too small");
|
||||
if (*pos_ == '+') {
|
||||
header->is_signed = false;
|
||||
} else if (*pos_ == '-') {
|
||||
header->is_signed = true;
|
||||
} else {
|
||||
return JXL_FAILURE("PGX: invalid signedness");
|
||||
}
|
||||
pos_++;
|
||||
// Skip optional space
|
||||
if (pos_ < end_ && *pos_ == ' ') pos_++;
|
||||
JXL_RETURN_IF_ERROR(ParseUnsigned(&header->bits_per_sample));
|
||||
JXL_RETURN_IF_ERROR(SkipSingleWhitespace());
|
||||
JXL_RETURN_IF_ERROR(ParseUnsigned(&header->xsize));
|
||||
JXL_RETURN_IF_ERROR(SkipSingleWhitespace());
|
||||
JXL_RETURN_IF_ERROR(ParseUnsigned(&header->ysize));
|
||||
// 0xa, or 0xd 0xa.
|
||||
JXL_RETURN_IF_ERROR(SkipLineBreak());
|
||||
|
||||
// TODO(jon): could do up to 24-bit by converting the values to
|
||||
// JXL_TYPE_FLOAT.
|
||||
if (header->bits_per_sample > 16) {
|
||||
return JXL_FAILURE("PGX: >16 bits not yet supported");
|
||||
}
|
||||
// TODO(lode): support signed integers. This may require changing the way
|
||||
// external_image works.
|
||||
if (header->is_signed) {
|
||||
return JXL_FAILURE("PGX: signed not yet supported");
|
||||
}
|
||||
|
||||
size_t numpixels = header->xsize * header->ysize;
|
||||
size_t bytes_per_pixel = header->bits_per_sample <= 8 ? 1 : 2;
|
||||
if (pos_ + numpixels * bytes_per_pixel > end_) {
|
||||
return JXL_FAILURE("PGX: data too small");
|
||||
}
|
||||
|
||||
*pos = pos_;
|
||||
return true;
|
||||
}
|
||||
|
||||
const uint8_t* pos_;
|
||||
const uint8_t* const end_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
Status DecodeImagePGX(const Span<const uint8_t> bytes,
|
||||
const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints,
|
||||
PackedPixelFile* ppf) {
|
||||
Parser parser(bytes);
|
||||
HeaderPGX header = {};
|
||||
const uint8_t* pos;
|
||||
if (!parser.ParseHeader(&header, &pos)) return false;
|
||||
JXL_RETURN_IF_ERROR(
|
||||
VerifyDimensions(&constraints, header.xsize, header.ysize));
|
||||
if (header.bits_per_sample == 0 || header.bits_per_sample > 32) {
|
||||
return JXL_FAILURE("PGX: bits_per_sample invalid");
|
||||
}
|
||||
|
||||
JXL_RETURN_IF_ERROR(ApplyColorHints(color_hints, /*color_already_set=*/false,
|
||||
/*is_gray=*/true, ppf));
|
||||
ppf->info.xsize = header.xsize;
|
||||
ppf->info.ysize = header.ysize;
|
||||
// Original data is uint, so exponent_bits_per_sample = 0.
|
||||
ppf->info.bits_per_sample = header.bits_per_sample;
|
||||
ppf->info.exponent_bits_per_sample = 0;
|
||||
ppf->info.uses_original_profile = true;
|
||||
|
||||
// No alpha in PGX
|
||||
ppf->info.alpha_bits = 0;
|
||||
ppf->info.alpha_exponent_bits = 0;
|
||||
ppf->info.num_color_channels = 1; // Always grayscale
|
||||
ppf->info.orientation = JXL_ORIENT_IDENTITY;
|
||||
|
||||
JxlDataType data_type;
|
||||
if (header.bits_per_sample > 8) {
|
||||
data_type = JXL_TYPE_UINT16;
|
||||
} else {
|
||||
data_type = JXL_TYPE_UINT8;
|
||||
}
|
||||
|
||||
const JxlPixelFormat format{
|
||||
/*num_channels=*/1,
|
||||
/*data_type=*/data_type,
|
||||
/*endianness=*/header.big_endian ? JXL_BIG_ENDIAN : JXL_LITTLE_ENDIAN,
|
||||
/*align=*/0,
|
||||
};
|
||||
ppf->frames.clear();
|
||||
// Allocates the frame buffer.
|
||||
ppf->frames.emplace_back(header.xsize, header.ysize, format);
|
||||
const auto& frame = ppf->frames.back();
|
||||
size_t pgx_remaining_size = bytes.data() + bytes.size() - pos;
|
||||
if (pgx_remaining_size < frame.color.pixels_size) {
|
||||
return JXL_FAILURE("PGX file too small");
|
||||
}
|
||||
memcpy(frame.color.pixels(), pos, frame.color.pixels_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
32
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/pgx.h
vendored
Normal file
32
thirdparty/SDL3_image/external/libjxl/lib/extras/dec/pgx.h
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) the JPEG XL Project Authors. All rights reserved.
|
||||
//
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
#ifndef LIB_EXTRAS_DEC_PGX_H_
|
||||
#define LIB_EXTRAS_DEC_PGX_H_
|
||||
|
||||
// Decodes PGX pixels in memory.
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "lib/extras/dec/color_hints.h"
|
||||
#include "lib/extras/packed_image.h"
|
||||
#include "lib/jxl/base/data_parallel.h"
|
||||
#include "lib/jxl/base/padded_bytes.h"
|
||||
#include "lib/jxl/base/span.h"
|
||||
#include "lib/jxl/base/status.h"
|
||||
#include "lib/jxl/codec_in_out.h"
|
||||
|
||||
namespace jxl {
|
||||
namespace extras {
|
||||
|
||||
// Decodes `bytes` into `ppf`.
|
||||
Status DecodeImagePGX(Span<const uint8_t> bytes, const ColorHints& color_hints,
|
||||
const SizeConstraints& constraints, PackedPixelFile* ppf);
|
||||
|
||||
} // namespace extras
|
||||
} // namespace jxl
|
||||
|
||||
#endif // LIB_EXTRAS_DEC_PGX_H_
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue